/framework/lib/classes/PHPExcel/Shared/PDF/tcpdf.php

https://bitbucket.org/designbyheart/original · PHP · 25629 lines · 17636 code · 698 blank · 7295 comment · 3846 complexity · 48aaa955d5f2857a2437e30f5e29414a MD5 · raw 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. /**
  99. * This is a PHP class for generating PDF documents without requiring external extensions.<br>
  100. * TCPDF project (http://www.tcpdf.org) was originally derived in 2002 from the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org), but now is almost entirely rewritten.<br>
  101. * <h3>TCPDF main features are:</h3>
  102. * <ul>
  103. * <li>no external libraries are required for the basic functions;</li>
  104. * <li>all standard page formats, custom page formats, custom margins and units of measure;</li>
  105. * <li>UTF-8 Unicode and Right-To-Left languages;</li>
  106. * <li>TrueTypeUnicode, OpenTypeUnicode, TrueType, OpenType, Type1 and CID-0 fonts;</li>
  107. * <li>font subsetting;</li>
  108. * <li>methods to publish some XHTML + CSS code, Javascript and Forms;</li>
  109. * <li>images, graphic (geometric figures) and transformation methods;
  110. * <li>supports JPEG, PNG and SVG images natively, all images supported by GD (GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM) and all images supported via ImagMagick (http://www.imagemagick.org/www/formats.html)</li>
  111. * <li>1D and 2D barcodes: CODE 39, ANSI MH10.8M-1983, USD-3, 3 of 9, CODE 93, USS-93, Standard 2 of 5, Interleaved 2 of 5, CODE 128 A/B/C, 2 and 5 Digits UPC-Based Extention, EAN 8, EAN 13, UPC-A, UPC-E, MSI, POSTNET, PLANET, RMS4CC (Royal Mail 4-state Customer Code), CBC (Customer Bar Code), KIX (Klant index - Customer index), Intelligent Mail Barcode, Onecode, USPS-B-3200, CODABAR, CODE 11, PHARMACODE, PHARMACODE TWO-TRACKS, QR-Code, PDF417;</li>
  112. * <li>Grayscale, RGB, CMYK, Spot Colors and Transparencies;</li>
  113. * <li>automatic page header and footer management;</li>
  114. * <li>document encryption up to 256 bit and digital signature certifications;</li>
  115. * <li>transactions to UNDO commands;</li>
  116. * <li>PDF annotations, including links, text and file attachments;</li>
  117. * <li>text rendering modes (fill, stroke and clipping);</li>
  118. * <li>multiple columns mode;</li>
  119. * <li>no-write page regions;</li>
  120. * <li>bookmarks and table of content;</li>
  121. * <li>text hyphenation;</li>
  122. * <li>text stretching and spacing (tracking/kerning);</li>
  123. * <li>automatic page break, line break and text alignments including justification;</li>
  124. * <li>automatic page numbering and page groups;</li>
  125. * <li>move and delete pages;</li>
  126. * <li>page compression (requires php-zlib extension);</li>
  127. * <li>XOBject Templates;</li>
  128. * </ul>
  129. * Tools to encode your unicode fonts are on fonts/utils directory.</p>
  130. * @package com.tecnick.tcpdf
  131. * @abstract Class for generating PDF files on-the-fly without requiring external extensions.
  132. * @author Nicola Asuni
  133. * @copyright 2002-2010 Nicola Asuni - Tecnick.com S.r.l (www.tecnick.com) Via Della Pace, 11 - 09044 - Quartucciu (CA) - ITALY - www.tecnick.com - info@tecnick.com
  134. * @link http://www.tcpdf.org
  135. * @license http://www.gnu.org/copyleft/lesser.html LGPL
  136. * @version 5.9.009
  137. */
  138. /**
  139. * main configuration file
  140. * (define the K_TCPDF_EXTERNAL_CONFIG constant to skip this file)
  141. */
  142. require_once(dirname(__FILE__).'/config/tcpdf_config.php');
  143. /**
  144. * define default PDF document producer
  145. */
  146. define('PDF_PRODUCER', 'TCPDF 5.9.009 (http://www.tcpdf.org)');
  147. /**
  148. * This is a PHP class for generating PDF documents without requiring external extensions.<br>
  149. * TCPDF project (http://www.tcpdf.org) has been originally derived in 2002 from the Public Domain FPDF class by Olivier Plathey (http://www.fpdf.org), but now is almost entirely rewritten.<br>
  150. * @name TCPDF
  151. * @package com.tecnick.tcpdf
  152. * @version 5.9.009
  153. * @author Nicola Asuni - info@tecnick.com
  154. * @link http://www.tcpdf.org
  155. * @license http://www.gnu.org/copyleft/lesser.html LGPL
  156. */
  157. class TCPDF {
  158. // Protected properties
  159. /**
  160. * @var current page number
  161. * @access protected
  162. */
  163. protected $page;
  164. /**
  165. * @var current object number
  166. * @access protected
  167. */
  168. protected $n;
  169. /**
  170. * @var array of object offsets
  171. * @access protected
  172. */
  173. protected $offsets;
  174. /**
  175. * @var buffer holding in-memory PDF
  176. * @access protected
  177. */
  178. protected $buffer;
  179. /**
  180. * @var array containing pages
  181. * @access protected
  182. */
  183. protected $pages = array();
  184. /**
  185. * @var current document state
  186. * @access protected
  187. */
  188. protected $state;
  189. /**
  190. * @var compression flag
  191. * @access protected
  192. */
  193. protected $compress;
  194. /**
  195. * @var current page orientation (P = Portrait, L = Landscape)
  196. * @access protected
  197. */
  198. protected $CurOrientation;
  199. /**
  200. * @var Page dimensions
  201. * @access protected
  202. */
  203. protected $pagedim = array();
  204. /**
  205. * @var scale factor (number of points in user unit)
  206. * @access protected
  207. */
  208. protected $k;
  209. /**
  210. * @var width of page format in points
  211. * @access protected
  212. */
  213. protected $fwPt;
  214. /**
  215. * @var height of page format in points
  216. * @access protected
  217. */
  218. protected $fhPt;
  219. /**
  220. * @var current width of page in points
  221. * @access protected
  222. */
  223. protected $wPt;
  224. /**
  225. * @var current height of page in points
  226. * @access protected
  227. */
  228. protected $hPt;
  229. /**
  230. * @var current width of page in user unit
  231. * @access protected
  232. */
  233. protected $w;
  234. /**
  235. * @var current height of page in user unit
  236. * @access protected
  237. */
  238. protected $h;
  239. /**
  240. * @var left margin
  241. * @access protected
  242. */
  243. protected $lMargin;
  244. /**
  245. * @var top margin
  246. * @access protected
  247. */
  248. protected $tMargin;
  249. /**
  250. * @var right margin
  251. * @access protected
  252. */
  253. protected $rMargin;
  254. /**
  255. * @var page break margin
  256. * @access protected
  257. */
  258. protected $bMargin;
  259. /**
  260. * @var array of cell internal paddings ('T' => top, 'R' => right, 'B' => bottom, 'L' => left)
  261. * @since 5.9.000 (2010-10-03)
  262. * @access protected
  263. */
  264. protected $cell_padding = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
  265. /**
  266. * @var array of cell margins ('T' => top, 'R' => right, 'B' => bottom, 'L' => left)
  267. * @since 5.9.000 (2010-10-04)
  268. * @access protected
  269. */
  270. protected $cell_margin = array('T' => 0, 'R' => 0, 'B' => 0, 'L' => 0);
  271. /**
  272. * @var current horizontal position in user unit for cell positioning
  273. * @access protected
  274. */
  275. protected $x;
  276. /**
  277. * @var current vertical position in user unit for cell positioning
  278. * @access protected
  279. */
  280. protected $y;
  281. /**
  282. * @var height of last cell printed
  283. * @access protected
  284. */
  285. protected $lasth;
  286. /**
  287. * @var line width in user unit
  288. * @access protected
  289. */
  290. protected $LineWidth;
  291. /**
  292. * @var array of standard font names
  293. * @access protected
  294. */
  295. protected $CoreFonts;
  296. /**
  297. * @var array of used fonts
  298. * @access protected
  299. */
  300. protected $fonts = array();
  301. /**
  302. * @var array of font files
  303. * @access protected
  304. */
  305. protected $FontFiles = array();
  306. /**
  307. * @var array of encoding differences
  308. * @access protected
  309. */
  310. protected $diffs = array();
  311. /**
  312. * @var array of used images
  313. * @access protected
  314. */
  315. protected $images = array();
  316. /**
  317. * @var array of Annotations in pages
  318. * @access protected
  319. */
  320. protected $PageAnnots = array();
  321. /**
  322. * @var array of internal links
  323. * @access protected
  324. */
  325. protected $links = array();
  326. /**
  327. * @var current font family
  328. * @access protected
  329. */
  330. protected $FontFamily;
  331. /**
  332. * @var current font style
  333. * @access protected
  334. */
  335. protected $FontStyle;
  336. /**
  337. * @var current font ascent (distance between font top and baseline)
  338. * @access protected
  339. * @since 2.8.000 (2007-03-29)
  340. */
  341. protected $FontAscent;
  342. /**
  343. * @var current font descent (distance between font bottom and baseline)
  344. * @access protected
  345. * @since 2.8.000 (2007-03-29)
  346. */
  347. protected $FontDescent;
  348. /**
  349. * @var underlining flag
  350. * @access protected
  351. */
  352. protected $underline;
  353. /**
  354. * @var overlining flag
  355. * @access protected
  356. */
  357. protected $overline;
  358. /**
  359. * @var current font info
  360. * @access protected
  361. */
  362. protected $CurrentFont;
  363. /**
  364. * @var current font size in points
  365. * @access protected
  366. */
  367. protected $FontSizePt;
  368. /**
  369. * @var current font size in user unit
  370. * @access protected
  371. */
  372. protected $FontSize;
  373. /**
  374. * @var commands for drawing color
  375. * @access protected
  376. */
  377. protected $DrawColor;
  378. /**
  379. * @var commands for filling color
  380. * @access protected
  381. */
  382. protected $FillColor;
  383. /**
  384. * @var commands for text color
  385. * @access protected
  386. */
  387. protected $TextColor;
  388. /**
  389. * @var indicates whether fill and text colors are different
  390. * @access protected
  391. */
  392. protected $ColorFlag;
  393. /**
  394. * @var automatic page breaking
  395. * @access protected
  396. */
  397. protected $AutoPageBreak;
  398. /**
  399. * @var threshold used to trigger page breaks
  400. * @access protected
  401. */
  402. protected $PageBreakTrigger;
  403. /**
  404. * @var flag set when processing footer
  405. * @access protected
  406. */
  407. protected $InFooter = false;
  408. /**
  409. * @var zoom display mode
  410. * @access protected
  411. */
  412. protected $ZoomMode;
  413. /**
  414. * @var layout display mode
  415. * @access protected
  416. */
  417. protected $LayoutMode;
  418. /**
  419. * @var title
  420. * @access protected
  421. */
  422. protected $title = '';
  423. /**
  424. * @var subject
  425. * @access protected
  426. */
  427. protected $subject = '';
  428. /**
  429. * @var author
  430. * @access protected
  431. */
  432. protected $author = '';
  433. /**
  434. * @var keywords
  435. * @access protected
  436. */
  437. protected $keywords = '';
  438. /**
  439. * @var creator
  440. * @access protected
  441. */
  442. protected $creator = '';
  443. /**
  444. * @var alias for total number of pages
  445. * @access protected
  446. */
  447. protected $AliasNbPages = '{nb}';
  448. /**
  449. * @var alias for page number
  450. * @access protected
  451. */
  452. protected $AliasNumPage = '{pnb}';
  453. /**
  454. * @var right-bottom corner X coordinate of inserted image
  455. * @since 2002-07-31
  456. * @author Nicola Asuni
  457. * @access protected
  458. */
  459. protected $img_rb_x;
  460. /**
  461. * @var right-bottom corner Y coordinate of inserted image
  462. * @since 2002-07-31
  463. * @author Nicola Asuni
  464. * @access protected
  465. */
  466. protected $img_rb_y;
  467. /**
  468. * @var adjusting factor to convert pixels to user units.
  469. * @since 2004-06-14
  470. * @author Nicola Asuni
  471. * @access protected
  472. */
  473. protected $imgscale = 1;
  474. /**
  475. * @var boolean set to true when the input text is unicode (require unicode fonts)
  476. * @since 2005-01-02
  477. * @author Nicola Asuni
  478. * @access protected
  479. */
  480. protected $isunicode = false;
  481. /**
  482. * @var object containing unicode data
  483. * @since 5.9.004 (2010-10-18)
  484. * @author Nicola Asuni
  485. * @access protected
  486. */
  487. protected $unicode;
  488. /**
  489. * @var PDF version
  490. * @since 1.5.3
  491. * @access protected
  492. */
  493. protected $PDFVersion = '1.7';
  494. /**
  495. * @var Minimum distance between header and top page margin.
  496. * @access protected
  497. */
  498. protected $header_margin;
  499. /**
  500. * @var Minimum distance between footer and bottom page margin.
  501. * @access protected
  502. */
  503. protected $footer_margin;
  504. /**
  505. * @var original left margin value
  506. * @access protected
  507. * @since 1.53.0.TC013
  508. */
  509. protected $original_lMargin;
  510. /**
  511. * @var original right margin value
  512. * @access protected
  513. * @since 1.53.0.TC013
  514. */
  515. protected $original_rMargin;
  516. /**
  517. * @var Header font.
  518. * @access protected
  519. */
  520. protected $header_font;
  521. /**
  522. * @var Footer font.
  523. * @access protected
  524. */
  525. protected $footer_font;
  526. /**
  527. * @var Language templates.
  528. * @access protected
  529. */
  530. protected $l;
  531. /**
  532. * @var Barcode to print on page footer (only if set).
  533. * @access protected
  534. */
  535. protected $barcode = false;
  536. /**
  537. * @var If true prints header
  538. * @access protected
  539. */
  540. protected $print_header = true;
  541. /**
  542. * @var If true prints footer.
  543. * @access protected
  544. */
  545. protected $print_footer = true;
  546. /**
  547. * @var Header image logo.
  548. * @access protected
  549. */
  550. protected $header_logo = '';
  551. /**
  552. * @var Header image logo width in mm.
  553. * @access protected
  554. */
  555. protected $header_logo_width = 30;
  556. /**
  557. * @var String to print as title on document header.
  558. * @access protected
  559. */
  560. protected $header_title = '';
  561. /**
  562. * @var String to print on document header.
  563. * @access protected
  564. */
  565. protected $header_string = '';
  566. /**
  567. * @var Default number of columns for html table.
  568. * @access protected
  569. */
  570. protected $default_table_columns = 4;
  571. // variables for html parser
  572. /**
  573. * @var HTML PARSER: array to store current link and rendering styles.
  574. * @access protected
  575. */
  576. protected $HREF = array();
  577. /**
  578. * @var store a list of available fonts on filesystem.
  579. * @access protected
  580. */
  581. protected $fontlist = array();
  582. /**
  583. * @var current foreground color
  584. * @access protected
  585. */
  586. protected $fgcolor;
  587. /**
  588. * @var HTML PARSER: array of boolean values, true in case of ordered list (OL), false otherwise.
  589. * @access protected
  590. */
  591. protected $listordered = array();
  592. /**
  593. * @var HTML PARSER: array count list items on nested lists.
  594. * @access protected
  595. */
  596. protected $listcount = array();
  597. /**
  598. * @var HTML PARSER: current list nesting level.
  599. * @access protected
  600. */
  601. protected $listnum = 0;
  602. /**
  603. * @var HTML PARSER: indent amount for lists.
  604. * @access protected
  605. */
  606. protected $listindent = 0;
  607. /**
  608. * @var HTML PARSER: current list indententation level.
  609. * @access protected
  610. */
  611. protected $listindentlevel = 0;
  612. /**
  613. * @var current background color
  614. * @access protected
  615. */
  616. protected $bgcolor;
  617. /**
  618. * @var Store temporary font size in points.
  619. * @access protected
  620. */
  621. protected $tempfontsize = 10;
  622. /**
  623. * @var spacer for LI tags.
  624. * @access protected
  625. */
  626. protected $lispacer = '';
  627. /**
  628. * @var default encoding
  629. * @access protected
  630. * @since 1.53.0.TC010
  631. */
  632. protected $encoding = 'UTF-8';
  633. /**
  634. * @var PHP internal encoding
  635. * @access protected
  636. * @since 1.53.0.TC016
  637. */
  638. protected $internal_encoding;
  639. /**
  640. * @var indicates if the document language is Right-To-Left
  641. * @access protected
  642. * @since 2.0.000
  643. */
  644. protected $rtl = false;
  645. /**
  646. * @var used to force RTL or LTR string inversion
  647. * @access protected
  648. * @since 2.0.000
  649. */
  650. protected $tmprtl = false;
  651. // --- Variables used for document encryption:
  652. /**
  653. * Indicates whether document is protected
  654. * @access protected
  655. * @since 2.0.000 (2008-01-02)
  656. */
  657. protected $encrypted;
  658. /**
  659. * Array containing encryption settings
  660. * @access protected
  661. * @since 5.0.005 (2010-05-11)
  662. */
  663. protected $encryptdata = array();
  664. /**
  665. * last RC4 key encrypted (cached for optimisation)
  666. * @access protected
  667. * @since 2.0.000 (2008-01-02)
  668. */
  669. protected $last_enc_key;
  670. /**
  671. * last RC4 computed key
  672. * @access protected
  673. * @since 2.0.000 (2008-01-02)
  674. */
  675. protected $last_enc_key_c;
  676. /**
  677. * Encryption padding
  678. * @access protected
  679. */
  680. 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";
  681. /**
  682. * File ID (used on trailer)
  683. * @access protected
  684. * @since 5.0.005 (2010-05-12)
  685. */
  686. protected $file_id;
  687. // --- bookmark ---
  688. /**
  689. * Outlines for bookmark
  690. * @access protected
  691. * @since 2.1.002 (2008-02-12)
  692. */
  693. protected $outlines = array();
  694. /**
  695. * Outline root for bookmark
  696. * @access protected
  697. * @since 2.1.002 (2008-02-12)
  698. */
  699. protected $OutlineRoot;
  700. // --- javascript and form ---
  701. /**
  702. * javascript code
  703. * @access protected
  704. * @since 2.1.002 (2008-02-12)
  705. */
  706. protected $javascript = '';
  707. /**
  708. * javascript counter
  709. * @access protected
  710. * @since 2.1.002 (2008-02-12)
  711. */
  712. protected $n_js;
  713. /**
  714. * line trough state
  715. * @access protected
  716. * @since 2.8.000 (2008-03-19)
  717. */
  718. protected $linethrough;
  719. /**
  720. * Array with additional document-wide usage rights for the document.
  721. * @access protected
  722. * @since 5.8.014 (2010-08-23)
  723. */
  724. protected $ur = array();
  725. /**
  726. * Dot Per Inch Document Resolution (do not change)
  727. * @access protected
  728. * @since 3.0.000 (2008-03-27)
  729. */
  730. protected $dpi = 72;
  731. /**
  732. * Array of page numbers were a new page group was started
  733. * @access protected
  734. * @since 3.0.000 (2008-03-27)
  735. */
  736. protected $newpagegroup = array();
  737. /**
  738. * Contains the number of pages of the groups
  739. * @access protected
  740. * @since 3.0.000 (2008-03-27)
  741. */
  742. protected $pagegroups;
  743. /**
  744. * Contains the alias of the current page group
  745. * @access protected
  746. * @since 3.0.000 (2008-03-27)
  747. */
  748. protected $currpagegroup;
  749. /**
  750. * Restrict the rendering of some elements to screen or printout.
  751. * @access protected
  752. * @since 3.0.000 (2008-03-27)
  753. */
  754. protected $visibility = 'all';
  755. /**
  756. * Print visibility.
  757. * @access protected
  758. * @since 3.0.000 (2008-03-27)
  759. */
  760. protected $n_ocg_print;
  761. /**
  762. * View visibility.
  763. * @access protected
  764. * @since 3.0.000 (2008-03-27)
  765. */
  766. protected $n_ocg_view;
  767. /**
  768. * Array of transparency objects and parameters.
  769. * @access protected
  770. * @since 3.0.000 (2008-03-27)
  771. */
  772. protected $extgstates;
  773. /**
  774. * Set the default JPEG compression quality (1-100)
  775. * @access protected
  776. * @since 3.0.000 (2008-03-27)
  777. */
  778. protected $jpeg_quality;
  779. /**
  780. * Default cell height ratio.
  781. * @access protected
  782. * @since 3.0.014 (2008-05-23)
  783. */
  784. protected $cell_height_ratio = K_CELL_HEIGHT_RATIO;
  785. /**
  786. * PDF viewer preferences.
  787. * @access protected
  788. * @since 3.1.000 (2008-06-09)
  789. */
  790. protected $viewer_preferences;
  791. /**
  792. * A name object specifying how the document should be displayed when opened.
  793. * @access protected
  794. * @since 3.1.000 (2008-06-09)
  795. */
  796. protected $PageMode;
  797. /**
  798. * Array for storing gradient information.
  799. * @access protected
  800. * @since 3.1.000 (2008-06-09)
  801. */
  802. protected $gradients = array();
  803. /**
  804. * Array used to store positions inside the pages buffer.
  805. * keys are the page numbers
  806. * @access protected
  807. * @since 3.2.000 (2008-06-26)
  808. */
  809. protected $intmrk = array();
  810. /**
  811. * Array used to store positions inside the pages buffer.
  812. * keys are the page numbers
  813. * @access protected
  814. * @since 5.7.000 (2010-08-03)
  815. */
  816. protected $bordermrk = array();
  817. /**
  818. * Array used to store page positions to track empty pages.
  819. * keys are the page numbers
  820. * @access protected
  821. * @since 5.8.007 (2010-08-18)
  822. */
  823. protected $emptypagemrk = array();
  824. /**
  825. * Array used to store content positions inside the pages buffer.
  826. * keys are the page numbers
  827. * @access protected
  828. * @since 4.6.021 (2009-07-20)
  829. */
  830. protected $cntmrk = array();
  831. /**
  832. * Array used to store footer positions of each page.
  833. * @access protected
  834. * @since 3.2.000 (2008-07-01)
  835. */
  836. protected $footerpos = array();
  837. /**
  838. * Array used to store footer length of each page.
  839. * @access protected
  840. * @since 4.0.014 (2008-07-29)
  841. */
  842. protected $footerlen = array();
  843. /**
  844. * True if a newline is created.
  845. * @access protected
  846. * @since 3.2.000 (2008-07-01)
  847. */
  848. protected $newline = true;
  849. /**
  850. * End position of the latest inserted line
  851. * @access protected
  852. * @since 3.2.000 (2008-07-01)
  853. */
  854. protected $endlinex = 0;
  855. /**
  856. * PDF string for last line width
  857. * @access protected
  858. * @since 4.0.006 (2008-07-16)
  859. */
  860. protected $linestyleWidth = '';
  861. /**
  862. * PDF string for last line width
  863. * @access protected
  864. * @since 4.0.006 (2008-07-16)
  865. */
  866. protected $linestyleCap = '0 J';
  867. /**
  868. * PDF string for last line width
  869. * @access protected
  870. * @since 4.0.006 (2008-07-16)
  871. */
  872. protected $linestyleJoin = '0 j';
  873. /**
  874. * PDF string for last line width
  875. * @access protected
  876. * @since 4.0.006 (2008-07-16)
  877. */
  878. protected $linestyleDash = '[] 0 d';
  879. /**
  880. * True if marked-content sequence is open
  881. * @access protected
  882. * @since 4.0.013 (2008-07-28)
  883. */
  884. protected $openMarkedContent = false;
  885. /**
  886. * Count the latest inserted vertical spaces on HTML
  887. * @access protected
  888. * @since 4.0.021 (2008-08-24)
  889. */
  890. protected $htmlvspace = 0;
  891. /**
  892. * Array of Spot colors
  893. * @access protected
  894. * @since 4.0.024 (2008-09-12)
  895. */
  896. protected $spot_colors = array();
  897. /**
  898. * Symbol used for HTML unordered list items
  899. * @access protected
  900. * @since 4.0.028 (2008-09-26)
  901. */
  902. protected $lisymbol = '';
  903. /**
  904. * String used to mark the beginning and end of EPS image blocks
  905. * @access protected
  906. * @since 4.1.000 (2008-10-18)
  907. */
  908. protected $epsmarker = 'x#!#EPS#!#x';
  909. /**
  910. * Array of transformation matrix
  911. * @access protected
  912. * @since 4.2.000 (2008-10-29)
  913. */
  914. protected $transfmatrix = array();
  915. /**
  916. * Current key for transformation matrix
  917. * @access protected
  918. * @since 4.8.005 (2009-09-17)
  919. */
  920. protected $transfmatrix_key = 0;
  921. /**
  922. * Booklet mode for double-sided pages
  923. * @access protected
  924. * @since 4.2.000 (2008-10-29)
  925. */
  926. protected $booklet = false;
  927. /**
  928. * Epsilon value used for float calculations
  929. * @access protected
  930. * @since 4.2.000 (2008-10-29)
  931. */
  932. protected $feps = 0.005;
  933. /**
  934. * Array used for custom vertical spaces for HTML tags
  935. * @access protected
  936. * @since 4.2.001 (2008-10-30)
  937. */
  938. protected $tagvspaces = array();
  939. /**
  940. * @var HTML PARSER: custom indent amount for lists.
  941. * Negative value means disabled.
  942. * @access protected
  943. * @since 4.2.007 (2008-11-12)
  944. */
  945. protected $customlistindent = -1;
  946. /**
  947. * @var if true keeps the border open for the cell sides that cross the page.
  948. * @access protected
  949. * @since 4.2.010 (2008-11-14)
  950. */
  951. protected $opencell = true;
  952. /**
  953. * @var array of files to embedd
  954. * @access protected
  955. * @since 4.4.000 (2008-12-07)
  956. */
  957. protected $embeddedfiles = array();
  958. /**
  959. * @var boolean true when inside html pre tag
  960. * @access protected
  961. * @since 4.4.001 (2008-12-08)
  962. */
  963. protected $premode = false;
  964. /**
  965. * Array used to store positions of graphics transformation blocks inside the page buffer.
  966. * keys are the page numbers
  967. * @access protected
  968. * @since 4.4.002 (2008-12-09)
  969. */
  970. protected $transfmrk = array();
  971. /**
  972. * Default color for html links
  973. * @access protected
  974. * @since 4.4.003 (2008-12-09)
  975. */
  976. protected $htmlLinkColorArray = array(0, 0, 255);
  977. /**
  978. * Default font style to add to html links
  979. * @access protected
  980. * @since 4.4.003 (2008-12-09)
  981. */
  982. protected $htmlLinkFontStyle = 'U';
  983. /**
  984. * Counts the number of pages.
  985. * @access protected
  986. * @since 4.5.000 (2008-12-31)
  987. */
  988. protected $numpages = 0;
  989. /**
  990. * Array containing page lengths in bytes.
  991. * @access protected
  992. * @since 4.5.000 (2008-12-31)
  993. */
  994. protected $pagelen = array();
  995. /**
  996. * Counts the number of pages.
  997. * @access protected
  998. * @since 4.5.000 (2008-12-31)
  999. */
  1000. protected $numimages = 0;
  1001. /**
  1002. * Store the image keys.
  1003. * @access protected
  1004. * @since 4.5.000 (2008-12-31)
  1005. */
  1006. protected $imagekeys = array();
  1007. /**
  1008. * Length of the buffer in bytes.
  1009. * @access protected
  1010. * @since 4.5.000 (2008-12-31)
  1011. */
  1012. protected $bufferlen = 0;
  1013. /**
  1014. * If true enables disk caching.
  1015. * @access protected
  1016. * @since 4.5.000 (2008-12-31)
  1017. */
  1018. protected $diskcache = false;
  1019. /**
  1020. * Counts the number of fonts.
  1021. * @access protected
  1022. * @since 4.5.000 (2009-01-02)
  1023. */
  1024. protected $numfonts = 0;
  1025. /**
  1026. * Store the font keys.
  1027. * @access protected
  1028. * @since 4.5.000 (2009-01-02)
  1029. */
  1030. protected $fontkeys = array();
  1031. /**
  1032. * Store the font object IDs.
  1033. * @access protected
  1034. * @since 4.8.001 (2009-09-09)
  1035. */
  1036. protected $font_obj_ids = array();
  1037. /**
  1038. * Store the fage status (true when opened, false when closed).
  1039. * @access protected
  1040. * @since 4.5.000 (2009-01-02)
  1041. */
  1042. protected $pageopen = array();
  1043. /**
  1044. * Default monospaced font
  1045. * @access protected
  1046. * @since 4.5.025 (2009-03-10)
  1047. */
  1048. protected $default_monospaced_font = 'courier';
  1049. /**
  1050. * Used to store a cloned copy of the current class object
  1051. * @access protected
  1052. * @since 4.5.029 (2009-03-19)
  1053. */
  1054. protected $objcopy;
  1055. /**
  1056. * Array used to store the lengths of cache files
  1057. * @access protected
  1058. * @since 4.5.029 (2009-03-19)
  1059. */
  1060. protected $cache_file_length = array();
  1061. /**
  1062. * Table header content to be repeated on each new page
  1063. * @access protected
  1064. * @since 4.5.030 (2009-03-20)
  1065. */
  1066. protected $thead = '';
  1067. /**
  1068. * Margins used for table header.
  1069. * @access protected
  1070. * @since 4.5.030 (2009-03-20)
  1071. */
  1072. protected $theadMargins = array();
  1073. /**
  1074. * Cache array for UTF8StringToArray() method.
  1075. * @access protected
  1076. * @since 4.5.037 (2009-04-07)
  1077. */
  1078. protected $cache_UTF8StringToArray = array();
  1079. /**
  1080. * Maximum size of cache array used for UTF8StringToArray() method.
  1081. * @access protected
  1082. * @since 4.5.037 (2009-04-07)
  1083. */
  1084. protected $cache_maxsize_UTF8StringToArray = 8;
  1085. /**
  1086. * Current size of cache array used for UTF8StringToArray() method.
  1087. * @access protected
  1088. * @since 4.5.037 (2009-04-07)
  1089. */
  1090. protected $cache_size_UTF8StringToArray = 0;
  1091. /**
  1092. * If true enables document signing
  1093. * @access protected
  1094. * @since 4.6.005 (2009-04-24)
  1095. */
  1096. protected $sign = false;
  1097. /**
  1098. * Signature data
  1099. * @access protected
  1100. * @since 4.6.005 (2009-04-24)
  1101. */
  1102. protected $signature_data = array();
  1103. /**
  1104. * Signature max length
  1105. * @access protected
  1106. * @since 4.6.005 (2009-04-24)
  1107. */
  1108. protected $signature_max_length = 11742;
  1109. /**
  1110. * data for signature appearance
  1111. * @access protected
  1112. * @since 5.3.011 (2010-06-16)
  1113. */
  1114. protected $signature_appearance = array('page' => 1, 'rect' => '0 0 0 0');
  1115. /**
  1116. * Regular expression used to find blank characters used for word-wrapping.
  1117. * @access protected
  1118. * @since 4.6.006 (2009-04-28)
  1119. */
  1120. protected $re_spaces = '/[^\S\xa0]/';
  1121. /**
  1122. * Array of parts $re_spaces
  1123. * @access protected
  1124. * @since 5.5.011 (2010-07-09)
  1125. */
  1126. protected $re_space = array('p' => '[^\S\xa0]', 'm' => '');
  1127. /**
  1128. * Signature object ID
  1129. * @access protected
  1130. * @since 4.6.022 (2009-06-23)
  1131. */
  1132. protected $sig_obj_id = 0;
  1133. /**
  1134. * ByteRange placemark used during signature process.
  1135. * @access protected
  1136. * @since 4.6.028 (2009-08-25)
  1137. */
  1138. protected $byterange_string = '/ByteRange[0 ********** ********** **********]';
  1139. /**
  1140. * Placemark used during signature process.
  1141. * @access protected
  1142. * @since 4.6.028 (2009-08-25)
  1143. */
  1144. protected $sig_annot_ref = '***SIGANNREF*** 0 R';
  1145. /**
  1146. * ID of page objects
  1147. * @access protected
  1148. * @since 4.7.000 (2009-08-29)
  1149. */
  1150. protected $page_obj_id = array();
  1151. /**
  1152. * List of form annotations IDs
  1153. * @access protected
  1154. * @since 4.8.000 (2009-09-07)
  1155. */
  1156. protected $form_obj_id = array();
  1157. /**
  1158. * Deafult Javascript field properties. Possible values are described on official Javascript for Acrobat API reference. Annotation options can be directly specified using the 'aopt' entry.
  1159. * @access protected
  1160. * @since 4.8.000 (2009-09-07)
  1161. */
  1162. protected $default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
  1163. /**
  1164. * Javascript objects array
  1165. * @access protected
  1166. * @since 4.8.000 (2009-09-07)
  1167. */
  1168. protected $js_objects = array();
  1169. /**
  1170. * Current form action (used during XHTML rendering)
  1171. * @access protected
  1172. * @since 4.8.000 (2009-09-07)
  1173. */
  1174. protected $form_action = '';
  1175. /**
  1176. * Current form encryption type (used during XHTML rendering)
  1177. * @access protected
  1178. * @since 4.8.000 (2009-09-07)
  1179. */
  1180. protected $form_enctype = 'application/x-www-form-urlencoded';
  1181. /**
  1182. * Current method to submit forms.
  1183. * @access protected
  1184. * @since 4.8.000 (2009-09-07)
  1185. */
  1186. protected $form_mode = 'post';
  1187. /**
  1188. * List of fonts used on form fields (fontname => fontkey).
  1189. * @access protected
  1190. * @since 4.8.001 (2009-09-09)
  1191. */
  1192. protected $annotation_fonts = array();
  1193. /**
  1194. * List of radio buttons parent objects.
  1195. * @access protected
  1196. * @since 4.8.001 (2009-09-09)
  1197. */
  1198. protected $radiobutton_groups = array();
  1199. /**
  1200. * List of radio group objects IDs
  1201. * @access protected
  1202. * @since 4.8.001 (2009-09-09)
  1203. */
  1204. protected $radio_groups = array();
  1205. /**
  1206. * Text indentation value (used for text-indent CSS attribute)
  1207. * @access protected
  1208. * @since 4.8.006 (2009-09-23)
  1209. */
  1210. protected $textindent = 0;
  1211. /**
  1212. * Store page number when startTransaction() is called.
  1213. * @access protected
  1214. * @since 4.8.006 (2009-09-23)
  1215. */
  1216. protected $start_transaction_page = 0;
  1217. /**
  1218. * Store Y position when startTransaction() is called.
  1219. * @access protected
  1220. * @since 4.9.001 (2010-03-28)
  1221. */
  1222. protected $start_transaction_y = 0;
  1223. /**
  1224. * True when we are printing the thead section on a new page
  1225. * @access protected
  1226. * @since 4.8.027 (2010-01-25)
  1227. */
  1228. protected $inthead = false;
  1229. /**
  1230. * Array of column measures (width, space, starting Y position)
  1231. * @access protected
  1232. * @since 4.9.001 (2010-03-28)
  1233. */
  1234. protected $columns = array();
  1235. /**
  1236. * Number of colums
  1237. * @access protected
  1238. * @since 4.9.001 (2010-03-28)
  1239. */
  1240. protected $num_columns = 1;
  1241. /**
  1242. * Current column number
  1243. * @access protected
  1244. * @since 4.9.001 (2010-03-28)
  1245. */
  1246. protected $current_column = 0;
  1247. /**
  1248. * Starting page for columns
  1249. * @access protected
  1250. * @since 4.9.001 (2010-03-28)
  1251. */
  1252. protected $column_start_page = 0;
  1253. /**
  1254. * Maximum page and column selected
  1255. * @access protected
  1256. * @since 5.8.000 (2010-08-11)
  1257. */
  1258. protected $maxselcol = array('page' => 0, 'column' => 0);
  1259. /**
  1260. * Array of: X difference between table cell x start and starting page margin, cellspacing, cellpadding
  1261. * @access protected
  1262. * @since 5.8.000 (2010-08-11)
  1263. */
  1264. protected $colxshift = array('x' => 0, 's' => 0, 'p' => 0);
  1265. /**
  1266. * Text rendering mode: 0 = Fill text; 1 = Stroke text; 2 = Fill, then stroke text; 3 = Neither fill nor stroke text (invisible); 4 = Fill text and add to path for clipping; 5 = Stroke text and add to path for clipping; 6 = Fill, then stroke text and add to path for clipping; 7 = Add text to path for clipping.
  1267. * @access protected
  1268. * @since 4.9.008 (2010-04-03)
  1269. */
  1270. protected $textrendermode = 0;
  1271. /**
  1272. * Text stroke width in doc units
  1273. * @access protected
  1274. * @since 4.9.008 (2010-04-03)
  1275. */
  1276. protected $textstrokewidth = 0;
  1277. /**
  1278. * @var current stroke color
  1279. * @access protected
  1280. * @since 4.9.008 (2010-04-03)
  1281. */
  1282. protected $strokecolor;
  1283. /**
  1284. * @var default unit of measure for document
  1285. * @access protected
  1286. * @since 5.0.000 (2010-04-22)
  1287. */
  1288. protected $pdfunit = 'mm';
  1289. /**
  1290. * @var true when we are on TOC (Table Of Content) page
  1291. * @access protected
  1292. */
  1293. protected $tocpage = false;
  1294. /**
  1295. * @var If true convert vector images (SVG, EPS) to raster image using GD or ImageMagick library.
  1296. * @access protected
  1297. * @since 5.0.000 (2010-04-26)
  1298. */
  1299. protected $rasterize_vector_images = false;
  1300. /**
  1301. * @var If true enables font subsetting by default
  1302. * @access protected
  1303. * @since 5.3.002 (2010-06-07)
  1304. */
  1305. protected $font_subsetting = true;
  1306. /**
  1307. * @var Array of default graphic settings
  1308. * @access protected
  1309. * @since 5.5.008 (2010-07-02)
  1310. */
  1311. protected $default_graphic_vars = array();
  1312. /**
  1313. * @var Array of XObjects
  1314. * @access protected
  1315. * @since 5.8.014 (2010-08-23)
  1316. */
  1317. protected $xobjects = array();
  1318. /**
  1319. * @var boolean true when we are inside an XObject
  1320. * @access protected
  1321. * @since 5.8.017 (2010-08-24)
  1322. */
  1323. protected $inxobj = false;
  1324. /**
  1325. * @var current XObject ID
  1326. * @access protected
  1327. * @since 5.8.017 (2010-08-24)
  1328. */
  1329. protected $xobjid = '';
  1330. /**
  1331. * @var percentage of character stretching
  1332. * @access protected
  1333. * @since 5.9.000 (2010-09-29)
  1334. */
  1335. protected $font_stretching = 100;
  1336. /**
  1337. * @var increases or decreases the space between characters in a text by the specified amount (tracking/kerning).
  1338. * @access protected
  1339. * @since 5.9.000 (2010-09-29)
  1340. */
  1341. protected $font_spacing = 0;
  1342. /**
  1343. * @var array of no-write regions
  1344. * ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right)
  1345. * @access protected
  1346. * @since 5.9.003 (2010-10-14)
  1347. */
  1348. protected $page_regions = array();
  1349. /**
  1350. * @var array containing HTML color names and values
  1351. * @access protected
  1352. * @since 5.9.004 (2010-10-18)
  1353. */
  1354. protected $webcolor = array();
  1355. /**
  1356. * @var directory used for the last SVG image
  1357. * @access protected
  1358. * @since 5.0.000 (2010-05-05)
  1359. */
  1360. protected $svgdir = '';
  1361. /**
  1362. * @var Deafult unit of measure for SVG
  1363. * @access protected
  1364. * @since 5.0.000 (2010-05-02)
  1365. */
  1366. protected $svgunit = 'px';
  1367. /**
  1368. * @var array of SVG gradients
  1369. * @access protected
  1370. * @since 5.0.000 (2010-05-02)
  1371. */
  1372. protected $svggradients = array();
  1373. /**
  1374. * @var ID of last SVG gradient
  1375. * @access protected
  1376. * @since 5.0.000 (2010-05-02)
  1377. */
  1378. protected $svggradientid = 0;
  1379. /**
  1380. * @var true when in SVG defs group
  1381. * @access protected
  1382. * @since 5.0.000 (2010-05-02)
  1383. */
  1384. protected $svgdefsmode = false;
  1385. /**
  1386. * @var array of SVG defs
  1387. * @access protected
  1388. * @since 5.0.000 (2010-05-02)
  1389. */
  1390. protected $svgdefs = array();
  1391. /**
  1392. * @var true when in SVG clipPath tag
  1393. * @access protected
  1394. * @since 5.0.000 (2010-04-26)
  1395. */
  1396. protected $svgclipmode = false;
  1397. /**
  1398. * @var array of SVG clipPath commands
  1399. * @access protected
  1400. * @since 5.0.000 (2010-05-02)
  1401. */
  1402. protected $svgclippaths = array();
  1403. /**
  1404. * @var array of SVG clipPath tranformation matrix
  1405. * @access protected
  1406. * @since 5.8.022 (2010-08-31)
  1407. */
  1408. protected $svgcliptm = array();
  1409. /**
  1410. * @var ID of last SVG clipPath
  1411. * @access protected
  1412. * @since 5.0.000 (2010-05-02)
  1413. */
  1414. protected $svgclipid = 0;
  1415. /**
  1416. * @var svg text
  1417. * @access protected
  1418. * @since 5.0.000 (2010-05-02)
  1419. */
  1420. protected $svgtext = '';
  1421. /**
  1422. * @var svg text properties
  1423. * @access protected
  1424. * @since 5.8.013 (2010-08-23)
  1425. */
  1426. protected $svgtextmode = array();
  1427. /**
  1428. * @var array of hinheritable SVG properties
  1429. * @access protected
  1430. * @since 5.0.000 (2010-05-02)
  1431. */
  1432. 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');
  1433. /**
  1434. * @var array of SVG properties
  1435. * @access protected
  1436. * @since 5.0.000 (2010-05-02)
  1437. */
  1438. protected $svgstyles = array(array(
  1439. 'alignment-baseline' => 'auto',
  1440. 'baseline-shift' => 'baseline',
  1441. 'clip' => 'auto',
  1442. 'clip-path' => 'none',
  1443. 'clip-rule' => 'nonzero',
  1444. 'color' => 'black',
  1445. 'color-interpolation' => 'sRGB',
  1446. 'color-interpolation-filters' => 'linearRGB',
  1447. 'color-profile' => 'auto',
  1448. 'color-rendering' => 'auto',
  1449. 'cursor' => 'auto',
  1450. 'direction' => 'ltr',
  1451. 'display' => 'inline',
  1452. 'dominant-baseline' => 'auto',
  1453. 'enable-background' => 'accumulate',
  1454. 'fill' => 'black',
  1455. 'fill-opacity' => 1,
  1456. 'fill-rule' => 'nonzero',
  1457. 'filter' => 'none',
  1458. 'flood-color' => 'black',
  1459. 'flood-opacity' => 1,
  1460. 'font' => '',
  1461. 'font-family' => 'helvetica',
  1462. 'font-size' => 'medium',
  1463. 'font-size-adjust' => 'none',
  1464. 'font-stretch' => 'normal',
  1465. 'font-style' => 'normal',
  1466. 'font-variant' => 'normal',
  1467. 'font-weight' => 'normal',
  1468. 'glyph-orientation-horizontal' => '0deg',
  1469. 'glyph-orientation-vertical' => 'auto',
  1470. 'image-rendering' => 'auto',
  1471. 'kerning' => 'auto',
  1472. 'letter-spacing' => 'normal',
  1473. 'lighting-color' => 'white',
  1474. 'marker' => '',
  1475. 'marker-end' => 'none',
  1476. 'marker-mid' => 'none',
  1477. 'marker-start' => 'none',
  1478. 'mask' => 'none',
  1479. 'opacity' => 1,
  1480. 'overflow' => 'auto',
  1481. 'pointer-events' => 'visiblePainted',
  1482. 'shape-rendering' => 'auto',
  1483. 'stop-color' => 'black',
  1484. 'stop-opacity' => 1,
  1485. 'stroke' => 'none',
  1486. 'stroke-dasharray' => 'none',
  1487. 'stroke-dashoffset' => 0,
  1488. 'stroke-linecap' => 'butt',
  1489. 'stroke-linejoin' => 'miter',
  1490. 'stroke-miterlimit' => 4,
  1491. 'stroke-opacity' => 1,
  1492. 'stroke-width' => 1,
  1493. 'text-anchor' => 'start',
  1494. 'text-decoration' => 'none',
  1495. 'text-rendering' => 'auto',
  1496. 'unicode-bidi' => 'normal',
  1497. 'visibility' => 'visible',
  1498. 'word-spacing' => 'normal',
  1499. 'writing-mode' => 'lr-tb',
  1500. 'text-color' => 'black',
  1501. 'transfmatrix' => array(1, 0, 0, 1, 0, 0)
  1502. ));
  1503. //------------------------------------------------------------
  1504. // METHODS
  1505. //------------------------------------------------------------
  1506. /**
  1507. * This is the class constructor.
  1508. * It allows to set up the page format, the orientation and the measure unit used in all the methods (except for the font sizes).
  1509. * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or Portrait (default)</li><li>L or Landscape</li><li>'' (empty string) for automatic orientation</li></ul>
  1510. * @param string $unit User measure unit. Possible values are:<ul><li>pt: point</li><li>mm: millimeter (default)</li><li>cm: centimeter</li><li>in: inch</li></ul><br />A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit.
  1511. * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
  1512. * @param boolean $unicode TRUE means that the input text is unicode (default = true)
  1513. * @param boolean $diskcache if TRUE reduce the RAM memory usage by caching temporary data on filesystem (slower).
  1514. * @param String $encoding charset encoding; default is UTF-8
  1515. * @access public
  1516. * @see getPageSizeFromFormat(), setPageFormat()
  1517. */
  1518. public function __construct($orientation='P', $unit='mm', $format='A4', $unicode=true, $encoding='UTF-8', $diskcache=false) {
  1519. /* Set internal character encoding to ASCII */
  1520. if (function_exists('mb_internal_encoding') AND mb_internal_encoding()) {
  1521. $this->internal_encoding = mb_internal_encoding();
  1522. mb_internal_encoding('ASCII');
  1523. }
  1524. require(dirname(__FILE__).'/htmlcolors.php');
  1525. $this->webcolor = $webcolor;
  1526. require_once(dirname(__FILE__).'/unicode_data.php');
  1527. $this->unicode = new TCPDF_UNICODE_DATA();
  1528. $this->font_obj_ids = array();
  1529. $this->page_obj_id = array();
  1530. $this->form_obj_id = array();
  1531. // set disk caching
  1532. $this->diskcache = $diskcache ? true : false;
  1533. // set language direction
  1534. $this->rtl = false;
  1535. $this->tmprtl = false;
  1536. // some checks
  1537. $this->_dochecks();
  1538. // initialization of properties
  1539. $this->isunicode = $unicode;
  1540. $this->page = 0;
  1541. $this->transfmrk[0] = array();
  1542. $this->pagedim = array();
  1543. $this->n = 2;
  1544. $this->buffer = '';
  1545. $this->pages = array();
  1546. $this->state = 0;
  1547. $this->fonts = array();
  1548. $this->FontFiles = array();
  1549. $this->diffs = array();
  1550. $this->images = array();
  1551. $this->links = array();
  1552. $this->gradients = array();
  1553. $this->InFooter = false;
  1554. $this->lasth = 0;
  1555. $this->FontFamily = 'helvetica';
  1556. $this->FontStyle = '';
  1557. $this->FontSizePt = 12;
  1558. $this->underline = false;
  1559. $this->overline = false;
  1560. $this->linethrough = false;
  1561. $this->DrawColor = '0 G';
  1562. $this->FillColor = '0 g';
  1563. $this->TextColor = '0 g';
  1564. $this->ColorFlag = false;
  1565. // encryption values
  1566. $this->encrypted = false;
  1567. $this->last_enc_key = '';
  1568. // standard Unicode fonts
  1569. $this->CoreFonts = array(
  1570. 'courier'=>'Courier',
  1571. 'courierB'=>'Courier-Bold',
  1572. 'courierI'=>'Courier-Oblique',
  1573. 'courierBI'=>'Courier-BoldOblique',
  1574. 'helvetica'=>'Helvetica',
  1575. 'helveticaB'=>'Helvetica-Bold',
  1576. 'helveticaI'=>'Helvetica-Oblique',
  1577. 'helveticaBI'=>'Helvetica-BoldOblique',
  1578. 'times'=>'Times-Roman',
  1579. 'timesB'=>'Times-Bold',
  1580. 'timesI'=>'Times-Italic',
  1581. 'timesBI'=>'Times-BoldItalic',
  1582. 'symbol'=>'Symbol',
  1583. 'zapfdingbats'=>'ZapfDingbats'
  1584. );
  1585. // set scale factor
  1586. $this->setPageUnit($unit);
  1587. // set page format and orientation
  1588. $this->setPageFormat($format, $orientation);
  1589. // page margins (1 cm)
  1590. $margin = 28.35 / $this->k;
  1591. $this->SetMargins($margin, $margin);
  1592. // internal cell padding
  1593. $cpadding = $margin / 10;
  1594. $this->setCellPaddings($cpadding, 0, $cpadding, 0);
  1595. // cell margins
  1596. $this->setCellMargins(0, 0, 0, 0);
  1597. // line width (0.2 mm)
  1598. $this->LineWidth = 0.57 / $this->k;
  1599. $this->linestyleWidth = sprintf('%.2F w', ($this->LineWidth * $this->k));
  1600. $this->linestyleCap = '0 J';
  1601. $this->linestyleJoin = '0 j';
  1602. $this->linestyleDash = '[] 0 d';
  1603. // automatic page break
  1604. $this->SetAutoPageBreak(true, (2 * $margin));
  1605. // full width display mode
  1606. $this->SetDisplayMode('fullwidth');
  1607. // compression
  1608. $this->SetCompression(true);
  1609. // set default PDF version number
  1610. $this->PDFVersion = '1.7';
  1611. $this->encoding = $encoding;
  1612. $this->HREF = array();
  1613. $this->getFontsList();
  1614. $this->fgcolor = array('R' => 0, 'G' => 0, 'B' => 0);
  1615. $this->strokecolor = array('R' => 0, 'G' => 0, 'B' => 0);
  1616. $this->bgcolor = array('R' => 255, 'G' => 255, 'B' => 255);
  1617. $this->extgstates = array();
  1618. // user's rights
  1619. $this->sign = false;
  1620. $this->ur['enabled'] = false;
  1621. $this->ur['document'] = '/FullSave';
  1622. $this->ur['annots'] = '/Create/Delete/Modify/Copy/Import/Export';
  1623. $this->ur['form'] = '/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate';
  1624. $this->ur['signature'] = '/Modify';
  1625. $this->ur['ef'] = '/Create/Delete/Modify/Import';
  1626. $this->ur['formex'] = '';
  1627. $this->signature_appearance = array('page' => 1, 'rect' => '0 0 0 0');
  1628. // set default JPEG quality
  1629. $this->jpeg_quality = 75;
  1630. // initialize some settings
  1631. $this->utf8Bidi(array(''), '');
  1632. // set default font
  1633. $this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
  1634. // check if PCRE Unicode support is enabled
  1635. if ($this->isunicode AND (@preg_match('/\pL/u', 'a') == 1)) {
  1636. // PCRE unicode support is turned ON
  1637. // \p{Z} or \p{Separator}: any kind of Unicode whitespace or invisible separator.
  1638. // \p{Lo} or \p{Other_Letter}: a Unicode letter or ideograph that does not have lowercase and uppercase variants.
  1639. // \p{Lo} is needed because Chinese characters are packed next to each other without spaces in between.
  1640. //$this->setSpacesRE('/[^\S\P{Z}\P{Lo}\xa0]/u');
  1641. $this->setSpacesRE('/[^\S\P{Z}\xa0]/u');
  1642. } else {
  1643. // PCRE unicode support is turned OFF
  1644. $this->setSpacesRE('/[^\S\xa0]/');
  1645. }
  1646. $this->default_form_prop = array('lineWidth'=>1, 'borderStyle'=>'solid', 'fillColor'=>array(255, 255, 255), 'strokeColor'=>array(128, 128, 128));
  1647. // set file ID for trailer
  1648. $this->file_id = md5($this->getRandomSeed('TCPDF'.$orientation.$unit.$format.$encoding));
  1649. // get default graphic vars
  1650. $this->default_graphic_vars = $this->getGraphicVars();
  1651. }
  1652. /**
  1653. * Default destructor.
  1654. * @access public
  1655. * @since 1.53.0.TC016
  1656. */
  1657. public function __destruct() {
  1658. // restore internal encoding
  1659. if (isset($this->internal_encoding) AND !empty($this->internal_encoding)) {
  1660. mb_internal_encoding($this->internal_encoding);
  1661. }
  1662. // unset all class variables
  1663. $this->_destroy(true);
  1664. }
  1665. /**
  1666. * Set the units of measure for the document.
  1667. * @param string $unit User measure unit. Possible values are:<ul><li>pt: point</li><li>mm: millimeter (default)</li><li>cm: centimeter</li><li>in: inch</li></ul><br />A point equals 1/72 of inch, that is to say about 0.35 mm (an inch being 2.54 cm). This is a very common unit in typography; font sizes are expressed in that unit.
  1668. * @access public
  1669. * @since 3.0.015 (2008-06-06)
  1670. */
  1671. public function setPageUnit($unit) {
  1672. $unit = strtolower($unit);
  1673. //Set scale factor
  1674. switch ($unit) {
  1675. // points
  1676. case 'px':
  1677. case 'pt': {
  1678. $this->k = 1;
  1679. break;
  1680. }
  1681. // millimeters
  1682. case 'mm': {
  1683. $this->k = $this->dpi / 25.4;
  1684. break;
  1685. }
  1686. // centimeters
  1687. case 'cm': {
  1688. $this->k = $this->dpi / 2.54;
  1689. break;
  1690. }
  1691. // inches
  1692. case 'in': {
  1693. $this->k = $this->dpi;
  1694. break;
  1695. }
  1696. // unsupported unit
  1697. default : {
  1698. $this->Error('Incorrect unit: '.$unit);
  1699. break;
  1700. }
  1701. }
  1702. $this->pdfunit = $unit;
  1703. if (isset($this->CurOrientation)) {
  1704. $this->setPageOrientation($this->CurOrientation);
  1705. }
  1706. }
  1707. /**
  1708. * Get page dimensions from format name.
  1709. * @param mixed $format The format name. It can be: <ul>
  1710. * <li><b>ISO 216 A Series + 2 SIS 014711 extensions</b></li>
  1711. * <li>A0 (841x1189 mm ; 33.11x46.81 in)</li>
  1712. * <li>A1 (594x841 mm ; 23.39x33.11 in)</li>
  1713. * <li>A2 (420x594 mm ; 16.54x23.39 in)</li>
  1714. * <li>A3 (297x420 mm ; 11.69x16.54 in)</li>
  1715. * <li>A4 (210x297 mm ; 8.27x11.69 in)</li>
  1716. * <li>A5 (148x210 mm ; 5.83x8.27 in)</li>
  1717. * <li>A6 (105x148 mm ; 4.13x5.83 in)</li>
  1718. * <li>A7 (74x105 mm ; 2.91x4.13 in)</li>
  1719. * <li>A8 (52x74 mm ; 2.05x2.91 in)</li>
  1720. * <li>A9 (37x52 mm ; 1.46x2.05 in)</li>
  1721. * <li>A10 (26x37 mm ; 1.02x1.46 in)</li>
  1722. * <li>A11 (18x26 mm ; 0.71x1.02 in)</li>
  1723. * <li>A12 (13x18 mm ; 0.51x0.71 in)</li>
  1724. * <li><b>ISO 216 B Series + 2 SIS 014711 extensions</b></li>
  1725. * <li>B0 (1000x1414 mm ; 39.37x55.67 in)</li>
  1726. * <li>B1 (707x1000 mm ; 27.83x39.37 in)</li>
  1727. * <li>B2 (500x707 mm ; 19.69x27.83 in)</li>
  1728. * <li>B3 (353x500 mm ; 13.90x19.69 in)</li>
  1729. * <li>B4 (250x353 mm ; 9.84x13.90 in)</li>
  1730. * <li>B5 (176x250 mm ; 6.93x9.84 in)</li>
  1731. * <li>B6 (125x176 mm ; 4.92x6.93 in)</li>
  1732. * <li>B7 (88x125 mm ; 3.46x4.92 in)</li>
  1733. * <li>B8 (62x88 mm ; 2.44x3.46 in)</li>
  1734. * <li>B9 (44x62 mm ; 1.73x2.44 in)</li>
  1735. * <li>B10 (31x44 mm ; 1.22x1.73 in)</li>
  1736. * <li>B11 (22x31 mm ; 0.87x1.22 in)</li>
  1737. * <li>B12 (15x22 mm ; 0.59x0.87 in)</li>
  1738. * <li><b>ISO 216 C Series + 2 SIS 014711 extensions + 2 EXTENSION</b></li>
  1739. * <li>C0 (917x1297 mm ; 36.10x51.06 in)</li>
  1740. * <li>C1 (648x917 mm ; 25.51x36.10 in)</li>
  1741. * <li>C2 (458x648 mm ; 18.03x25.51 in)</li>
  1742. * <li>C3 (324x458 mm ; 12.76x18.03 in)</li>
  1743. * <li>C4 (229x324 mm ; 9.02x12.76 in)</li>
  1744. * <li>C5 (162x229 mm ; 6.38x9.02 in)</li>
  1745. * <li>C6 (114x162 mm ; 4.49x6.38 in)</li>
  1746. * <li>C7 (81x114 mm ; 3.19x4.49 in)</li>
  1747. * <li>C8 (57x81 mm ; 2.24x3.19 in)</li>
  1748. * <li>C9 (40x57 mm ; 1.57x2.24 in)</li>
  1749. * <li>C10 (28x40 mm ; 1.10x1.57 in)</li>
  1750. * <li>C11 (20x28 mm ; 0.79x1.10 in)</li>
  1751. * <li>C12 (14x20 mm ; 0.55x0.79 in)</li>
  1752. * <li>C76 (81x162 mm ; 3.19x6.38 in)</li>
  1753. * <li>DL (110x220 mm ; 4.33x8.66 in)</li>
  1754. * <li><b>SIS 014711 E Series</b></li>
  1755. * <li>E0 (879x1241 mm ; 34.61x48.86 in)</li>
  1756. * <li>E1 (620x879 mm ; 24.41x34.61 in)</li>
  1757. * <li>E2 (440x620 mm ; 17.32x24.41 in)</li>
  1758. * <li>E3 (310x440 mm ; 12.20x17.32 in)</li>
  1759. * <li>E4 (220x310 mm ; 8.66x12.20 in)</li>
  1760. * <li>E5 (155x220 mm ; 6.10x8.66 in)</li>
  1761. * <li>E6 (110x155 mm ; 4.33x6.10 in)</li>
  1762. * <li>E7 (78x110 mm ; 3.07x4.33 in)</li>
  1763. * <li>E8 (55x78 mm ; 2.17x3.07 in)</li>
  1764. * <li>E9 (39x55 mm ; 1.54x2.17 in)</li>
  1765. * <li>E10 (27x39 mm ; 1.06x1.54 in)</li>
  1766. * <li>E11 (19x27 mm ; 0.75x1.06 in)</li>
  1767. * <li>E12 (13x19 mm ; 0.51x0.75 in)</li>
  1768. * <li><b>SIS 014711 G Series</b></li>
  1769. * <li>G0 (958x1354 mm ; 37.72x53.31 in)</li>
  1770. * <li>G1 (677x958 mm ; 26.65x37.72 in)</li>
  1771. * <li>G2 (479x677 mm ; 18.86x26.65 in)</li>
  1772. * <li>G3 (338x479 mm ; 13.31x18.86 in)</li>
  1773. * <li>G4 (239x338 mm ; 9.41x13.31 in)</li>
  1774. * <li>G5 (169x239 mm ; 6.65x9.41 in)</li>
  1775. * <li>G6 (119x169 mm ; 4.69x6.65 in)</li>
  1776. * <li>G7 (84x119 mm ; 3.31x4.69 in)</li>
  1777. * <li>G8 (59x84 mm ; 2.32x3.31 in)</li>
  1778. * <li>G9 (42x59 mm ; 1.65x2.32 in)</li>
  1779. * <li>G10 (29x42 mm ; 1.14x1.65 in)</li>
  1780. * <li>G11 (21x29 mm ; 0.83x1.14 in)</li>
  1781. * <li>G12 (14x21 mm ; 0.55x0.83 in)</li>
  1782. * <li><b>ISO Press</b></li>
  1783. * <li>RA0 (860x1220 mm ; 33.86x48.03 in)</li>
  1784. * <li>RA1 (610x860 mm ; 24.02x33.86 in)</li>
  1785. * <li>RA2 (430x610 mm ; 16.93x24.02 in)</li>
  1786. * <li>RA3 (305x430 mm ; 12.01x16.93 in)</li>
  1787. * <li>RA4 (215x305 mm ; 8.46x12.01 in)</li>
  1788. * <li>SRA0 (900x1280 mm ; 35.43x50.39 in)</li>
  1789. * <li>SRA1 (640x900 mm ; 25.20x35.43 in)</li>
  1790. * <li>SRA2 (450x640 mm ; 17.72x25.20 in)</li>
  1791. * <li>SRA3 (320x450 mm ; 12.60x17.72 in)</li>
  1792. * <li>SRA4 (225x320 mm ; 8.86x12.60 in)</li>
  1793. * <li><b>German DIN 476</b></li>
  1794. * <li>4A0 (1682x2378 mm ; 66.22x93.62 in)</li>
  1795. * <li>2A0 (1189x1682 mm ; 46.81x66.22 in)</li>
  1796. * <li><b>Variations on the ISO Standard</b></li>
  1797. * <li>A2_EXTRA (445x619 mm ; 17.52x24.37 in)</li>
  1798. * <li>A3+ (329x483 mm ; 12.95x19.02 in)</li>
  1799. * <li>A3_EXTRA (322x445 mm ; 12.68x17.52 in)</li>
  1800. * <li>A3_SUPER (305x508 mm ; 12.01x20.00 in)</li>
  1801. * <li>SUPER_A3 (305x487 mm ; 12.01x19.17 in)</li>
  1802. * <li>A4_EXTRA (235x322 mm ; 9.25x12.68 in)</li>
  1803. * <li>A4_SUPER (229x322 mm ; 9.02x12.68 in)</li>
  1804. * <li>SUPER_A4 (227x356 mm ; 8.94x14.02 in)</li>
  1805. * <li>A4_LONG (210x348 mm ; 8.27x13.70 in)</li>
  1806. * <li>F4 (210x330 mm ; 8.27x12.99 in)</li>
  1807. * <li>SO_B5_EXTRA (202x276 mm ; 7.95x10.87 in)</li>
  1808. * <li>A5_EXTRA (173x235 mm ; 6.81x9.25 in)</li>
  1809. * <li><b>ANSI Series</b></li>
  1810. * <li>ANSI_E (864x1118 mm ; 34.00x44.00 in)</li>
  1811. * <li>ANSI_D (559x864 mm ; 22.00x34.00 in)</li>
  1812. * <li>ANSI_C (432x559 mm ; 17.00x22.00 in)</li>
  1813. * <li>ANSI_B (279x432 mm ; 11.00x17.00 in)</li>
  1814. * <li>ANSI_A (216x279 mm ; 8.50x11.00 in)</li>
  1815. * <li><b>Traditional 'Loose' North American Paper Sizes</b></li>
  1816. * <li>LEDGER, USLEDGER (432x279 mm ; 17.00x11.00 in)</li>
  1817. * <li>TABLOID, USTABLOID, BIBLE, ORGANIZERK (279x432 mm ; 11.00x17.00 in)</li>
  1818. * <li>LETTER, USLETTER, ORGANIZERM (216x279 mm ; 8.50x11.00 in)</li>
  1819. * <li>LEGAL, USLEGAL (216x356 mm ; 8.50x14.00 in)</li>
  1820. * <li>GLETTER, GOVERNMENTLETTER (203x267 mm ; 8.00x10.50 in)</li>
  1821. * <li>JLEGAL, JUNIORLEGAL (203x127 mm ; 8.00x5.00 in)</li>
  1822. * <li><b>Other North American Paper Sizes</b></li>
  1823. * <li>QUADDEMY (889x1143 mm ; 35.00x45.00 in)</li>
  1824. * <li>SUPER_B (330x483 mm ; 13.00x19.00 in)</li>
  1825. * <li>QUARTO (229x279 mm ; 9.00x11.00 in)</li>
  1826. * <li>FOLIO, GOVERNMENTLEGAL (216x330 mm ; 8.50x13.00 in)</li>
  1827. * <li>EXECUTIVE, MONARCH (184x267 mm ; 7.25x10.50 in)</li>
  1828. * <li>MEMO, STATEMENT, ORGANIZERL (140x216 mm ; 5.50x8.50 in)</li>
  1829. * <li>FOOLSCAP (210x330 mm ; 8.27x13.00 in)</li>
  1830. * <li>COMPACT (108x171 mm ; 4.25x6.75 in)</li>
  1831. * <li>ORGANIZERJ (70x127 mm ; 2.75x5.00 in)</li>
  1832. * <li><b>Canadian standard CAN 2-9.60M</b></li>
  1833. * <li>P1 (560x860 mm ; 22.05x33.86 in)</li>
  1834. * <li>P2 (430x560 mm ; 16.93x22.05 in)</li>
  1835. * <li>P3 (280x430 mm ; 11.02x16.93 in)</li>
  1836. * <li>P4 (215x280 mm ; 8.46x11.02 in)</li>
  1837. * <li>P5 (140x215 mm ; 5.51x8.46 in)</li>
  1838. * <li>P6 (107x140 mm ; 4.21x5.51 in)</li>
  1839. * <li><b>North American Architectural Sizes</b></li>
  1840. * <li>ARCH_E (914x1219 mm ; 36.00x48.00 in)</li>
  1841. * <li>ARCH_E1 (762x1067 mm ; 30.00x42.00 in)</li>
  1842. * <li>ARCH_D (610x914 mm ; 24.00x36.00 in)</li>
  1843. * <li>ARCH_C, BROADSHEET (457x610 mm ; 18.00x24.00 in)</li>
  1844. * <li>ARCH_B (305x457 mm ; 12.00x18.00 in)</li>
  1845. * <li>ARCH_A (229x305 mm ; 9.00x12.00 in)</li>
  1846. * <li><b>Announcement Envelopes</b></li>
  1847. * <li>ANNENV_A2 (111x146 mm ; 4.37x5.75 in)</li>
  1848. * <li>ANNENV_A6 (121x165 mm ; 4.75x6.50 in)</li>
  1849. * <li>ANNENV_A7 (133x184 mm ; 5.25x7.25 in)</li>
  1850. * <li>ANNENV_A8 (140x206 mm ; 5.50x8.12 in)</li>
  1851. * <li>ANNENV_A10 (159x244 mm ; 6.25x9.62 in)</li>
  1852. * <li>ANNENV_SLIM (98x225 mm ; 3.87x8.87 in)</li>
  1853. * <li><b>Commercial Envelopes</b></li>
  1854. * <li>COMMENV_N6_1/4 (89x152 mm ; 3.50x6.00 in)</li>
  1855. * <li>COMMENV_N6_3/4 (92x165 mm ; 3.62x6.50 in)</li>
  1856. * <li>COMMENV_N8 (98x191 mm ; 3.87x7.50 in)</li>
  1857. * <li>COMMENV_N9 (98x225 mm ; 3.87x8.87 in)</li>
  1858. * <li>COMMENV_N10 (105x241 mm ; 4.12x9.50 in)</li>
  1859. * <li>COMMENV_N11 (114x263 mm ; 4.50x10.37 in)</li>
  1860. * <li>COMMENV_N12 (121x279 mm ; 4.75x11.00 in)</li>
  1861. * <li>COMMENV_N14 (127x292 mm ; 5.00x11.50 in)</li>
  1862. * <li><b>Catalogue Envelopes</b></li>
  1863. * <li>CATENV_N1 (152x229 mm ; 6.00x9.00 in)</li>
  1864. * <li>CATENV_N1_3/4 (165x241 mm ; 6.50x9.50 in)</li>
  1865. * <li>CATENV_N2 (165x254 mm ; 6.50x10.00 in)</li>
  1866. * <li>CATENV_N3 (178x254 mm ; 7.00x10.00 in)</li>
  1867. * <li>CATENV_N6 (191x267 mm ; 7.50x10.50 in)</li>
  1868. * <li>CATENV_N7 (203x279 mm ; 8.00x11.00 in)</li>
  1869. * <li>CATENV_N8 (210x286 mm ; 8.25x11.25 in)</li>
  1870. * <li>CATENV_N9_1/2 (216x267 mm ; 8.50x10.50 in)</li>
  1871. * <li>CATENV_N9_3/4 (222x286 mm ; 8.75x11.25 in)</li>
  1872. * <li>CATENV_N10_1/2 (229x305 mm ; 9.00x12.00 in)</li>
  1873. * <li>CATENV_N12_1/2 (241x318 mm ; 9.50x12.50 in)</li>
  1874. * <li>CATENV_N13_1/2 (254x330 mm ; 10.00x13.00 in)</li>
  1875. * <li>CATENV_N14_1/4 (286x311 mm ; 11.25x12.25 in)</li>
  1876. * <li>CATENV_N14_1/2 (292x368 mm ; 11.50x14.50 in)</li>
  1877. * <li><b>Japanese (JIS P 0138-61) Standard B-Series</b></li>
  1878. * <li>JIS_B0 (1030x1456 mm ; 40.55x57.32 in)</li>
  1879. * <li>JIS_B1 (728x1030 mm ; 28.66x40.55 in)</li>
  1880. * <li>JIS_B2 (515x728 mm ; 20.28x28.66 in)</li>
  1881. * <li>JIS_B3 (364x515 mm ; 14.33x20.28 in)</li>
  1882. * <li>JIS_B4 (257x364 mm ; 10.12x14.33 in)</li>
  1883. * <li>JIS_B5 (182x257 mm ; 7.17x10.12 in)</li>
  1884. * <li>JIS_B6 (128x182 mm ; 5.04x7.17 in)</li>
  1885. * <li>JIS_B7 (91x128 mm ; 3.58x5.04 in)</li>
  1886. * <li>JIS_B8 (64x91 mm ; 2.52x3.58 in)</li>
  1887. * <li>JIS_B9 (45x64 mm ; 1.77x2.52 in)</li>
  1888. * <li>JIS_B10 (32x45 mm ; 1.26x1.77 in)</li>
  1889. * <li>JIS_B11 (22x32 mm ; 0.87x1.26 in)</li>
  1890. * <li>JIS_B12 (16x22 mm ; 0.63x0.87 in)</li>
  1891. * <li><b>PA Series</b></li>
  1892. * <li>PA0 (840x1120 mm ; 33.07x44.09 in)</li>
  1893. * <li>PA1 (560x840 mm ; 22.05x33.07 in)</li>
  1894. * <li>PA2 (420x560 mm ; 16.54x22.05 in)</li>
  1895. * <li>PA3 (280x420 mm ; 11.02x16.54 in)</li>
  1896. * <li>PA4 (210x280 mm ; 8.27x11.02 in)</li>
  1897. * <li>PA5 (140x210 mm ; 5.51x8.27 in)</li>
  1898. * <li>PA6 (105x140 mm ; 4.13x5.51 in)</li>
  1899. * <li>PA7 (70x105 mm ; 2.76x4.13 in)</li>
  1900. * <li>PA8 (52x70 mm ; 2.05x2.76 in)</li>
  1901. * <li>PA9 (35x52 mm ; 1.38x2.05 in)</li>
  1902. * <li>PA10 (26x35 mm ; 1.02x1.38 in)</li>
  1903. * <li><b>Standard Photographic Print Sizes</b></li>
  1904. * <li>PASSPORT_PHOTO (35x45 mm ; 1.38x1.77 in)</li>
  1905. * <li>E (82x120 mm ; 3.25x4.72 in)</li>
  1906. * <li>3R, L (89x127 mm ; 3.50x5.00 in)</li>
  1907. * <li>4R, KG (102x152 mm ; 4.02x5.98 in)</li>
  1908. * <li>4D (120x152 mm ; 4.72x5.98 in)</li>
  1909. * <li>5R, 2L (127x178 mm ; 5.00x7.01 in)</li>
  1910. * <li>6R, 8P (152x203 mm ; 5.98x7.99 in)</li>
  1911. * <li>8R, 6P (203x254 mm ; 7.99x10.00 in)</li>
  1912. * <li>S8R, 6PW (203x305 mm ; 7.99x12.01 in)</li>
  1913. * <li>10R, 4P (254x305 mm ; 10.00x12.01 in)</li>
  1914. * <li>S10R, 4PW (254x381 mm ; 10.00x15.00 in)</li>
  1915. * <li>11R (279x356 mm ; 10.98x14.02 in)</li>
  1916. * <li>S11R (279x432 mm ; 10.98x17.01 in)</li>
  1917. * <li>12R (305x381 mm ; 12.01x15.00 in)</li>
  1918. * <li>S12R (305x456 mm ; 12.01x17.95 in)</li>
  1919. * <li><b>Common Newspaper Sizes</b></li>
  1920. * <li>NEWSPAPER_BROADSHEET (750x600 mm ; 29.53x23.62 in)</li>
  1921. * <li>NEWSPAPER_BERLINER (470x315 mm ; 18.50x12.40 in)</li>
  1922. * <li>NEWSPAPER_COMPACT, NEWSPAPER_TABLOID (430x280 mm ; 16.93x11.02 in)</li>
  1923. * <li><b>Business Cards</b></li>
  1924. * <li>CREDIT_CARD, BUSINESS_CARD, BUSINESS_CARD_ISO7810 (54x86 mm ; 2.13x3.37 in)</li>
  1925. * <li>BUSINESS_CARD_ISO216 (52x74 mm ; 2.05x2.91 in)</li>
  1926. * <li>BUSINESS_CARD_IT, BUSINESS_CARD_UK, BUSINESS_CARD_FR, BUSINESS_CARD_DE, BUSINESS_CARD_ES (55x85 mm ; 2.17x3.35 in)</li>
  1927. * <li>BUSINESS_CARD_US, BUSINESS_CARD_CA (51x89 mm ; 2.01x3.50 in)</li>
  1928. * <li>BUSINESS_CARD_JP (55x91 mm ; 2.17x3.58 in)</li>
  1929. * <li>BUSINESS_CARD_HK (54x90 mm ; 2.13x3.54 in)</li>
  1930. * <li>BUSINESS_CARD_AU, BUSINESS_CARD_DK, BUSINESS_CARD_SE (55x90 mm ; 2.17x3.54 in)</li>
  1931. * <li>BUSINESS_CARD_RU, BUSINESS_CARD_CZ, BUSINESS_CARD_FI, BUSINESS_CARD_HU, BUSINESS_CARD_IL (50x90 mm ; 1.97x3.54 in)</li>
  1932. * <li><b>Billboards</b></li>
  1933. * <li>4SHEET (1016x1524 mm ; 40.00x60.00 in)</li>
  1934. * <li>6SHEET (1200x1800 mm ; 47.24x70.87 in)</li>
  1935. * <li>12SHEET (3048x1524 mm ; 120.00x60.00 in)</li>
  1936. * <li>16SHEET (2032x3048 mm ; 80.00x120.00 in)</li>
  1937. * <li>32SHEET (4064x3048 mm ; 160.00x120.00 in)</li>
  1938. * <li>48SHEET (6096x3048 mm ; 240.00x120.00 in)</li>
  1939. * <li>64SHEET (8128x3048 mm ; 320.00x120.00 in)</li>
  1940. * <li>96SHEET (12192x3048 mm ; 480.00x120.00 in)</li>
  1941. * <li><b>Old Imperial English (some are still used in USA)</b></li>
  1942. * <li>EN_EMPEROR (1219x1829 mm ; 48.00x72.00 in)</li>
  1943. * <li>EN_ANTIQUARIAN (787x1346 mm ; 31.00x53.00 in)</li>
  1944. * <li>EN_GRAND_EAGLE (730x1067 mm ; 28.75x42.00 in)</li>
  1945. * <li>EN_DOUBLE_ELEPHANT (679x1016 mm ; 26.75x40.00 in)</li>
  1946. * <li>EN_ATLAS (660x864 mm ; 26.00x34.00 in)</li>
  1947. * <li>EN_COLOMBIER (597x876 mm ; 23.50x34.50 in)</li>
  1948. * <li>EN_ELEPHANT (584x711 mm ; 23.00x28.00 in)</li>
  1949. * <li>EN_DOUBLE_DEMY (572x902 mm ; 22.50x35.50 in)</li>
  1950. * <li>EN_IMPERIAL (559x762 mm ; 22.00x30.00 in)</li>
  1951. * <li>EN_PRINCESS (546x711 mm ; 21.50x28.00 in)</li>
  1952. * <li>EN_CARTRIDGE (533x660 mm ; 21.00x26.00 in)</li>
  1953. * <li>EN_DOUBLE_LARGE_POST (533x838 mm ; 21.00x33.00 in)</li>
  1954. * <li>EN_ROYAL (508x635 mm ; 20.00x25.00 in)</li>
  1955. * <li>EN_SHEET, EN_HALF_POST (495x597 mm ; 19.50x23.50 in)</li>
  1956. * <li>EN_SUPER_ROYAL (483x686 mm ; 19.00x27.00 in)</li>
  1957. * <li>EN_DOUBLE_POST (483x775 mm ; 19.00x30.50 in)</li>
  1958. * <li>EN_MEDIUM (445x584 mm ; 17.50x23.00 in)</li>
  1959. * <li>EN_DEMY (445x572 mm ; 17.50x22.50 in)</li>
  1960. * <li>EN_LARGE_POST (419x533 mm ; 16.50x21.00 in)</li>
  1961. * <li>EN_COPY_DRAUGHT (406x508 mm ; 16.00x20.00 in)</li>
  1962. * <li>EN_POST (394x489 mm ; 15.50x19.25 in)</li>
  1963. * <li>EN_CROWN (381x508 mm ; 15.00x20.00 in)</li>
  1964. * <li>EN_PINCHED_POST (375x470 mm ; 14.75x18.50 in)</li>
  1965. * <li>EN_BRIEF (343x406 mm ; 13.50x16.00 in)</li>
  1966. * <li>EN_FOOLSCAP (343x432 mm ; 13.50x17.00 in)</li>
  1967. * <li>EN_SMALL_FOOLSCAP (337x419 mm ; 13.25x16.50 in)</li>
  1968. * <li>EN_POTT (318x381 mm ; 12.50x15.00 in)</li>
  1969. * <li><b>Old Imperial Belgian</b></li>
  1970. * <li>BE_GRAND_AIGLE (700x1040 mm ; 27.56x40.94 in)</li>
  1971. * <li>BE_COLOMBIER (620x850 mm ; 24.41x33.46 in)</li>
  1972. * <li>BE_DOUBLE_CARRE (620x920 mm ; 24.41x36.22 in)</li>
  1973. * <li>BE_ELEPHANT (616x770 mm ; 24.25x30.31 in)</li>
  1974. * <li>BE_PETIT_AIGLE (600x840 mm ; 23.62x33.07 in)</li>
  1975. * <li>BE_GRAND_JESUS (550x730 mm ; 21.65x28.74 in)</li>
  1976. * <li>BE_JESUS (540x730 mm ; 21.26x28.74 in)</li>
  1977. * <li>BE_RAISIN (500x650 mm ; 19.69x25.59 in)</li>
  1978. * <li>BE_GRAND_MEDIAN (460x605 mm ; 18.11x23.82 in)</li>
  1979. * <li>BE_DOUBLE_POSTE (435x565 mm ; 17.13x22.24 in)</li>
  1980. * <li>BE_COQUILLE (430x560 mm ; 16.93x22.05 in)</li>
  1981. * <li>BE_PETIT_MEDIAN (415x530 mm ; 16.34x20.87 in)</li>
  1982. * <li>BE_RUCHE (360x460 mm ; 14.17x18.11 in)</li>
  1983. * <li>BE_PROPATRIA (345x430 mm ; 13.58x16.93 in)</li>
  1984. * <li>BE_LYS (317x397 mm ; 12.48x15.63 in)</li>
  1985. * <li>BE_POT (307x384 mm ; 12.09x15.12 in)</li>
  1986. * <li>BE_ROSETTE (270x347 mm ; 10.63x13.66 in)</li>
  1987. * <li><b>Old Imperial French</b></li>
  1988. * <li>FR_UNIVERS (1000x1300 mm ; 39.37x51.18 in)</li>
  1989. * <li>FR_DOUBLE_COLOMBIER (900x1260 mm ; 35.43x49.61 in)</li>
  1990. * <li>FR_GRANDE_MONDE (900x1260 mm ; 35.43x49.61 in)</li>
  1991. * <li>FR_DOUBLE_SOLEIL (800x1200 mm ; 31.50x47.24 in)</li>
  1992. * <li>FR_DOUBLE_JESUS (760x1120 mm ; 29.92x44.09 in)</li>
  1993. * <li>FR_GRAND_AIGLE (750x1060 mm ; 29.53x41.73 in)</li>
  1994. * <li>FR_PETIT_AIGLE (700x940 mm ; 27.56x37.01 in)</li>
  1995. * <li>FR_DOUBLE_RAISIN (650x1000 mm ; 25.59x39.37 in)</li>
  1996. * <li>FR_JOURNAL (650x940 mm ; 25.59x37.01 in)</li>
  1997. * <li>FR_COLOMBIER_AFFICHE (630x900 mm ; 24.80x35.43 in)</li>
  1998. * <li>FR_DOUBLE_CAVALIER (620x920 mm ; 24.41x36.22 in)</li>
  1999. * <li>FR_CLOCHE (600x800 mm ; 23.62x31.50 in)</li>
  2000. * <li>FR_SOLEIL (600x800 mm ; 23.62x31.50 in)</li>
  2001. * <li>FR_DOUBLE_CARRE (560x900 mm ; 22.05x35.43 in)</li>
  2002. * <li>FR_DOUBLE_COQUILLE (560x880 mm ; 22.05x34.65 in)</li>
  2003. * <li>FR_JESUS (560x760 mm ; 22.05x29.92 in)</li>
  2004. * <li>FR_RAISIN (500x650 mm ; 19.69x25.59 in)</li>
  2005. * <li>FR_CAVALIER (460x620 mm ; 18.11x24.41 in)</li>
  2006. * <li>FR_DOUBLE_COURONNE (460x720 mm ; 18.11x28.35 in)</li>
  2007. * <li>FR_CARRE (450x560 mm ; 17.72x22.05 in)</li>
  2008. * <li>FR_COQUILLE (440x560 mm ; 17.32x22.05 in)</li>
  2009. * <li>FR_DOUBLE_TELLIERE (440x680 mm ; 17.32x26.77 in)</li>
  2010. * <li>FR_DOUBLE_CLOCHE (400x600 mm ; 15.75x23.62 in)</li>
  2011. * <li>FR_DOUBLE_POT (400x620 mm ; 15.75x24.41 in)</li>
  2012. * <li>FR_ECU (400x520 mm ; 15.75x20.47 in)</li>
  2013. * <li>FR_COURONNE (360x460 mm ; 14.17x18.11 in)</li>
  2014. * <li>FR_TELLIERE (340x440 mm ; 13.39x17.32 in)</li>
  2015. * <li>FR_POT (310x400 mm ; 12.20x15.75 in)</li>
  2016. * </ul>
  2017. * @return array containing page width and height in points
  2018. * @access public
  2019. * @since 5.0.010 (2010-05-17)
  2020. */
  2021. public function getPageSizeFromFormat($format) {
  2022. // Paper cordinates are calculated in this way: (inches * 72) where (1 inch = 25.4 mm)
  2023. switch (strtoupper($format)) {
  2024. // ISO 216 A Series + 2 SIS 014711 extensions
  2025. case 'A0' : {$pf = array( 2383.937, 3370.394); break;}
  2026. case 'A1' : {$pf = array( 1683.780, 2383.937); break;}
  2027. case 'A2' : {$pf = array( 1190.551, 1683.780); break;}
  2028. case 'A3' : {$pf = array( 841.890, 1190.551); break;}
  2029. case 'A4' : {$pf = array( 595.276, 841.890); break;}
  2030. case 'A5' : {$pf = array( 419.528, 595.276); break;}
  2031. case 'A6' : {$pf = array( 297.638, 419.528); break;}
  2032. case 'A7' : {$pf = array( 209.764, 297.638); break;}
  2033. case 'A8' : {$pf = array( 147.402, 209.764); break;}
  2034. case 'A9' : {$pf = array( 104.882, 147.402); break;}
  2035. case 'A10': {$pf = array( 73.701, 104.882); break;}
  2036. case 'A11': {$pf = array( 51.024, 73.701); break;}
  2037. case 'A12': {$pf = array( 36.850, 51.024); break;}
  2038. // ISO 216 B Series + 2 SIS 014711 extensions
  2039. case 'B0' : {$pf = array( 2834.646, 4008.189); break;}
  2040. case 'B1' : {$pf = array( 2004.094, 2834.646); break;}
  2041. case 'B2' : {$pf = array( 1417.323, 2004.094); break;}
  2042. case 'B3' : {$pf = array( 1000.630, 1417.323); break;}
  2043. case 'B4' : {$pf = array( 708.661, 1000.630); break;}
  2044. case 'B5' : {$pf = array( 498.898, 708.661); break;}
  2045. case 'B6' : {$pf = array( 354.331, 498.898); break;}
  2046. case 'B7' : {$pf = array( 249.449, 354.331); break;}
  2047. case 'B8' : {$pf = array( 175.748, 249.449); break;}
  2048. case 'B9' : {$pf = array( 124.724, 175.748); break;}
  2049. case 'B10': {$pf = array( 87.874, 124.724); break;}
  2050. case 'B11': {$pf = array( 62.362, 87.874); break;}
  2051. case 'B12': {$pf = array( 42.520, 62.362); break;}
  2052. // ISO 216 C Series + 2 SIS 014711 extensions + 2 EXTENSION
  2053. case 'C0' : {$pf = array( 2599.370, 3676.535); break;}
  2054. case 'C1' : {$pf = array( 1836.850, 2599.370); break;}
  2055. case 'C2' : {$pf = array( 1298.268, 1836.850); break;}
  2056. case 'C3' : {$pf = array( 918.425, 1298.268); break;}
  2057. case 'C4' : {$pf = array( 649.134, 918.425); break;}
  2058. case 'C5' : {$pf = array( 459.213, 649.134); break;}
  2059. case 'C6' : {$pf = array( 323.150, 459.213); break;}
  2060. case 'C7' : {$pf = array( 229.606, 323.150); break;}
  2061. case 'C8' : {$pf = array( 161.575, 229.606); break;}
  2062. case 'C9' : {$pf = array( 113.386, 161.575); break;}
  2063. case 'C10': {$pf = array( 79.370, 113.386); break;}
  2064. case 'C11': {$pf = array( 56.693, 79.370); break;}
  2065. case 'C12': {$pf = array( 39.685, 56.693); break;}
  2066. case 'C76': {$pf = array( 229.606, 459.213); break;}
  2067. case 'DL' : {$pf = array( 311.811, 623.622); break;}
  2068. // SIS 014711 E Series
  2069. case 'E0' : {$pf = array( 2491.654, 3517.795); break;}
  2070. case 'E1' : {$pf = array( 1757.480, 2491.654); break;}
  2071. case 'E2' : {$pf = array( 1247.244, 1757.480); break;}
  2072. case 'E3' : {$pf = array( 878.740, 1247.244); break;}
  2073. case 'E4' : {$pf = array( 623.622, 878.740); break;}
  2074. case 'E5' : {$pf = array( 439.370, 623.622); break;}
  2075. case 'E6' : {$pf = array( 311.811, 439.370); break;}
  2076. case 'E7' : {$pf = array( 221.102, 311.811); break;}
  2077. case 'E8' : {$pf = array( 155.906, 221.102); break;}
  2078. case 'E9' : {$pf = array( 110.551, 155.906); break;}
  2079. case 'E10': {$pf = array( 76.535, 110.551); break;}
  2080. case 'E11': {$pf = array( 53.858, 76.535); break;}
  2081. case 'E12': {$pf = array( 36.850, 53.858); break;}
  2082. // SIS 014711 G Series
  2083. case 'G0' : {$pf = array( 2715.591, 3838.110); break;}
  2084. case 'G1' : {$pf = array( 1919.055, 2715.591); break;}
  2085. case 'G2' : {$pf = array( 1357.795, 1919.055); break;}
  2086. case 'G3' : {$pf = array( 958.110, 1357.795); break;}
  2087. case 'G4' : {$pf = array( 677.480, 958.110); break;}
  2088. case 'G5' : {$pf = array( 479.055, 677.480); break;}
  2089. case 'G6' : {$pf = array( 337.323, 479.055); break;}
  2090. case 'G7' : {$pf = array( 238.110, 337.323); break;}
  2091. case 'G8' : {$pf = array( 167.244, 238.110); break;}
  2092. case 'G9' : {$pf = array( 119.055, 167.244); break;}
  2093. case 'G10': {$pf = array( 82.205, 119.055); break;}
  2094. case 'G11': {$pf = array( 59.528, 82.205); break;}
  2095. case 'G12': {$pf = array( 39.685, 59.528); break;}
  2096. // ISO Press
  2097. case 'RA0': {$pf = array( 2437.795, 3458.268); break;}
  2098. case 'RA1': {$pf = array( 1729.134, 2437.795); break;}
  2099. case 'RA2': {$pf = array( 1218.898, 1729.134); break;}
  2100. case 'RA3': {$pf = array( 864.567, 1218.898); break;}
  2101. case 'RA4': {$pf = array( 609.449, 864.567); break;}
  2102. case 'SRA0': {$pf = array( 2551.181, 3628.346); break;}
  2103. case 'SRA1': {$pf = array( 1814.173, 2551.181); break;}
  2104. case 'SRA2': {$pf = array( 1275.591, 1814.173); break;}
  2105. case 'SRA3': {$pf = array( 907.087, 1275.591); break;}
  2106. case 'SRA4': {$pf = array( 637.795, 907.087); break;}
  2107. // German DIN 476
  2108. case '4A0': {$pf = array( 4767.874, 6740.787); break;}
  2109. case '2A0': {$pf = array( 3370.394, 4767.874); break;}
  2110. // Variations on the ISO Standard
  2111. case 'A2_EXTRA' : {$pf = array( 1261.417, 1754.646); break;}
  2112. case 'A3+' : {$pf = array( 932.598, 1369.134); break;}
  2113. case 'A3_EXTRA' : {$pf = array( 912.756, 1261.417); break;}
  2114. case 'A3_SUPER' : {$pf = array( 864.567, 1440.000); break;}
  2115. case 'SUPER_A3' : {$pf = array( 864.567, 1380.472); break;}
  2116. case 'A4_EXTRA' : {$pf = array( 666.142, 912.756); break;}
  2117. case 'A4_SUPER' : {$pf = array( 649.134, 912.756); break;}
  2118. case 'SUPER_A4' : {$pf = array( 643.465, 1009.134); break;}
  2119. case 'A4_LONG' : {$pf = array( 595.276, 986.457); break;}
  2120. case 'F4' : {$pf = array( 595.276, 935.433); break;}
  2121. case 'SO_B5_EXTRA': {$pf = array( 572.598, 782.362); break;}
  2122. case 'A5_EXTRA' : {$pf = array( 490.394, 666.142); break;}
  2123. // ANSI Series
  2124. case 'ANSI_E': {$pf = array( 2448.000, 3168.000); break;}
  2125. case 'ANSI_D': {$pf = array( 1584.000, 2448.000); break;}
  2126. case 'ANSI_C': {$pf = array( 1224.000, 1584.000); break;}
  2127. case 'ANSI_B': {$pf = array( 792.000, 1224.000); break;}
  2128. case 'ANSI_A': {$pf = array( 612.000, 792.000); break;}
  2129. // Traditional 'Loose' North American Paper Sizes
  2130. case 'USLEDGER':
  2131. case 'LEDGER' : {$pf = array( 1224.000, 792.000); break;}
  2132. case 'ORGANIZERK':
  2133. case 'BIBLE':
  2134. case 'USTABLOID':
  2135. case 'TABLOID': {$pf = array( 792.000, 1224.000); break;}
  2136. case 'ORGANIZERM':
  2137. case 'USLETTER':
  2138. case 'LETTER' : {$pf = array( 612.000, 792.000); break;}
  2139. case 'USLEGAL':
  2140. case 'LEGAL' : {$pf = array( 612.000, 1008.000); break;}
  2141. case 'GOVERNMENTLETTER':
  2142. case 'GLETTER': {$pf = array( 576.000, 756.000); break;}
  2143. case 'JUNIORLEGAL':
  2144. case 'JLEGAL' : {$pf = array( 576.000, 360.000); break;}
  2145. // Other North American Paper Sizes
  2146. case 'QUADDEMY': {$pf = array( 2520.000, 3240.000); break;}
  2147. case 'SUPER_B': {$pf = array( 936.000, 1368.000); break;}
  2148. case 'QUARTO': {$pf = array( 648.000, 792.000); break;}
  2149. case 'GOVERNMENTLEGAL':
  2150. case 'FOLIO': {$pf = array( 612.000, 936.000); break;}
  2151. case 'MONARCH':
  2152. case 'EXECUTIVE': {$pf = array( 522.000, 756.000); break;}
  2153. case 'ORGANIZERL':
  2154. case 'STATEMENT':
  2155. case 'MEMO': {$pf = array( 396.000, 612.000); break;}
  2156. case 'FOOLSCAP': {$pf = array( 595.440, 936.000); break;}
  2157. case 'COMPACT': {$pf = array( 306.000, 486.000); break;}
  2158. case 'ORGANIZERJ': {$pf = array( 198.000, 360.000); break;}
  2159. // Canadian standard CAN 2-9.60M
  2160. case 'P1': {$pf = array( 1587.402, 2437.795); break;}
  2161. case 'P2': {$pf = array( 1218.898, 1587.402); break;}
  2162. case 'P3': {$pf = array( 793.701, 1218.898); break;}
  2163. case 'P4': {$pf = array( 609.449, 793.701); break;}
  2164. case 'P5': {$pf = array( 396.850, 609.449); break;}
  2165. case 'P6': {$pf = array( 303.307, 396.850); break;}
  2166. // North American Architectural Sizes
  2167. case 'ARCH_E' : {$pf = array( 2592.000, 3456.000); break;}
  2168. case 'ARCH_E1': {$pf = array( 2160.000, 3024.000); break;}
  2169. case 'ARCH_D' : {$pf = array( 1728.000, 2592.000); break;}
  2170. case 'BROADSHEET':
  2171. case 'ARCH_C' : {$pf = array( 1296.000, 1728.000); break;}
  2172. case 'ARCH_B' : {$pf = array( 864.000, 1296.000); break;}
  2173. case 'ARCH_A' : {$pf = array( 648.000, 864.000); break;}
  2174. // --- North American Envelope Sizes ---
  2175. // - Announcement Envelopes
  2176. case 'ANNENV_A2' : {$pf = array( 314.640, 414.000); break;}
  2177. case 'ANNENV_A6' : {$pf = array( 342.000, 468.000); break;}
  2178. case 'ANNENV_A7' : {$pf = array( 378.000, 522.000); break;}
  2179. case 'ANNENV_A8' : {$pf = array( 396.000, 584.640); break;}
  2180. case 'ANNENV_A10' : {$pf = array( 450.000, 692.640); break;}
  2181. case 'ANNENV_SLIM': {$pf = array( 278.640, 638.640); break;}
  2182. // - Commercial Envelopes
  2183. case 'COMMENV_N6_1/4': {$pf = array( 252.000, 432.000); break;}
  2184. case 'COMMENV_N6_3/4': {$pf = array( 260.640, 468.000); break;}
  2185. case 'COMMENV_N8' : {$pf = array( 278.640, 540.000); break;}
  2186. case 'COMMENV_N9' : {$pf = array( 278.640, 638.640); break;}
  2187. case 'COMMENV_N10' : {$pf = array( 296.640, 684.000); break;}
  2188. case 'COMMENV_N11' : {$pf = array( 324.000, 746.640); break;}
  2189. case 'COMMENV_N12' : {$pf = array( 342.000, 792.000); break;}
  2190. case 'COMMENV_N14' : {$pf = array( 360.000, 828.000); break;}
  2191. // - Catalogue Envelopes
  2192. case 'CATENV_N1' : {$pf = array( 432.000, 648.000); break;}
  2193. case 'CATENV_N1_3/4' : {$pf = array( 468.000, 684.000); break;}
  2194. case 'CATENV_N2' : {$pf = array( 468.000, 720.000); break;}
  2195. case 'CATENV_N3' : {$pf = array( 504.000, 720.000); break;}
  2196. case 'CATENV_N6' : {$pf = array( 540.000, 756.000); break;}
  2197. case 'CATENV_N7' : {$pf = array( 576.000, 792.000); break;}
  2198. case 'CATENV_N8' : {$pf = array( 594.000, 810.000); break;}
  2199. case 'CATENV_N9_1/2' : {$pf = array( 612.000, 756.000); break;}
  2200. case 'CATENV_N9_3/4' : {$pf = array( 630.000, 810.000); break;}
  2201. case 'CATENV_N10_1/2': {$pf = array( 648.000, 864.000); break;}
  2202. case 'CATENV_N12_1/2': {$pf = array( 684.000, 900.000); break;}
  2203. case 'CATENV_N13_1/2': {$pf = array( 720.000, 936.000); break;}
  2204. case 'CATENV_N14_1/4': {$pf = array( 810.000, 882.000); break;}
  2205. case 'CATENV_N14_1/2': {$pf = array( 828.000, 1044.000); break;}
  2206. // Japanese (JIS P 0138-61) Standard B-Series
  2207. case 'JIS_B0' : {$pf = array( 2919.685, 4127.244); break;}
  2208. case 'JIS_B1' : {$pf = array( 2063.622, 2919.685); break;}
  2209. case 'JIS_B2' : {$pf = array( 1459.843, 2063.622); break;}
  2210. case 'JIS_B3' : {$pf = array( 1031.811, 1459.843); break;}
  2211. case 'JIS_B4' : {$pf = array( 728.504, 1031.811); break;}
  2212. case 'JIS_B5' : {$pf = array( 515.906, 728.504); break;}
  2213. case 'JIS_B6' : {$pf = array( 362.835, 515.906); break;}
  2214. case 'JIS_B7' : {$pf = array( 257.953, 362.835); break;}
  2215. case 'JIS_B8' : {$pf = array( 181.417, 257.953); break;}
  2216. case 'JIS_B9' : {$pf = array( 127.559, 181.417); break;}
  2217. case 'JIS_B10': {$pf = array( 90.709, 127.559); break;}
  2218. case 'JIS_B11': {$pf = array( 62.362, 90.709); break;}
  2219. case 'JIS_B12': {$pf = array( 45.354, 62.362); break;}
  2220. // PA Series
  2221. case 'PA0' : {$pf = array( 2381.102, 3174.803,); break;}
  2222. case 'PA1' : {$pf = array( 1587.402, 2381.102); break;}
  2223. case 'PA2' : {$pf = array( 1190.551, 1587.402); break;}
  2224. case 'PA3' : {$pf = array( 793.701, 1190.551); break;}
  2225. case 'PA4' : {$pf = array( 595.276, 793.701); break;}
  2226. case 'PA5' : {$pf = array( 396.850, 595.276); break;}
  2227. case 'PA6' : {$pf = array( 297.638, 396.850); break;}
  2228. case 'PA7' : {$pf = array( 198.425, 297.638); break;}
  2229. case 'PA8' : {$pf = array( 147.402, 198.425); break;}
  2230. case 'PA9' : {$pf = array( 99.213, 147.402); break;}
  2231. case 'PA10': {$pf = array( 73.701, 99.213); break;}
  2232. // Standard Photographic Print Sizes
  2233. case 'PASSPORT_PHOTO': {$pf = array( 99.213, 127.559); break;}
  2234. case 'E' : {$pf = array( 233.858, 340.157); break;}
  2235. case 'L':
  2236. case '3R' : {$pf = array( 252.283, 360.000); break;}
  2237. case 'KG':
  2238. case '4R' : {$pf = array( 289.134, 430.866); break;}
  2239. case '4D' : {$pf = array( 340.157, 430.866); break;}
  2240. case '2L':
  2241. case '5R' : {$pf = array( 360.000, 504.567); break;}
  2242. case '8P':
  2243. case '6R' : {$pf = array( 430.866, 575.433); break;}
  2244. case '6P':
  2245. case '8R' : {$pf = array( 575.433, 720.000); break;}
  2246. case '6PW':
  2247. case 'S8R' : {$pf = array( 575.433, 864.567); break;}
  2248. case '4P':
  2249. case '10R' : {$pf = array( 720.000, 864.567); break;}
  2250. case '4PW':
  2251. case 'S10R': {$pf = array( 720.000, 1080.000); break;}
  2252. case '11R' : {$pf = array( 790.866, 1009.134); break;}
  2253. case 'S11R': {$pf = array( 790.866, 1224.567); break;}
  2254. case '12R' : {$pf = array( 864.567, 1080.000); break;}
  2255. case 'S12R': {$pf = array( 864.567, 1292.598); break;}
  2256. // Common Newspaper Sizes
  2257. case 'NEWSPAPER_BROADSHEET': {$pf = array( 2125.984, 1700.787); break;}
  2258. case 'NEWSPAPER_BERLINER' : {$pf = array( 1332.283, 892.913); break;}
  2259. case 'NEWSPAPER_TABLOID':
  2260. case 'NEWSPAPER_COMPACT' : {$pf = array( 1218.898, 793.701); break;}
  2261. // Business Cards
  2262. case 'CREDIT_CARD':
  2263. case 'BUSINESS_CARD':
  2264. case 'BUSINESS_CARD_ISO7810': {$pf = array( 153.014, 242.646); break;}
  2265. case 'BUSINESS_CARD_ISO216' : {$pf = array( 147.402, 209.764); break;}
  2266. case 'BUSINESS_CARD_IT':
  2267. case 'BUSINESS_CARD_UK':
  2268. case 'BUSINESS_CARD_FR':
  2269. case 'BUSINESS_CARD_DE':
  2270. case 'BUSINESS_CARD_ES' : {$pf = array( 155.906, 240.945); break;}
  2271. case 'BUSINESS_CARD_CA':
  2272. case 'BUSINESS_CARD_US' : {$pf = array( 144.567, 252.283); break;}
  2273. case 'BUSINESS_CARD_JP' : {$pf = array( 155.906, 257.953); break;}
  2274. case 'BUSINESS_CARD_HK' : {$pf = array( 153.071, 255.118); break;}
  2275. case 'BUSINESS_CARD_AU':
  2276. case 'BUSINESS_CARD_DK':
  2277. case 'BUSINESS_CARD_SE' : {$pf = array( 155.906, 255.118); break;}
  2278. case 'BUSINESS_CARD_RU':
  2279. case 'BUSINESS_CARD_CZ':
  2280. case 'BUSINESS_CARD_FI':
  2281. case 'BUSINESS_CARD_HU':
  2282. case 'BUSINESS_CARD_IL' : {$pf = array( 141.732, 255.118); break;}
  2283. // Billboards
  2284. case '4SHEET' : {$pf = array( 2880.000, 4320.000); break;}
  2285. case '6SHEET' : {$pf = array( 3401.575, 5102.362); break;}
  2286. case '12SHEET': {$pf = array( 8640.000, 4320.000); break;}
  2287. case '16SHEET': {$pf = array( 5760.000, 8640.000); break;}
  2288. case '32SHEET': {$pf = array(11520.000, 8640.000); break;}
  2289. case '48SHEET': {$pf = array(17280.000, 8640.000); break;}
  2290. case '64SHEET': {$pf = array(23040.000, 8640.000); break;}
  2291. case '96SHEET': {$pf = array(34560.000, 8640.000); break;}
  2292. // Old European Sizes
  2293. // - Old Imperial English Sizes
  2294. case 'EN_EMPEROR' : {$pf = array( 3456.000, 5184.000); break;}
  2295. case 'EN_ANTIQUARIAN' : {$pf = array( 2232.000, 3816.000); break;}
  2296. case 'EN_GRAND_EAGLE' : {$pf = array( 2070.000, 3024.000); break;}
  2297. case 'EN_DOUBLE_ELEPHANT' : {$pf = array( 1926.000, 2880.000); break;}
  2298. case 'EN_ATLAS' : {$pf = array( 1872.000, 2448.000); break;}
  2299. case 'EN_COLOMBIER' : {$pf = array( 1692.000, 2484.000); break;}
  2300. case 'EN_ELEPHANT' : {$pf = array( 1656.000, 2016.000); break;}
  2301. case 'EN_DOUBLE_DEMY' : {$pf = array( 1620.000, 2556.000); break;}
  2302. case 'EN_IMPERIAL' : {$pf = array( 1584.000, 2160.000); break;}
  2303. case 'EN_PRINCESS' : {$pf = array( 1548.000, 2016.000); break;}
  2304. case 'EN_CARTRIDGE' : {$pf = array( 1512.000, 1872.000); break;}
  2305. case 'EN_DOUBLE_LARGE_POST': {$pf = array( 1512.000, 2376.000); break;}
  2306. case 'EN_ROYAL' : {$pf = array( 1440.000, 1800.000); break;}
  2307. case 'EN_SHEET':
  2308. case 'EN_HALF_POST' : {$pf = array( 1404.000, 1692.000); break;}
  2309. case 'EN_SUPER_ROYAL' : {$pf = array( 1368.000, 1944.000); break;}
  2310. case 'EN_DOUBLE_POST' : {$pf = array( 1368.000, 2196.000); break;}
  2311. case 'EN_MEDIUM' : {$pf = array( 1260.000, 1656.000); break;}
  2312. case 'EN_DEMY' : {$pf = array( 1260.000, 1620.000); break;}
  2313. case 'EN_LARGE_POST' : {$pf = array( 1188.000, 1512.000); break;}
  2314. case 'EN_COPY_DRAUGHT' : {$pf = array( 1152.000, 1440.000); break;}
  2315. case 'EN_POST' : {$pf = array( 1116.000, 1386.000); break;}
  2316. case 'EN_CROWN' : {$pf = array( 1080.000, 1440.000); break;}
  2317. case 'EN_PINCHED_POST' : {$pf = array( 1062.000, 1332.000); break;}
  2318. case 'EN_BRIEF' : {$pf = array( 972.000, 1152.000); break;}
  2319. case 'EN_FOOLSCAP' : {$pf = array( 972.000, 1224.000); break;}
  2320. case 'EN_SMALL_FOOLSCAP' : {$pf = array( 954.000, 1188.000); break;}
  2321. case 'EN_POTT' : {$pf = array( 900.000, 1080.000); break;}
  2322. // - Old Imperial Belgian Sizes
  2323. case 'BE_GRAND_AIGLE' : {$pf = array( 1984.252, 2948.031); break;}
  2324. case 'BE_COLOMBIER' : {$pf = array( 1757.480, 2409.449); break;}
  2325. case 'BE_DOUBLE_CARRE': {$pf = array( 1757.480, 2607.874); break;}
  2326. case 'BE_ELEPHANT' : {$pf = array( 1746.142, 2182.677); break;}
  2327. case 'BE_PETIT_AIGLE' : {$pf = array( 1700.787, 2381.102); break;}
  2328. case 'BE_GRAND_JESUS' : {$pf = array( 1559.055, 2069.291); break;}
  2329. case 'BE_JESUS' : {$pf = array( 1530.709, 2069.291); break;}
  2330. case 'BE_RAISIN' : {$pf = array( 1417.323, 1842.520); break;}
  2331. case 'BE_GRAND_MEDIAN': {$pf = array( 1303.937, 1714.961); break;}
  2332. case 'BE_DOUBLE_POSTE': {$pf = array( 1233.071, 1601.575); break;}
  2333. case 'BE_COQUILLE' : {$pf = array( 1218.898, 1587.402); break;}
  2334. case 'BE_PETIT_MEDIAN': {$pf = array( 1176.378, 1502.362); break;}
  2335. case 'BE_RUCHE' : {$pf = array( 1020.472, 1303.937); break;}
  2336. case 'BE_PROPATRIA' : {$pf = array( 977.953, 1218.898); break;}
  2337. case 'BE_LYS' : {$pf = array( 898.583, 1125.354); break;}
  2338. case 'BE_POT' : {$pf = array( 870.236, 1088.504); break;}
  2339. case 'BE_ROSETTE' : {$pf = array( 765.354, 983.622); break;}
  2340. // - Old Imperial French Sizes
  2341. case 'FR_UNIVERS' : {$pf = array( 2834.646, 3685.039); break;}
  2342. case 'FR_DOUBLE_COLOMBIER' : {$pf = array( 2551.181, 3571.654); break;}
  2343. case 'FR_GRANDE_MONDE' : {$pf = array( 2551.181, 3571.654); break;}
  2344. case 'FR_DOUBLE_SOLEIL' : {$pf = array( 2267.717, 3401.575); break;}
  2345. case 'FR_DOUBLE_JESUS' : {$pf = array( 2154.331, 3174.803); break;}
  2346. case 'FR_GRAND_AIGLE' : {$pf = array( 2125.984, 3004.724); break;}
  2347. case 'FR_PETIT_AIGLE' : {$pf = array( 1984.252, 2664.567); break;}
  2348. case 'FR_DOUBLE_RAISIN' : {$pf = array( 1842.520, 2834.646); break;}
  2349. case 'FR_JOURNAL' : {$pf = array( 1842.520, 2664.567); break;}
  2350. case 'FR_COLOMBIER_AFFICHE': {$pf = array( 1785.827, 2551.181); break;}
  2351. case 'FR_DOUBLE_CAVALIER' : {$pf = array( 1757.480, 2607.874); break;}
  2352. case 'FR_CLOCHE' : {$pf = array( 1700.787, 2267.717); break;}
  2353. case 'FR_SOLEIL' : {$pf = array( 1700.787, 2267.717); break;}
  2354. case 'FR_DOUBLE_CARRE' : {$pf = array( 1587.402, 2551.181); break;}
  2355. case 'FR_DOUBLE_COQUILLE' : {$pf = array( 1587.402, 2494.488); break;}
  2356. case 'FR_JESUS' : {$pf = array( 1587.402, 2154.331); break;}
  2357. case 'FR_RAISIN' : {$pf = array( 1417.323, 1842.520); break;}
  2358. case 'FR_CAVALIER' : {$pf = array( 1303.937, 1757.480); break;}
  2359. case 'FR_DOUBLE_COURONNE' : {$pf = array( 1303.937, 2040.945); break;}
  2360. case 'FR_CARRE' : {$pf = array( 1275.591, 1587.402); break;}
  2361. case 'FR_COQUILLE' : {$pf = array( 1247.244, 1587.402); break;}
  2362. case 'FR_DOUBLE_TELLIERE' : {$pf = array( 1247.244, 1927.559); break;}
  2363. case 'FR_DOUBLE_CLOCHE' : {$pf = array( 1133.858, 1700.787); break;}
  2364. case 'FR_DOUBLE_POT' : {$pf = array( 1133.858, 1757.480); break;}
  2365. case 'FR_ECU' : {$pf = array( 1133.858, 1474.016); break;}
  2366. case 'FR_COURONNE' : {$pf = array( 1020.472, 1303.937); break;}
  2367. case 'FR_TELLIERE' : {$pf = array( 963.780, 1247.244); break;}
  2368. case 'FR_POT' : {$pf = array( 878.740, 1133.858); break;}
  2369. // DEFAULT ISO A4
  2370. default: {$pf = array( 595.276, 841.890); break;}
  2371. }
  2372. return $pf;
  2373. }
  2374. /**
  2375. * Change the format of the current page
  2376. * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() documentation or an array of two numners (width, height) or an array containing the following measures and options:<ul>
  2377. * <li>['format'] = page format name (one of the above);</li>
  2378. * <li>['Rotate'] : The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.</li>
  2379. * <li>['PZ'] : The page's preferred zoom (magnification) factor.</li>
  2380. * <li>['MediaBox'] : the boundaries of the physical medium on which the page shall be displayed or printed:</li>
  2381. * <li>['MediaBox']['llx'] : lower-left x coordinate in points</li>
  2382. * <li>['MediaBox']['lly'] : lower-left y coordinate in points</li>
  2383. * <li>['MediaBox']['urx'] : upper-right x coordinate in points</li>
  2384. * <li>['MediaBox']['ury'] : upper-right y coordinate in points</li>
  2385. * <li>['CropBox'] : the visible region of default user space:</li>
  2386. * <li>['CropBox']['llx'] : lower-left x coordinate in points</li>
  2387. * <li>['CropBox']['lly'] : lower-left y coordinate in points</li>
  2388. * <li>['CropBox']['urx'] : upper-right x coordinate in points</li>
  2389. * <li>['CropBox']['ury'] : upper-right y coordinate in points</li>
  2390. * <li>['BleedBox'] : the region to which the contents of the page shall be clipped when output in a production environment:</li>
  2391. * <li>['BleedBox']['llx'] : lower-left x coordinate in points</li>
  2392. * <li>['BleedBox']['lly'] : lower-left y coordinate in points</li>
  2393. * <li>['BleedBox']['urx'] : upper-right x coordinate in points</li>
  2394. * <li>['BleedBox']['ury'] : upper-right y coordinate in points</li>
  2395. * <li>['TrimBox'] : the intended dimensions of the finished page after trimming:</li>
  2396. * <li>['TrimBox']['llx'] : lower-left x coordinate in points</li>
  2397. * <li>['TrimBox']['lly'] : lower-left y coordinate in points</li>
  2398. * <li>['TrimBox']['urx'] : upper-right x coordinate in points</li>
  2399. * <li>['TrimBox']['ury'] : upper-right y coordinate in points</li>
  2400. * <li>['ArtBox'] : the extent of the page's meaningful content:</li>
  2401. * <li>['ArtBox']['llx'] : lower-left x coordinate in points</li>
  2402. * <li>['ArtBox']['lly'] : lower-left y coordinate in points</li>
  2403. * <li>['ArtBox']['urx'] : upper-right x coordinate in points</li>
  2404. * <li>['ArtBox']['ury'] : upper-right y coordinate in points</li>
  2405. * <li>['BoxColorInfo'] :specify the colours and other visual characteristics that should be used in displaying guidelines on the screen for each of the possible page boundaries other than the MediaBox:</li>
  2406. * <li>['BoxColorInfo'][BOXTYPE]['C'] : an array of three numbers in the range 0-255, representing the components in the DeviceRGB colour space.</li>
  2407. * <li>['BoxColorInfo'][BOXTYPE]['W'] : the guideline width in default user units</li>
  2408. * <li>['BoxColorInfo'][BOXTYPE]['S'] : the guideline style: S = Solid; D = Dashed</li>
  2409. * <li>['BoxColorInfo'][BOXTYPE]['D'] : dash array defining a pattern of dashes and gaps to be used in drawing dashed guidelines</li>
  2410. * <li>['trans'] : the style and duration of the visual transition to use when moving from another page to the given page during a presentation</li>
  2411. * <li>['trans']['Dur'] : The page's display duration (also called its advance timing): the maximum length of time, in seconds, that the page shall be displayed during presentations before the viewer application shall automatically advance to the next page.</li>
  2412. * <li>['trans']['S'] : transition style : Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade</li>
  2413. * <li>['trans']['D'] : The duration of the transition effect, in seconds.</li>
  2414. * <li>['trans']['Dm'] : (Split and Blinds transition styles only) The dimension in which the specified transition effect shall occur: H = Horizontal, V = Vertical. Default value: H.</li>
  2415. * <li>['trans']['M'] : (Split, Box and Fly transition styles only) The direction of motion for the specified transition effect: I = Inward from the edges of the page, O = Outward from the center of the pageDefault value: I.</li>
  2416. * <li>['trans']['Di'] : (Wipe, Glitter, Fly, Cover, Uncover and Push transition styles only) The direction in which the specified transition effect shall moves, expressed in degrees counterclockwise starting from a left-to-right direction. If the value is a number, it shall be one of: 0 = Left to right, 90 = Bottom to top (Wipe only), 180 = Right to left (Wipe only), 270 = Top to bottom, 315 = Top-left to bottom-right (Glitter only). If the value is a name, it shall be None, which is relevant only for the Fly transition when the value of SS is not 1.0. Default value: 0.</li>
  2417. * <li>['trans']['SS'] : (Fly transition style only) The starting or ending scale at which the changes shall be drawn. If M specifies an inward transition, the scale of the changes drawn shall progress from SS to 1.0 over the course of the transition. If M specifies an outward transition, the scale of the changes drawn shall progress from 1.0 to SS over the course of the transition. Default: 1.0.</li>
  2418. * <li>['trans']['B'] : (Fly transition style only) If true, the area that shall be flown in is rectangular and opaque. Default: false.</li>
  2419. * </ul>
  2420. * @param string $orientation page orientation. Possible values are (case insensitive):<ul>
  2421. * <li>P or Portrait (default)</li>
  2422. * <li>L or Landscape</li>
  2423. * <li>'' (empty string) for automatic orientation</li>
  2424. * </ul>
  2425. * @access protected
  2426. * @since 3.0.015 (2008-06-06)
  2427. * @see getPageSizeFromFormat()
  2428. */
  2429. protected function setPageFormat($format, $orientation='P') {
  2430. if (!empty($format) AND isset($this->pagedim[$this->page])) {
  2431. // remove inherited values
  2432. unset($this->pagedim[$this->page]);
  2433. }
  2434. if (is_string($format)) {
  2435. // get page measures from format name
  2436. $pf = $this->getPageSizeFromFormat($format);
  2437. $this->fwPt = $pf[0];
  2438. $this->fhPt = $pf[1];
  2439. } else {
  2440. // the boundaries of the physical medium on which the page shall be displayed or printed
  2441. if (isset($format['MediaBox'])) {
  2442. $this->setPageBoxes($this->page, 'MediaBox', $format['MediaBox']['llx'], $format['MediaBox']['lly'], $format['MediaBox']['urx'], $format['MediaBox']['ury'], false);
  2443. $this->fwPt = (($format['MediaBox']['urx'] - $format['MediaBox']['llx']) * $this->k);
  2444. $this->fhPt = (($format['MediaBox']['ury'] - $format['MediaBox']['lly']) * $this->k);
  2445. } else {
  2446. if (isset($format[0]) AND is_numeric($format[0]) AND isset($format[1]) AND is_numeric($format[1])) {
  2447. $pf = array(($format[0] * $this->k), ($format[1] * $this->k));
  2448. } else {
  2449. if (!isset($format['format'])) {
  2450. // default value
  2451. $format['format'] = 'A4';
  2452. }
  2453. $pf = $this->getPageSizeFromFormat($format['format']);
  2454. }
  2455. $this->fwPt = $pf[0];
  2456. $this->fhPt = $pf[1];
  2457. $this->setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true);
  2458. }
  2459. // the visible region of default user space
  2460. if (isset($format['CropBox'])) {
  2461. $this->setPageBoxes($this->page, 'CropBox', $format['CropBox']['llx'], $format['CropBox']['lly'], $format['CropBox']['urx'], $format['CropBox']['ury'], false);
  2462. }
  2463. // the region to which the contents of the page shall be clipped when output in a production environment
  2464. if (isset($format['BleedBox'])) {
  2465. $this->setPageBoxes($this->page, 'BleedBox', $format['BleedBox']['llx'], $format['BleedBox']['lly'], $format['BleedBox']['urx'], $format['BleedBox']['ury'], false);
  2466. }
  2467. // the intended dimensions of the finished page after trimming
  2468. if (isset($format['TrimBox'])) {
  2469. $this->setPageBoxes($this->page, 'TrimBox', $format['TrimBox']['llx'], $format['TrimBox']['lly'], $format['TrimBox']['urx'], $format['TrimBox']['ury'], false);
  2470. }
  2471. // the page's meaningful content (including potential white space)
  2472. if (isset($format['ArtBox'])) {
  2473. $this->setPageBoxes($this->page, 'ArtBox', $format['ArtBox']['llx'], $format['ArtBox']['lly'], $format['ArtBox']['urx'], $format['ArtBox']['ury'], false);
  2474. }
  2475. // specify the colours and other visual characteristics that should be used in displaying guidelines on the screen for the various page boundaries
  2476. if (isset($format['BoxColorInfo'])) {
  2477. $this->pagedim[$this->page]['BoxColorInfo'] = $format['BoxColorInfo'];
  2478. }
  2479. if (isset($format['Rotate']) AND (($format['Rotate'] % 90) == 0)) {
  2480. // The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
  2481. $this->pagedim[$this->page]['Rotate'] = intval($format['Rotate']);
  2482. }
  2483. if (isset($format['PZ'])) {
  2484. // The page's preferred zoom (magnification) factor
  2485. $this->pagedim[$this->page]['PZ'] = floatval($format['PZ']);
  2486. }
  2487. if (isset($format['trans'])) {
  2488. // The style and duration of the visual transition to use when moving from another page to the given page during a presentation
  2489. if (isset($format['trans']['Dur'])) {
  2490. // The page's display duration
  2491. $this->pagedim[$this->page]['trans']['Dur'] = floatval($format['trans']['Dur']);
  2492. }
  2493. $stansition_styles = array('Split', 'Blinds', 'Box', 'Wipe', 'Dissolve', 'Glitter', 'R', 'Fly', 'Push', 'Cover', 'Uncover', 'Fade');
  2494. if (isset($format['trans']['S']) AND in_array($format['trans']['S'], $stansition_styles)) {
  2495. // The transition style that shall be used when moving to this page from another during a presentation
  2496. $this->pagedim[$this->page]['trans']['S'] = $format['trans']['S'];
  2497. $valid_effect = array('Split', 'Blinds');
  2498. $valid_vals = array('H', 'V');
  2499. if (isset($format['trans']['Dm']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['Dm'], $valid_vals)) {
  2500. $this->pagedim[$this->page]['trans']['Dm'] = $format['trans']['Dm'];
  2501. }
  2502. $valid_effect = array('Split', 'Box', 'Fly');
  2503. $valid_vals = array('I', 'O');
  2504. if (isset($format['trans']['M']) AND in_array($format['trans']['S'], $valid_effect) AND in_array($format['trans']['M'], $valid_vals)) {
  2505. $this->pagedim[$this->page]['trans']['M'] = $format['trans']['M'];
  2506. }
  2507. $valid_effect = array('Wipe', 'Glitter', 'Fly', 'Cover', 'Uncover', 'Push');
  2508. if (isset($format['trans']['Di']) AND in_array($format['trans']['S'], $valid_effect)) {
  2509. if (((($format['trans']['Di'] == 90) OR ($format['trans']['Di'] == 180)) AND ($format['trans']['S'] == 'Wipe'))
  2510. OR (($format['trans']['Di'] == 315) AND ($format['trans']['S'] == 'Glitter'))
  2511. OR (($format['trans']['Di'] == 0) OR ($format['trans']['Di'] == 270))) {
  2512. $this->pagedim[$this->page]['trans']['Di'] = intval($format['trans']['Di']);
  2513. }
  2514. }
  2515. if (isset($format['trans']['SS']) AND ($format['trans']['S'] == 'Fly')) {
  2516. $this->pagedim[$this->page]['trans']['SS'] = floatval($format['trans']['SS']);
  2517. }
  2518. if (isset($format['trans']['B']) AND ($format['trans']['B'] === true) AND ($format['trans']['S'] == 'Fly')) {
  2519. $this->pagedim[$this->page]['trans']['B'] = 'true';
  2520. }
  2521. } else {
  2522. $this->pagedim[$this->page]['trans']['S'] = 'R';
  2523. }
  2524. if (isset($format['trans']['D'])) {
  2525. // The duration of the transition effect, in seconds
  2526. $this->pagedim[$this->page]['trans']['D'] = floatval($format['trans']['D']);
  2527. } else {
  2528. $this->pagedim[$this->page]['trans']['D'] = 1;
  2529. }
  2530. }
  2531. }
  2532. $this->setPageOrientation($orientation);
  2533. }
  2534. /**
  2535. * Set page boundaries.
  2536. * @param int $page page number
  2537. * @param string $type valid values are: <ul><li>'MediaBox' : the boundaries of the physical medium on which the page shall be displayed or printed;</li><li>'CropBox' : the visible region of default user space;</li><li>'BleedBox' : the region to which the contents of the page shall be clipped when output in a production environment;</li><li>'TrimBox' : the intended dimensions of the finished page after trimming;</li><li>'ArtBox' : the page's meaningful content (including potential white space).</li></ul>
  2538. * @param float $llx lower-left x coordinate in user units
  2539. * @param float $lly lower-left y coordinate in user units
  2540. * @param float $urx upper-right x coordinate in user units
  2541. * @param float $ury upper-right y coordinate in user units
  2542. * @param boolean $points if true uses user units as unit of measure, otherwise uses PDF points
  2543. * @access public
  2544. * @since 5.0.010 (2010-05-17)
  2545. */
  2546. public function setPageBoxes($page, $type, $llx, $lly, $urx, $ury, $points=false) {
  2547. if (!isset($this->pagedim[$page])) {
  2548. // initialize array
  2549. $this->pagedim[$page] = array();
  2550. }
  2551. $pageboxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
  2552. if (!in_array($type, $pageboxes)) {
  2553. return;
  2554. }
  2555. if ($points) {
  2556. $k = 1;
  2557. } else {
  2558. $k = $this->k;
  2559. }
  2560. $this->pagedim[$page][$type]['llx'] = ($llx * $k);
  2561. $this->pagedim[$page][$type]['lly'] = ($lly * $k);
  2562. $this->pagedim[$page][$type]['urx'] = ($urx * $k);
  2563. $this->pagedim[$page][$type]['ury'] = ($ury * $k);
  2564. }
  2565. /**
  2566. * Swap X and Y coordinates of page boxes (change page boxes orientation).
  2567. * @param int $page page number
  2568. * @access protected
  2569. * @since 5.0.010 (2010-05-17)
  2570. */
  2571. protected function swapPageBoxCoordinates($page) {
  2572. $pageboxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
  2573. foreach ($pageboxes as $type) {
  2574. // swap X and Y coordinates
  2575. if (isset($this->pagedim[$page][$type])) {
  2576. $tmp = $this->pagedim[$page][$type]['llx'];
  2577. $this->pagedim[$page][$type]['llx'] = $this->pagedim[$page][$type]['lly'];
  2578. $this->pagedim[$page][$type]['lly'] = $tmp;
  2579. $tmp = $this->pagedim[$page][$type]['urx'];
  2580. $this->pagedim[$page][$type]['urx'] = $this->pagedim[$page][$type]['ury'];
  2581. $this->pagedim[$page][$type]['ury'] = $tmp;
  2582. }
  2583. }
  2584. }
  2585. /**
  2586. * Set page orientation.
  2587. * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or Portrait (default)</li><li>L or Landscape</li><li>'' (empty string) for automatic orientation</li></ul>
  2588. * @param boolean $autopagebreak Boolean indicating if auto-page-break mode should be on or off.
  2589. * @param float $bottommargin bottom margin of the page.
  2590. * @access public
  2591. * @since 3.0.015 (2008-06-06)
  2592. */
  2593. public function setPageOrientation($orientation, $autopagebreak='', $bottommargin='') {
  2594. if (!isset($this->pagedim[$this->page]['MediaBox'])) {
  2595. // the boundaries of the physical medium on which the page shall be displayed or printed
  2596. $this->setPageBoxes($this->page, 'MediaBox', 0, 0, $this->fwPt, $this->fhPt, true);
  2597. }
  2598. if (!isset($this->pagedim[$this->page]['CropBox'])) {
  2599. // the visible region of default user space
  2600. $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);
  2601. }
  2602. if (!isset($this->pagedim[$this->page]['BleedBox'])) {
  2603. // the region to which the contents of the page shall be clipped when output in a production environment
  2604. $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);
  2605. }
  2606. if (!isset($this->pagedim[$this->page]['TrimBox'])) {
  2607. // the intended dimensions of the finished page after trimming
  2608. $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);
  2609. }
  2610. if (!isset($this->pagedim[$this->page]['ArtBox'])) {
  2611. // the page's meaningful content (including potential white space)
  2612. $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);
  2613. }
  2614. if (!isset($this->pagedim[$this->page]['Rotate'])) {
  2615. // The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.
  2616. $this->pagedim[$this->page]['Rotate'] = 0;
  2617. }
  2618. if (!isset($this->pagedim[$this->page]['PZ'])) {
  2619. // The page's preferred zoom (magnification) factor
  2620. $this->pagedim[$this->page]['PZ'] = 1;
  2621. }
  2622. if ($this->fwPt > $this->fhPt) {
  2623. // landscape
  2624. $default_orientation = 'L';
  2625. } else {
  2626. // portrait
  2627. $default_orientation = 'P';
  2628. }
  2629. $valid_orientations = array('P', 'L');
  2630. if (empty($orientation)) {
  2631. $orientation = $default_orientation;
  2632. } else {
  2633. $orientation = strtoupper($orientation{0});
  2634. }
  2635. if (in_array($orientation, $valid_orientations) AND ($orientation != $default_orientation)) {
  2636. $this->CurOrientation = $orientation;
  2637. $this->wPt = $this->fhPt;
  2638. $this->hPt = $this->fwPt;
  2639. } else {
  2640. $this->CurOrientation = $default_orientation;
  2641. $this->wPt = $this->fwPt;
  2642. $this->hPt = $this->fhPt;
  2643. }
  2644. if ((abs($this->pagedim[$this->page]['MediaBox']['urx'] - $this->hPt) < $this->feps) AND (abs($this->pagedim[$this->page]['MediaBox']['ury'] - $this->wPt) < $this->feps)){
  2645. // swap X and Y coordinates (change page orientation)
  2646. $this->swapPageBoxCoordinates($this->page);
  2647. }
  2648. $this->w = $this->wPt / $this->k;
  2649. $this->h = $this->hPt / $this->k;
  2650. if ($this->empty_string($autopagebreak)) {
  2651. if (isset($this->AutoPageBreak)) {
  2652. $autopagebreak = $this->AutoPageBreak;
  2653. } else {
  2654. $autopagebreak = true;
  2655. }
  2656. }
  2657. if ($this->empty_string($bottommargin)) {
  2658. if (isset($this->bMargin)) {
  2659. $bottommargin = $this->bMargin;
  2660. } else {
  2661. // default value = 2 cm
  2662. $bottommargin = 2 * 28.35 / $this->k;
  2663. }
  2664. }
  2665. $this->SetAutoPageBreak($autopagebreak, $bottommargin);
  2666. // store page dimensions
  2667. $this->pagedim[$this->page]['w'] = $this->wPt;
  2668. $this->pagedim[$this->page]['h'] = $this->hPt;
  2669. $this->pagedim[$this->page]['wk'] = $this->w;
  2670. $this->pagedim[$this->page]['hk'] = $this->h;
  2671. $this->pagedim[$this->page]['tm'] = $this->tMargin;
  2672. $this->pagedim[$this->page]['bm'] = $bottommargin;
  2673. $this->pagedim[$this->page]['lm'] = $this->lMargin;
  2674. $this->pagedim[$this->page]['rm'] = $this->rMargin;
  2675. $this->pagedim[$this->page]['pb'] = $autopagebreak;
  2676. $this->pagedim[$this->page]['or'] = $this->CurOrientation;
  2677. $this->pagedim[$this->page]['olm'] = $this->original_lMargin;
  2678. $this->pagedim[$this->page]['orm'] = $this->original_rMargin;
  2679. }
  2680. /**
  2681. * Set regular expression to detect withespaces or word separators.
  2682. * The pattern delimiter must be the forward-slash character '/'.
  2683. * Some example patterns are:
  2684. * <pre>
  2685. * Non-Unicode or missing PCRE unicode support: '/[^\S\xa0]/'
  2686. * Unicode and PCRE unicode support: '/[^\S\P{Z}\xa0]/u'
  2687. * Unicode and PCRE unicode support in Chinese mode: '/[^\S\P{Z}\P{Lo}\xa0]/u'
  2688. * if PCRE unicode support is turned ON (\P is the negate class of \p):
  2689. * \p{Z} or \p{Separator}: any kind of Unicode whitespace or invisible separator.
  2690. * \p{Lo} or \p{Other_Letter}: a Unicode letter or ideograph that does not have lowercase and uppercase variants.
  2691. * \p{Lo} is needed for Chinese characters because are packed next to each other without spaces in between.
  2692. * </pre>
  2693. * @param string $re regular expression (leave empty for default).
  2694. * @access public
  2695. * @since 4.6.016 (2009-06-15)
  2696. */
  2697. public function setSpacesRE($re='/[^\S\xa0]/') {
  2698. $this->re_spaces = $re;
  2699. $re_parts = explode('/', $re);
  2700. // get pattern parts
  2701. $this->re_space = array();
  2702. if (isset($re_parts[1]) AND !empty($re_parts[1])) {
  2703. $this->re_space['p'] = $re_parts[1];
  2704. } else {
  2705. $this->re_space['p'] = '[\s]';
  2706. }
  2707. // set pattern modifiers
  2708. if (isset($re_parts[2]) AND !empty($re_parts[2])) {
  2709. $this->re_space['m'] = $re_parts[2];
  2710. } else {
  2711. $this->re_space['m'] = '';
  2712. }
  2713. }
  2714. /**
  2715. * Enable or disable Right-To-Left language mode
  2716. * @param Boolean $enable if true enable Right-To-Left language mode.
  2717. * @param Boolean $resetx if true reset the X position on direction change.
  2718. * @access public
  2719. * @since 2.0.000 (2008-01-03)
  2720. */
  2721. public function setRTL($enable, $resetx=true) {
  2722. $enable = $enable ? true : false;
  2723. $resetx = ($resetx AND ($enable != $this->rtl));
  2724. $this->rtl = $enable;
  2725. $this->tmprtl = false;
  2726. if ($resetx) {
  2727. $this->Ln(0);
  2728. }
  2729. }
  2730. /**
  2731. * Return the RTL status
  2732. * @return boolean
  2733. * @access public
  2734. * @since 4.0.012 (2008-07-24)
  2735. */
  2736. public function getRTL() {
  2737. return $this->rtl;
  2738. }
  2739. /**
  2740. * Force temporary RTL language direction
  2741. * @param mixed $mode can be false, 'L' for LTR or 'R' for RTL
  2742. * @access public
  2743. * @since 2.1.000 (2008-01-09)
  2744. */
  2745. public function setTempRTL($mode) {
  2746. $newmode = false;
  2747. switch (strtoupper($mode)) {
  2748. case 'LTR':
  2749. case 'L': {
  2750. if ($this->rtl) {
  2751. $newmode = 'L';
  2752. }
  2753. break;
  2754. }
  2755. case 'RTL':
  2756. case 'R': {
  2757. if (!$this->rtl) {
  2758. $newmode = 'R';
  2759. }
  2760. break;
  2761. }
  2762. case false:
  2763. default: {
  2764. $newmode = false;
  2765. break;
  2766. }
  2767. }
  2768. $this->tmprtl = $newmode;
  2769. }
  2770. /**
  2771. * Return the current temporary RTL status
  2772. * @return boolean
  2773. * @access public
  2774. * @since 4.8.014 (2009-11-04)
  2775. */
  2776. public function isRTLTextDir() {
  2777. return ($this->rtl OR ($this->tmprtl == 'R'));
  2778. }
  2779. /**
  2780. * Set the last cell height.
  2781. * @param float $h cell height.
  2782. * @author Nicola Asuni
  2783. * @access public
  2784. * @since 1.53.0.TC034
  2785. */
  2786. public function setLastH($h) {
  2787. $this->lasth = $h;
  2788. }
  2789. /**
  2790. * Reset the last cell height.
  2791. * @access public
  2792. * @since 5.9.000 (2010-10-03)
  2793. */
  2794. public function resetLastH() {
  2795. $this->lasth = ($this->FontSize * $this->cell_height_ratio) + $this->cell_padding['T'] + $this->cell_padding['B'];
  2796. }
  2797. /**
  2798. * Get the last cell height.
  2799. * @return last cell height
  2800. * @access public
  2801. * @since 4.0.017 (2008-08-05)
  2802. */
  2803. public function getLastH() {
  2804. return $this->lasth;
  2805. }
  2806. /**
  2807. * Set the adjusting factor to convert pixels to user units.
  2808. * @param float $scale adjusting factor to convert pixels to user units.
  2809. * @author Nicola Asuni
  2810. * @access public
  2811. * @since 1.5.2
  2812. */
  2813. public function setImageScale($scale) {
  2814. $this->imgscale = $scale;
  2815. }
  2816. /**
  2817. * Returns the adjusting factor to convert pixels to user units.
  2818. * @return float adjusting factor to convert pixels to user units.
  2819. * @author Nicola Asuni
  2820. * @access public
  2821. * @since 1.5.2
  2822. */
  2823. public function getImageScale() {
  2824. return $this->imgscale;
  2825. }
  2826. /**
  2827. * Returns an array of page dimensions:
  2828. * <ul><li>$this->pagedim[$this->page]['w'] = page width in points</li><li>$this->pagedim[$this->page]['h'] = height in points</li><li>$this->pagedim[$this->page]['wk'] = page width in user units</li><li>$this->pagedim[$this->page]['hk'] = page height in user units</li><li>$this->pagedim[$this->page]['tm'] = top margin</li><li>$this->pagedim[$this->page]['bm'] = bottom margin</li><li>$this->pagedim[$this->page]['lm'] = left margin</li><li>$this->pagedim[$this->page]['rm'] = right margin</li><li>$this->pagedim[$this->page]['pb'] = auto page break</li><li>$this->pagedim[$this->page]['or'] = page orientation</li><li>$this->pagedim[$this->page]['olm'] = original left margin</li><li>$this->pagedim[$this->page]['orm'] = original right margin</li><li>$this->pagedim[$this->page]['Rotate'] = The number of degrees by which the page shall be rotated clockwise when displayed or printed. The value shall be a multiple of 90.</li><li>$this->pagedim[$this->page]['PZ'] = The page's preferred zoom (magnification) factor.</li><li>$this->pagedim[$this->page]['trans'] : the style and duration of the visual transition to use when moving from another page to the given page during a presentation<ul><li>$this->pagedim[$this->page]['trans']['Dur'] = The page's display duration (also called its advance timing): the maximum length of time, in seconds, that the page shall be displayed during presentations before the viewer application shall automatically advance to the next page.</li><li>$this->pagedim[$this->page]['trans']['S'] = transition style : Split, Blinds, Box, Wipe, Dissolve, Glitter, R, Fly, Push, Cover, Uncover, Fade</li><li>$this->pagedim[$this->page]['trans']['D'] = The duration of the transition effect, in seconds.</li><li>$this->pagedim[$this->page]['trans']['Dm'] = (Split and Blinds transition styles only) The dimension in which the specified transition effect shall occur: H = Horizontal, V = Vertical. Default value: H.</li><li>$this->pagedim[$this->page]['trans']['M'] = (Split, Box and Fly transition styles only) The direction of motion for the specified transition effect: I = Inward from the edges of the page, O = Outward from the center of the pageDefault value: I.</li><li>$this->pagedim[$this->page]['trans']['Di'] = (Wipe, Glitter, Fly, Cover, Uncover and Push transition styles only) The direction in which the specified transition effect shall moves, expressed in degrees counterclockwise starting from a left-to-right direction. If the value is a number, it shall be one of: 0 = Left to right, 90 = Bottom to top (Wipe only), 180 = Right to left (Wipe only), 270 = Top to bottom, 315 = Top-left to bottom-right (Glitter only). If the value is a name, it shall be None, which is relevant only for the Fly transition when the value of SS is not 1.0. Default value: 0.</li><li>$this->pagedim[$this->page]['trans']['SS'] = (Fly transition style only) The starting or ending scale at which the changes shall be drawn. If M specifies an inward transition, the scale of the changes drawn shall progress from SS to 1.0 over the course of the transition. If M specifies an outward transition, the scale of the changes drawn shall progress from 1.0 to SS over the course of the transition. Default: 1.0. </li><li>$this->pagedim[$this->page]['trans']['B'] = (Fly transition style only) If true, the area that shall be flown in is rectangular and opaque. Default: false.</li></ul></li><li>$this->pagedim[$this->page]['MediaBox'] : the boundaries of the physical medium on which the page shall be displayed or printed<ul><li>$this->pagedim[$this->page]['MediaBox']['llx'] = lower-left x coordinate in points</li><li>$this->pagedim[$this->page]['MediaBox']['lly'] = lower-left y coordinate in points</li><li>$this->pagedim[$this->page]['MediaBox']['urx'] = upper-right x coordinate in points</li><li>$this->pagedim[$this->page]['MediaBox']['ury'] = upper-right y coordinate in points</li></ul></li><li>$this->pagedim[$this->page]['CropBox'] : the visible region of default user space<ul><li>$this->pagedim[$this->page]['CropBox']['llx'] = lower-left x coordinate in points</li><li>$this->pagedim[$this->page]['CropBox']['lly'] = lower-left y coordinate in points</li><li>$this->pagedim[$this->page]['CropBox']['urx'] = upper-right x coordinate in points</li><li>$this->pagedim[$this->page]['CropBox']['ury'] = upper-right y coordinate in points</li></ul></li><li>$this->pagedim[$this->page]['BleedBox'] : the region to which the contents of the page shall be clipped when output in a production environment<ul><li>$this->pagedim[$this->page]['BleedBox']['llx'] = lower-left x coordinate in points</li><li>$this->pagedim[$this->page]['BleedBox']['lly'] = lower-left y coordinate in points</li><li>$this->pagedim[$this->page]['BleedBox']['urx'] = upper-right x coordinate in points</li><li>$this->pagedim[$this->page]['BleedBox']['ury'] = upper-right y coordinate in points</li></ul></li><li>$this->pagedim[$this->page]['TrimBox'] : the intended dimensions of the finished page after trimming<ul><li>$this->pagedim[$this->page]['TrimBox']['llx'] = lower-left x coordinate in points</li><li>$this->pagedim[$this->page]['TrimBox']['lly'] = lower-left y coordinate in points</li><li>$this->pagedim[$this->page]['TrimBox']['urx'] = upper-right x coordinate in points</li><li>$this->pagedim[$this->page]['TrimBox']['ury'] = upper-right y coordinate in points</li></ul></li><li>$this->pagedim[$this->page]['ArtBox'] : the extent of the page's meaningful content<ul><li>$this->pagedim[$this->page]['ArtBox']['llx'] = lower-left x coordinate in points</li><li>$this->pagedim[$this->page]['ArtBox']['lly'] = lower-left y coordinate in points</li><li>$this->pagedim[$this->page]['ArtBox']['urx'] = upper-right x coordinate in points</li><li>$this->pagedim[$this->page]['ArtBox']['ury'] = upper-right y coordinate in points</li></ul></li></ul>
  2829. * @param int $pagenum page number (empty = current page)
  2830. * @return array of page dimensions.
  2831. * @author Nicola Asuni
  2832. * @access public
  2833. * @since 4.5.027 (2009-03-16)
  2834. */
  2835. public function getPageDimensions($pagenum='') {
  2836. if (empty($pagenum)) {
  2837. $pagenum = $this->page;
  2838. }
  2839. return $this->pagedim[$pagenum];
  2840. }
  2841. /**
  2842. * Returns the page width in units.
  2843. * @param int $pagenum page number (empty = current page)
  2844. * @return int page width.
  2845. * @author Nicola Asuni
  2846. * @access public
  2847. * @since 1.5.2
  2848. * @see getPageDimensions()
  2849. */
  2850. public function getPageWidth($pagenum='') {
  2851. if (empty($pagenum)) {
  2852. return $this->w;
  2853. }
  2854. return $this->pagedim[$pagenum]['w'];
  2855. }
  2856. /**
  2857. * Returns the page height in units.
  2858. * @param int $pagenum page number (empty = current page)
  2859. * @return int page height.
  2860. * @author Nicola Asuni
  2861. * @access public
  2862. * @since 1.5.2
  2863. * @see getPageDimensions()
  2864. */
  2865. public function getPageHeight($pagenum='') {
  2866. if (empty($pagenum)) {
  2867. return $this->h;
  2868. }
  2869. return $this->pagedim[$pagenum]['h'];
  2870. }
  2871. /**
  2872. * Returns the page break margin.
  2873. * @param int $pagenum page number (empty = current page)
  2874. * @return int page break margin.
  2875. * @author Nicola Asuni
  2876. * @access public
  2877. * @since 1.5.2
  2878. * @see getPageDimensions()
  2879. */
  2880. public function getBreakMargin($pagenum='') {
  2881. if (empty($pagenum)) {
  2882. return $this->bMargin;
  2883. }
  2884. return $this->pagedim[$pagenum]['bm'];
  2885. }
  2886. /**
  2887. * Returns the scale factor (number of points in user unit).
  2888. * @return int scale factor.
  2889. * @author Nicola Asuni
  2890. * @access public
  2891. * @since 1.5.2
  2892. */
  2893. public function getScaleFactor() {
  2894. return $this->k;
  2895. }
  2896. /**
  2897. * Defines the left, top and right margins.
  2898. * @param float $left Left margin.
  2899. * @param float $top Top margin.
  2900. * @param float $right Right margin. Default value is the left one.
  2901. * @param boolean $keepmargins if true overwrites the default page margins
  2902. * @access public
  2903. * @since 1.0
  2904. * @see SetLeftMargin(), SetTopMargin(), SetRightMargin(), SetAutoPageBreak()
  2905. */
  2906. public function SetMargins($left, $top, $right=-1, $keepmargins=false) {
  2907. //Set left, top and right margins
  2908. $this->lMargin = $left;
  2909. $this->tMargin = $top;
  2910. if ($right == -1) {
  2911. $right = $left;
  2912. }
  2913. $this->rMargin = $right;
  2914. if ($keepmargins) {
  2915. // overwrite original values
  2916. $this->original_lMargin = $this->lMargin;
  2917. $this->original_rMargin = $this->rMargin;
  2918. }
  2919. }
  2920. /**
  2921. * Defines the left margin. The method can be called before creating the first page. If the current abscissa gets out of page, it is brought back to the margin.
  2922. * @param float $margin The margin.
  2923. * @access public
  2924. * @since 1.4
  2925. * @see SetTopMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
  2926. */
  2927. public function SetLeftMargin($margin) {
  2928. //Set left margin
  2929. $this->lMargin = $margin;
  2930. if (($this->page > 0) AND ($this->x < $margin)) {
  2931. $this->x = $margin;
  2932. }
  2933. }
  2934. /**
  2935. * Defines the top margin. The method can be called before creating the first page.
  2936. * @param float $margin The margin.
  2937. * @access public
  2938. * @since 1.5
  2939. * @see SetLeftMargin(), SetRightMargin(), SetAutoPageBreak(), SetMargins()
  2940. */
  2941. public function SetTopMargin($margin) {
  2942. //Set top margin
  2943. $this->tMargin = $margin;
  2944. if (($this->page > 0) AND ($this->y < $margin)) {
  2945. $this->y = $margin;
  2946. }
  2947. }
  2948. /**
  2949. * Defines the right margin. The method can be called before creating the first page.
  2950. * @param float $margin The margin.
  2951. * @access public
  2952. * @since 1.5
  2953. * @see SetLeftMargin(), SetTopMargin(), SetAutoPageBreak(), SetMargins()
  2954. */
  2955. public function SetRightMargin($margin) {
  2956. $this->rMargin = $margin;
  2957. if (($this->page > 0) AND ($this->x > ($this->w - $margin))) {
  2958. $this->x = $this->w - $margin;
  2959. }
  2960. }
  2961. /**
  2962. * Set the same internal Cell padding for top, right, bottom, left-
  2963. * @param float $pad internal padding.
  2964. * @access public
  2965. * @since 2.1.000 (2008-01-09)
  2966. * @see getCellPaddings(), setCellPaddings()
  2967. */
  2968. public function SetCellPadding($pad) {
  2969. if ($pad >= 0) {
  2970. $this->cell_padding['L'] = $pad;
  2971. $this->cell_padding['T'] = $pad;
  2972. $this->cell_padding['R'] = $pad;
  2973. $this->cell_padding['B'] = $pad;
  2974. }
  2975. }
  2976. /**
  2977. * Set the internal Cell paddings.
  2978. * @param float $left left padding
  2979. * @param float $top top padding
  2980. * @param float $right right padding
  2981. * @param float $bottom bottom padding
  2982. * @access public
  2983. * @since 5.9.000 (2010-10-03)
  2984. * @see getCellPaddings(), SetCellPadding()
  2985. */
  2986. public function setCellPaddings($left='', $top='', $right='', $bottom='') {
  2987. if (($left !== '') AND ($left >= 0)) {
  2988. $this->cell_padding['L'] = $left;
  2989. }
  2990. if (($top !== '') AND ($top >= 0)) {
  2991. $this->cell_padding['T'] = $top;
  2992. }
  2993. if (($right !== '') AND ($right >= 0)) {
  2994. $this->cell_padding['R'] = $right;
  2995. }
  2996. if (($bottom !== '') AND ($bottom >= 0)) {
  2997. $this->cell_padding['B'] = $bottom;
  2998. }
  2999. }
  3000. /**
  3001. * Get the internal Cell padding array.
  3002. * @return array of padding values
  3003. * @access public
  3004. * @since 5.9.000 (2010-10-03)
  3005. * @see setCellPaddings(), SetCellPadding()
  3006. */
  3007. public function getCellPaddings() {
  3008. return $this->cell_padding;
  3009. }
  3010. /**
  3011. * Set the internal Cell margins.
  3012. * @param float $left left margin
  3013. * @param float $top top margin
  3014. * @param float $right right margin
  3015. * @param float $bottom bottom margin
  3016. * @access public
  3017. * @since 5.9.000 (2010-10-03)
  3018. * @see getCellMargins()
  3019. */
  3020. public function setCellMargins($left='', $top='', $right='', $bottom='') {
  3021. if (($left !== '') AND ($left >= 0)) {
  3022. $this->cell_margin['L'] = $left;
  3023. }
  3024. if (($top !== '') AND ($top >= 0)) {
  3025. $this->cell_margin['T'] = $top;
  3026. }
  3027. if (($right !== '') AND ($right >= 0)) {
  3028. $this->cell_margin['R'] = $right;
  3029. }
  3030. if (($bottom !== '') AND ($bottom >= 0)) {
  3031. $this->cell_margin['B'] = $bottom;
  3032. }
  3033. }
  3034. /**
  3035. * Get the internal Cell margin array.
  3036. * @return array of margin values
  3037. * @access public
  3038. * @since 5.9.000 (2010-10-03)
  3039. * @see setCellMargins()
  3040. */
  3041. public function getCellMargins() {
  3042. return $this->cell_margin;
  3043. }
  3044. /**
  3045. * Adjust the internal Cell padding array to take account of the line width.
  3046. * @param mixed $brd Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
  3047. * @return array of adjustments
  3048. * @access public
  3049. * @since 5.9.000 (2010-10-03)
  3050. */
  3051. protected function adjustCellPadding($brd=0) {
  3052. if (empty($brd)) {
  3053. return;
  3054. }
  3055. if (is_string($brd)) {
  3056. // convert string to array
  3057. $slen = strlen($brd);
  3058. $newbrd = array();
  3059. for ($i = 0; $i < $slen; ++$i) {
  3060. $newbrd[$brd{$i}] = true;
  3061. }
  3062. $brd = $newbrd;
  3063. } elseif (($brd === 1) OR ($brd === true) OR (is_numeric($brd) AND (intval($brd) > 0))) {
  3064. $brd = array('LRTB' => true);
  3065. }
  3066. if (!is_array($brd)) {
  3067. return;
  3068. }
  3069. // store current cell padding
  3070. $cp = $this->cell_padding;
  3071. // select border mode
  3072. if (isset($brd['mode'])) {
  3073. $mode = $brd['mode'];
  3074. unset($brd['mode']);
  3075. } else {
  3076. $mode = 'normal';
  3077. }
  3078. // process borders
  3079. foreach ($brd as $border => $style) {
  3080. $line_width = $this->LineWidth;
  3081. if (is_array($style) AND isset($style['width'])) {
  3082. // get border width
  3083. $line_width = $style['width'];
  3084. }
  3085. $adj = 0; // line width inside the cell
  3086. switch ($mode) {
  3087. case 'ext': {
  3088. $adj = 0;
  3089. break;
  3090. }
  3091. case 'int': {
  3092. $adj = $line_width;
  3093. break;
  3094. }
  3095. case 'normal':
  3096. default: {
  3097. $adj = ($line_width / 2);
  3098. break;
  3099. }
  3100. }
  3101. // correct internal cell padding if required to avoid overlap between text and lines
  3102. if ((strpos($border,'T') !== false) AND ($this->cell_padding['T'] < $adj)) {
  3103. $this->cell_padding['T'] = $adj;
  3104. }
  3105. if ((strpos($border,'R') !== false) AND ($this->cell_padding['R'] < $adj)) {
  3106. $this->cell_padding['R'] = $adj;
  3107. }
  3108. if ((strpos($border,'B') !== false) AND ($this->cell_padding['B'] < $adj)) {
  3109. $this->cell_padding['B'] = $adj;
  3110. }
  3111. if ((strpos($border,'L') !== false) AND ($this->cell_padding['L'] < $adj)) {
  3112. $this->cell_padding['L'] = $adj;
  3113. }
  3114. }
  3115. 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']));
  3116. }
  3117. /**
  3118. * Enables or disables the automatic page breaking mode. When enabling, the second parameter is the distance from the bottom of the page that defines the triggering limit. By default, the mode is on and the margin is 2 cm.
  3119. * @param boolean $auto Boolean indicating if mode should be on or off.
  3120. * @param float $margin Distance from the bottom of the page.
  3121. * @access public
  3122. * @since 1.0
  3123. * @see Cell(), MultiCell(), AcceptPageBreak()
  3124. */
  3125. public function SetAutoPageBreak($auto, $margin=0) {
  3126. //Set auto page break mode and triggering margin
  3127. $this->AutoPageBreak = $auto;
  3128. $this->bMargin = $margin;
  3129. $this->PageBreakTrigger = $this->h - $margin;
  3130. }
  3131. /**
  3132. * Defines the way the document is to be displayed by the viewer.
  3133. * @param mixed $zoom The zoom to use. It can be one of the following string values or a number indicating the zooming factor to use. <ul><li>fullpage: displays the entire page on screen </li><li>fullwidth: uses maximum width of window</li><li>real: uses real size (equivalent to 100% zoom)</li><li>default: uses viewer default mode</li></ul>
  3134. * @param string $layout The page layout. Possible values are:<ul><li>SinglePage Display one page at a time</li><li>OneColumn Display the pages in one column</li><li>TwoColumnLeft Display the pages in two columns, with odd-numbered pages on the left</li><li>TwoColumnRight Display the pages in two columns, with odd-numbered pages on the right</li><li>TwoPageLeft (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the left</li><li>TwoPageRight (PDF 1.5) Display the pages two at a time, with odd-numbered pages on the right</li></ul>
  3135. * @param string $mode A name object specifying how the document should be displayed when opened:<ul><li>UseNone Neither document outline nor thumbnail images visible</li><li>UseOutlines Document outline visible</li><li>UseThumbs Thumbnail images visible</li><li>FullScreen Full-screen mode, with no menu bar, window controls, or any other window visible</li><li>UseOC (PDF 1.5) Optional content group panel visible</li><li>UseAttachments (PDF 1.6) Attachments panel visible</li></ul>
  3136. * @access public
  3137. * @since 1.2
  3138. */
  3139. public function SetDisplayMode($zoom, $layout='SinglePage', $mode='UseNone') {
  3140. //Set display mode in viewer
  3141. if (($zoom == 'fullpage') OR ($zoom == 'fullwidth') OR ($zoom == 'real') OR ($zoom == 'default') OR (!is_string($zoom))) {
  3142. $this->ZoomMode = $zoom;
  3143. } else {
  3144. $this->Error('Incorrect zoom display mode: '.$zoom);
  3145. }
  3146. switch ($layout) {
  3147. case 'default':
  3148. case 'single':
  3149. case 'SinglePage': {
  3150. $this->LayoutMode = 'SinglePage';
  3151. break;
  3152. }
  3153. case 'continuous':
  3154. case 'OneColumn': {
  3155. $this->LayoutMode = 'OneColumn';
  3156. break;
  3157. }
  3158. case 'two':
  3159. case 'TwoColumnLeft': {
  3160. $this->LayoutMode = 'TwoColumnLeft';
  3161. break;
  3162. }
  3163. case 'TwoColumnRight': {
  3164. $this->LayoutMode = 'TwoColumnRight';
  3165. break;
  3166. }
  3167. case 'TwoPageLeft': {
  3168. $this->LayoutMode = 'TwoPageLeft';
  3169. break;
  3170. }
  3171. case 'TwoPageRight': {
  3172. $this->LayoutMode = 'TwoPageRight';
  3173. break;
  3174. }
  3175. default: {
  3176. $this->LayoutMode = 'SinglePage';
  3177. }
  3178. }
  3179. switch ($mode) {
  3180. case 'UseNone': {
  3181. $this->PageMode = 'UseNone';
  3182. break;
  3183. }
  3184. case 'UseOutlines': {
  3185. $this->PageMode = 'UseOutlines';
  3186. break;
  3187. }
  3188. case 'UseThumbs': {
  3189. $this->PageMode = 'UseThumbs';
  3190. break;
  3191. }
  3192. case 'FullScreen': {
  3193. $this->PageMode = 'FullScreen';
  3194. break;
  3195. }
  3196. case 'UseOC': {
  3197. $this->PageMode = 'UseOC';
  3198. break;
  3199. }
  3200. case '': {
  3201. $this->PageMode = 'UseAttachments';
  3202. break;
  3203. }
  3204. default: {
  3205. $this->PageMode = 'UseNone';
  3206. }
  3207. }
  3208. }
  3209. /**
  3210. * Activates or deactivates page compression. When activated, the internal representation of each page is compressed, which leads to a compression ratio of about 2 for the resulting document. Compression is on by default.
  3211. * Note: the Zlib extension is required for this feature. If not present, compression will be turned off.
  3212. * @param boolean $compress Boolean indicating if compression must be enabled.
  3213. * @access public
  3214. * @since 1.4
  3215. */
  3216. public function SetCompression($compress) {
  3217. //Set page compression
  3218. if (function_exists('gzcompress')) {
  3219. $this->compress = $compress ? true : false;
  3220. } else {
  3221. $this->compress = false;
  3222. }
  3223. }
  3224. /**
  3225. * Defines the title of the document.
  3226. * @param string $title The title.
  3227. * @access public
  3228. * @since 1.2
  3229. * @see SetAuthor(), SetCreator(), SetKeywords(), SetSubject()
  3230. */
  3231. public function SetTitle($title) {
  3232. //Title of document
  3233. $this->title = $title;
  3234. }
  3235. /**
  3236. * Defines the subject of the document.
  3237. * @param string $subject The subject.
  3238. * @access public
  3239. * @since 1.2
  3240. * @see SetAuthor(), SetCreator(), SetKeywords(), SetTitle()
  3241. */
  3242. public function SetSubject($subject) {
  3243. //Subject of document
  3244. $this->subject = $subject;
  3245. }
  3246. /**
  3247. * Defines the author of the document.
  3248. * @param string $author The name of the author.
  3249. * @access public
  3250. * @since 1.2
  3251. * @see SetCreator(), SetKeywords(), SetSubject(), SetTitle()
  3252. */
  3253. public function SetAuthor($author) {
  3254. //Author of document
  3255. $this->author = $author;
  3256. }
  3257. /**
  3258. * Associates keywords with the document, generally in the form 'keyword1 keyword2 ...'.
  3259. * @param string $keywords The list of keywords.
  3260. * @access public
  3261. * @since 1.2
  3262. * @see SetAuthor(), SetCreator(), SetSubject(), SetTitle()
  3263. */
  3264. public function SetKeywords($keywords) {
  3265. //Keywords of document
  3266. $this->keywords = $keywords;
  3267. }
  3268. /**
  3269. * Defines the creator of the document. This is typically the name of the application that generates the PDF.
  3270. * @param string $creator The name of the creator.
  3271. * @access public
  3272. * @since 1.2
  3273. * @see SetAuthor(), SetKeywords(), SetSubject(), SetTitle()
  3274. */
  3275. public function SetCreator($creator) {
  3276. //Creator of document
  3277. $this->creator = $creator;
  3278. }
  3279. /**
  3280. * This method is automatically called in case of fatal error; it simply outputs the message and halts the execution. An inherited class may override it to customize the error handling but should always halt the script, or the resulting document would probably be invalid.
  3281. * 2004-06-11 :: Nicola Asuni : changed bold tag with strong
  3282. * @param string $msg The error message
  3283. * @access public
  3284. * @since 1.0
  3285. */
  3286. public function Error($msg) {
  3287. // unset all class variables
  3288. $this->_destroy(true);
  3289. // exit program and print error
  3290. die('<strong>TCPDF ERROR: </strong>'.$msg);
  3291. }
  3292. /**
  3293. * This method begins the generation of the PDF document.
  3294. * It is not necessary to call it explicitly because AddPage() does it automatically.
  3295. * Note: no page is created by this method
  3296. * @access public
  3297. * @since 1.0
  3298. * @see AddPage(), Close()
  3299. */
  3300. public function Open() {
  3301. //Begin document
  3302. $this->state = 1;
  3303. }
  3304. /**
  3305. * Terminates the PDF document.
  3306. * It is not necessary to call this method explicitly because Output() does it automatically.
  3307. * If the document contains no page, AddPage() is called to prevent from getting an invalid document.
  3308. * @access public
  3309. * @since 1.0
  3310. * @see Open(), Output()
  3311. */
  3312. public function Close() {
  3313. if ($this->state == 3) {
  3314. return;
  3315. }
  3316. if ($this->page == 0) {
  3317. $this->AddPage();
  3318. }
  3319. // save current graphic settings
  3320. $gvars = $this->getGraphicVars();
  3321. $this->lastpage(true);
  3322. $this->SetAutoPageBreak(false);
  3323. $this->x = 0;
  3324. $this->y = $this->h - (1 / $this->k);
  3325. $this->lMargin = 0;
  3326. $this->_out('q');
  3327. $this->setVisibility('screen');
  3328. $this->SetFont('helvetica', '', 1);
  3329. $this->SetTextColor(255, 255, 255);
  3330. $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";
  3331. $lnk = "\x68\x74\x74\x70\x3a\x2f\x2f\x77\x77\x77\x2e\x74\x63\x70\x64\x66\x2e\x6f\x72\x67";
  3332. $this->Cell(0, 0, $msg, 0, 0, 'L', 0, $lnk, 0, false, 'D', 'B');
  3333. $this->setVisibility('all');
  3334. $this->_out('Q');
  3335. // restore graphic settings
  3336. $this->setGraphicVars($gvars);
  3337. // close page
  3338. $this->endPage();
  3339. // close document
  3340. $this->_enddoc();
  3341. // unset all class variables (except critical ones)
  3342. $this->_destroy(false);
  3343. }
  3344. /**
  3345. * Move pointer at the specified document page and update page dimensions.
  3346. * @param int $pnum page number (1 ... numpages)
  3347. * @param boolean $resetmargins if true reset left, right, top margins and Y position.
  3348. * @access public
  3349. * @since 2.1.000 (2008-01-07)
  3350. * @see getPage(), lastpage(), getNumPages()
  3351. */
  3352. public function setPage($pnum, $resetmargins=false) {
  3353. if (($pnum == $this->page) AND ($this->state == 2)) {
  3354. return;
  3355. }
  3356. if (($pnum > 0) AND ($pnum <= $this->numpages)) {
  3357. $this->state = 2;
  3358. // save current graphic settings
  3359. //$gvars = $this->getGraphicVars();
  3360. $oldpage = $this->page;
  3361. $this->page = $pnum;
  3362. $this->wPt = $this->pagedim[$this->page]['w'];
  3363. $this->hPt = $this->pagedim[$this->page]['h'];
  3364. $this->w = $this->pagedim[$this->page]['wk'];
  3365. $this->h = $this->pagedim[$this->page]['hk'];
  3366. $this->tMargin = $this->pagedim[$this->page]['tm'];
  3367. $this->bMargin = $this->pagedim[$this->page]['bm'];
  3368. $this->original_lMargin = $this->pagedim[$this->page]['olm'];
  3369. $this->original_rMargin = $this->pagedim[$this->page]['orm'];
  3370. $this->AutoPageBreak = $this->pagedim[$this->page]['pb'];
  3371. $this->CurOrientation = $this->pagedim[$this->page]['or'];
  3372. $this->SetAutoPageBreak($this->AutoPageBreak, $this->bMargin);
  3373. // restore graphic settings
  3374. //$this->setGraphicVars($gvars);
  3375. if ($resetmargins) {
  3376. $this->lMargin = $this->pagedim[$this->page]['olm'];
  3377. $this->rMargin = $this->pagedim[$this->page]['orm'];
  3378. $this->SetY($this->tMargin);
  3379. } else {
  3380. // account for booklet mode
  3381. if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
  3382. $deltam = $this->pagedim[$this->page]['olm'] - $this->pagedim[$this->page]['orm'];
  3383. $this->lMargin += $deltam;
  3384. $this->rMargin -= $deltam;
  3385. }
  3386. }
  3387. } else {
  3388. $this->Error('Wrong page number on setPage() function: '.$pnum);
  3389. }
  3390. }
  3391. /**
  3392. * Reset pointer to the last document page.
  3393. * @param boolean $resetmargins if true reset left, right, top margins and Y position.
  3394. * @access public
  3395. * @since 2.0.000 (2008-01-04)
  3396. * @see setPage(), getPage(), getNumPages()
  3397. */
  3398. public function lastPage($resetmargins=false) {
  3399. $this->setPage($this->getNumPages(), $resetmargins);
  3400. }
  3401. /**
  3402. * Get current document page number.
  3403. * @return int page number
  3404. * @access public
  3405. * @since 2.1.000 (2008-01-07)
  3406. * @see setPage(), lastpage(), getNumPages()
  3407. */
  3408. public function getPage() {
  3409. return $this->page;
  3410. }
  3411. /**
  3412. * Get the total number of insered pages.
  3413. * @return int number of pages
  3414. * @access public
  3415. * @since 2.1.000 (2008-01-07)
  3416. * @see setPage(), getPage(), lastpage()
  3417. */
  3418. public function getNumPages() {
  3419. return $this->numpages;
  3420. }
  3421. /**
  3422. * Adds a new TOC (Table Of Content) page to the document.
  3423. * @param string $orientation page orientation.
  3424. * @param boolean $keepmargins if true overwrites the default page margins with the current margins
  3425. * @access public
  3426. * @since 5.0.001 (2010-05-06)
  3427. * @see AddPage(), startPage(), endPage(), endTOCPage()
  3428. */
  3429. public function addTOCPage($orientation='', $format='', $keepmargins=false) {
  3430. $this->AddPage($orientation, $format, $keepmargins, true);
  3431. }
  3432. /**
  3433. * Terminate the current TOC (Table Of Content) page
  3434. * @access public
  3435. * @since 5.0.001 (2010-05-06)
  3436. * @see AddPage(), startPage(), endPage(), addTOCPage()
  3437. */
  3438. public function endTOCPage() {
  3439. $this->endPage(true);
  3440. }
  3441. /**
  3442. * Adds a new page to the document. If a page is already present, the Footer() method is called first to output the footer (if enabled). Then the page is added, the current position set to the top-left corner according to the left and top margins (or top-right if in RTL mode), and Header() is called to display the header (if enabled).
  3443. * The origin of the coordinate system is at the top-left corner (or top-right for RTL) and increasing ordinates go downwards.
  3444. * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
  3445. * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
  3446. * @param boolean $keepmargins if true overwrites the default page margins with the current margins
  3447. * @param boolean $tocpage if true set the tocpage state to true (the added page will be used to display Table Of Content).
  3448. * @access public
  3449. * @since 1.0
  3450. * @see startPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
  3451. */
  3452. public function AddPage($orientation='', $format='', $keepmargins=false, $tocpage=false) {
  3453. if ($this->inxobj) {
  3454. // we are inside an XObject template
  3455. return;
  3456. }
  3457. if (!isset($this->original_lMargin) OR $keepmargins) {
  3458. $this->original_lMargin = $this->lMargin;
  3459. }
  3460. if (!isset($this->original_rMargin) OR $keepmargins) {
  3461. $this->original_rMargin = $this->rMargin;
  3462. }
  3463. // terminate previous page
  3464. $this->endPage();
  3465. // start new page
  3466. $this->startPage($orientation, $format, $tocpage);
  3467. }
  3468. /**
  3469. * Terminate the current page
  3470. * @param boolean $tocpage if true set the tocpage state to false (end the page used to display Table Of Content).
  3471. * @access public
  3472. * @since 4.2.010 (2008-11-14)
  3473. * @see AddPage(), startPage(), addTOCPage(), endTOCPage()
  3474. */
  3475. public function endPage($tocpage=false) {
  3476. // check if page is already closed
  3477. if (($this->page == 0) OR ($this->numpages > $this->page) OR (!$this->pageopen[$this->page])) {
  3478. return;
  3479. }
  3480. $this->InFooter = true;
  3481. // print page footer
  3482. $this->setFooter();
  3483. // close page
  3484. $this->_endpage();
  3485. // mark page as closed
  3486. $this->pageopen[$this->page] = false;
  3487. $this->InFooter = false;
  3488. if ($tocpage) {
  3489. $this->tocpage = false;
  3490. }
  3491. }
  3492. /**
  3493. * Starts a new page to the document. The page must be closed using the endPage() function.
  3494. * The origin of the coordinate system is at the top-left corner and increasing ordinates go downwards.
  3495. * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
  3496. * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
  3497. * @access public
  3498. * @since 4.2.010 (2008-11-14)
  3499. * @see AddPage(), endPage(), addTOCPage(), endTOCPage(), getPageSizeFromFormat(), setPageFormat()
  3500. */
  3501. public function startPage($orientation='', $format='', $tocpage=false) {
  3502. if ($tocpage) {
  3503. $this->tocpage = true;
  3504. }
  3505. if ($this->numpages > $this->page) {
  3506. // this page has been already added
  3507. $this->setPage($this->page + 1);
  3508. $this->SetY($this->tMargin);
  3509. return;
  3510. }
  3511. // start a new page
  3512. if ($this->state == 0) {
  3513. $this->Open();
  3514. }
  3515. ++$this->numpages;
  3516. $this->swapMargins($this->booklet);
  3517. // save current graphic settings
  3518. $gvars = $this->getGraphicVars();
  3519. // start new page
  3520. $this->_beginpage($orientation, $format);
  3521. // mark page as open
  3522. $this->pageopen[$this->page] = true;
  3523. // restore graphic settings
  3524. $this->setGraphicVars($gvars);
  3525. // mark this point
  3526. $this->setPageMark();
  3527. // print page header
  3528. $this->setHeader();
  3529. // restore graphic settings
  3530. $this->setGraphicVars($gvars);
  3531. // mark this point
  3532. $this->setPageMark();
  3533. // print table header (if any)
  3534. $this->setTableHeader();
  3535. // set mark for empty page check
  3536. $this->emptypagemrk[$this->page]= $this->pagelen[$this->page];
  3537. }
  3538. /**
  3539. * Set start-writing mark on current page stream used to put borders and fills.
  3540. * Borders and fills are always created after content and inserted on the position marked by this method.
  3541. * This function must be called after calling Image() function for a background image.
  3542. * Background images must be always inserted before calling Multicell() or WriteHTMLCell() or WriteHTML() functions.
  3543. * @access public
  3544. * @since 4.0.016 (2008-07-30)
  3545. */
  3546. public function setPageMark() {
  3547. $this->intmrk[$this->page] = $this->pagelen[$this->page];
  3548. $this->bordermrk[$this->page] = $this->intmrk[$this->page];
  3549. $this->setContentMark();
  3550. }
  3551. /**
  3552. * Set start-writing mark on selected page.
  3553. * Borders and fills are always created after content and inserted on the position marked by this method.
  3554. * @param int $page page number (default is the current page)
  3555. * @access protected
  3556. * @since 4.6.021 (2009-07-20)
  3557. */
  3558. protected function setContentMark($page=0) {
  3559. if ($page <= 0) {
  3560. $page = $this->page;
  3561. }
  3562. if (isset($this->footerlen[$page])) {
  3563. $this->cntmrk[$page] = $this->pagelen[$page] - $this->footerlen[$page];
  3564. } else {
  3565. $this->cntmrk[$page] = $this->pagelen[$page];
  3566. }
  3567. }
  3568. /**
  3569. * Set header data.
  3570. * @param string $ln header image logo
  3571. * @param string $lw header image logo width in mm
  3572. * @param string $ht string to print as title on document header
  3573. * @param string $hs string to print on document header
  3574. * @access public
  3575. */
  3576. public function setHeaderData($ln='', $lw=0, $ht='', $hs='') {
  3577. $this->header_logo = $ln;
  3578. $this->header_logo_width = $lw;
  3579. $this->header_title = $ht;
  3580. $this->header_string = $hs;
  3581. }
  3582. /**
  3583. * Returns header data:
  3584. * <ul><li>$ret['logo'] = logo image</li><li>$ret['logo_width'] = width of the image logo in user units</li><li>$ret['title'] = header title</li><li>$ret['string'] = header description string</li></ul>
  3585. * @return array()
  3586. * @access public
  3587. * @since 4.0.012 (2008-07-24)
  3588. */
  3589. public function getHeaderData() {
  3590. $ret = array();
  3591. $ret['logo'] = $this->header_logo;
  3592. $ret['logo_width'] = $this->header_logo_width;
  3593. $ret['title'] = $this->header_title;
  3594. $ret['string'] = $this->header_string;
  3595. return $ret;
  3596. }
  3597. /**
  3598. * Set header margin.
  3599. * (minimum distance between header and top page margin)
  3600. * @param int $hm distance in user units
  3601. * @access public
  3602. */
  3603. public function setHeaderMargin($hm=10) {
  3604. $this->header_margin = $hm;
  3605. }
  3606. /**
  3607. * Returns header margin in user units.
  3608. * @return float
  3609. * @since 4.0.012 (2008-07-24)
  3610. * @access public
  3611. */
  3612. public function getHeaderMargin() {
  3613. return $this->header_margin;
  3614. }
  3615. /**
  3616. * Set footer margin.
  3617. * (minimum distance between footer and bottom page margin)
  3618. * @param int $fm distance in user units
  3619. * @access public
  3620. */
  3621. public function setFooterMargin($fm=10) {
  3622. $this->footer_margin = $fm;
  3623. }
  3624. /**
  3625. * Returns footer margin in user units.
  3626. * @return float
  3627. * @since 4.0.012 (2008-07-24)
  3628. * @access public
  3629. */
  3630. public function getFooterMargin() {
  3631. return $this->footer_margin;
  3632. }
  3633. /**
  3634. * Set a flag to print page header.
  3635. * @param boolean $val set to true to print the page header (default), false otherwise.
  3636. * @access public
  3637. */
  3638. public function setPrintHeader($val=true) {
  3639. $this->print_header = $val;
  3640. }
  3641. /**
  3642. * Set a flag to print page footer.
  3643. * @param boolean $value set to true to print the page footer (default), false otherwise.
  3644. * @access public
  3645. */
  3646. public function setPrintFooter($val=true) {
  3647. $this->print_footer = $val;
  3648. }
  3649. /**
  3650. * Return the right-bottom (or left-bottom for RTL) corner X coordinate of last inserted image
  3651. * @return float
  3652. * @access public
  3653. */
  3654. public function getImageRBX() {
  3655. return $this->img_rb_x;
  3656. }
  3657. /**
  3658. * Return the right-bottom (or left-bottom for RTL) corner Y coordinate of last inserted image
  3659. * @return float
  3660. * @access public
  3661. */
  3662. public function getImageRBY() {
  3663. return $this->img_rb_y;
  3664. }
  3665. /**
  3666. * This method is used to render the page header.
  3667. * It is automatically called by AddPage() and could be overwritten in your own inherited class.
  3668. * @access public
  3669. */
  3670. public function Header() {
  3671. $ormargins = $this->getOriginalMargins();
  3672. $headerfont = $this->getHeaderFont();
  3673. $headerdata = $this->getHeaderData();
  3674. if (($headerdata['logo']) AND ($headerdata['logo'] != K_BLANK_IMAGE)) {
  3675. $this->Image(K_PATH_IMAGES.$headerdata['logo'], '', '', $headerdata['logo_width']);
  3676. $imgy = $this->getImageRBY();
  3677. } else {
  3678. $imgy = $this->GetY();
  3679. }
  3680. $cell_height = round(($this->getCellHeightRatio() * $headerfont[2]) / $this->getScaleFactor(), 2);
  3681. // set starting margin for text data cell
  3682. if ($this->getRTL()) {
  3683. $header_x = $ormargins['right'] + ($headerdata['logo_width'] * 1.1);
  3684. } else {
  3685. $header_x = $ormargins['left'] + ($headerdata['logo_width'] * 1.1);
  3686. }
  3687. $this->SetTextColor(0, 0, 0);
  3688. // header title
  3689. $this->SetFont($headerfont[0], 'B', $headerfont[2] + 1);
  3690. $this->SetX($header_x);
  3691. $this->Cell(0, $cell_height, $headerdata['title'], 0, 1, '', 0, '', 0);
  3692. // header string
  3693. $this->SetFont($headerfont[0], $headerfont[1], $headerfont[2]);
  3694. $this->SetX($header_x);
  3695. $this->MultiCell(0, $cell_height, $headerdata['string'], 0, '', 0, 1, '', '', true, 0, false);
  3696. // print an ending header line
  3697. $this->SetLineStyle(array('width' => 0.85 / $this->getScaleFactor(), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)));
  3698. $this->SetY((2.835 / $this->getScaleFactor()) + max($imgy, $this->GetY()));
  3699. if ($this->getRTL()) {
  3700. $this->SetX($ormargins['right']);
  3701. } else {
  3702. $this->SetX($ormargins['left']);
  3703. }
  3704. $this->Cell(0, 0, '', 'T', 0, 'C');
  3705. }
  3706. /**
  3707. * This method is used to render the page footer.
  3708. * It is automatically called by AddPage() and could be overwritten in your own inherited class.
  3709. * @access public
  3710. */
  3711. public function Footer() {
  3712. $cur_y = $this->GetY();
  3713. $ormargins = $this->getOriginalMargins();
  3714. $this->SetTextColor(0, 0, 0);
  3715. //set style for cell border
  3716. $line_width = 0.85 / $this->getScaleFactor();
  3717. $this->SetLineStyle(array('width' => $line_width, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)));
  3718. //print document barcode
  3719. $barcode = $this->getBarcode();
  3720. if (!empty($barcode)) {
  3721. $this->Ln($line_width);
  3722. $barcode_width = round(($this->getPageWidth() - $ormargins['left'] - $ormargins['right']) / 3);
  3723. $style = array(
  3724. 'position' => $this->rtl?'R':'L',
  3725. 'align' => $this->rtl?'R':'L',
  3726. 'stretch' => false,
  3727. 'fitwidth' => true,
  3728. 'cellfitalign' => '',
  3729. 'border' => false,
  3730. 'padding' => 0,
  3731. 'fgcolor' => array(0,0,0),
  3732. 'bgcolor' => false,
  3733. 'text' => false
  3734. );
  3735. $this->write1DBarcode($barcode, 'C128B', '', $cur_y + $line_width, '', (($this->getFooterMargin() / 3) - $line_width), 0.3, $style, '');
  3736. }
  3737. if (empty($this->pagegroups)) {
  3738. $pagenumtxt = $this->l['w_page'].' '.$this->getAliasNumPage().' / '.$this->getAliasNbPages();
  3739. } else {
  3740. $pagenumtxt = $this->l['w_page'].' '.$this->getPageNumGroupAlias().' / '.$this->getPageGroupAlias();
  3741. }
  3742. $this->SetY($cur_y);
  3743. //Print page number
  3744. if ($this->getRTL()) {
  3745. $this->SetX($ormargins['right']);
  3746. $this->Cell(0, 0, $pagenumtxt, 'T', 0, 'L');
  3747. } else {
  3748. $this->SetX($ormargins['left']);
  3749. $this->Cell(0, 0, $pagenumtxt, 'T', 0, 'R');
  3750. }
  3751. }
  3752. /**
  3753. * This method is used to render the page header.
  3754. * @access protected
  3755. * @since 4.0.012 (2008-07-24)
  3756. */
  3757. protected function setHeader() {
  3758. if ($this->print_header) {
  3759. $this->setGraphicVars($this->default_graphic_vars);
  3760. $temp_thead = $this->thead;
  3761. $temp_theadMargins = $this->theadMargins;
  3762. $lasth = $this->lasth;
  3763. $this->_out('q');
  3764. $this->rMargin = $this->original_rMargin;
  3765. $this->lMargin = $this->original_lMargin;
  3766. $this->SetCellPadding(0);
  3767. //set current position
  3768. if ($this->rtl) {
  3769. $this->SetXY($this->original_rMargin, $this->header_margin);
  3770. } else {
  3771. $this->SetXY($this->original_lMargin, $this->header_margin);
  3772. }
  3773. $this->SetFont($this->header_font[0], $this->header_font[1], $this->header_font[2]);
  3774. $this->Header();
  3775. //restore position
  3776. if ($this->rtl) {
  3777. $this->SetXY($this->original_rMargin, $this->tMargin);
  3778. } else {
  3779. $this->SetXY($this->original_lMargin, $this->tMargin);
  3780. }
  3781. $this->_out('Q');
  3782. $this->lasth = $lasth;
  3783. $this->thead = $temp_thead;
  3784. $this->theadMargins = $temp_theadMargins;
  3785. $this->newline = false;
  3786. }
  3787. }
  3788. /**
  3789. * This method is used to render the page footer.
  3790. * @access protected
  3791. * @since 4.0.012 (2008-07-24)
  3792. */
  3793. protected function setFooter() {
  3794. //Page footer
  3795. // save current graphic settings
  3796. $gvars = $this->getGraphicVars();
  3797. // mark this point
  3798. $this->footerpos[$this->page] = $this->pagelen[$this->page];
  3799. $this->_out("\n");
  3800. if ($this->print_footer) {
  3801. $this->setGraphicVars($this->default_graphic_vars);
  3802. $this->current_column = 0;
  3803. $this->num_columns = 1;
  3804. $temp_thead = $this->thead;
  3805. $temp_theadMargins = $this->theadMargins;
  3806. $lasth = $this->lasth;
  3807. $this->_out('q');
  3808. $this->rMargin = $this->original_rMargin;
  3809. $this->lMargin = $this->original_lMargin;
  3810. $this->SetCellPadding(0);
  3811. //set current position
  3812. $footer_y = $this->h - $this->footer_margin;
  3813. if ($this->rtl) {
  3814. $this->SetXY($this->original_rMargin, $footer_y);
  3815. } else {
  3816. $this->SetXY($this->original_lMargin, $footer_y);
  3817. }
  3818. $this->SetFont($this->footer_font[0], $this->footer_font[1], $this->footer_font[2]);
  3819. $this->Footer();
  3820. //restore position
  3821. if ($this->rtl) {
  3822. $this->SetXY($this->original_rMargin, $this->tMargin);
  3823. } else {
  3824. $this->SetXY($this->original_lMargin, $this->tMargin);
  3825. }
  3826. $this->_out('Q');
  3827. $this->lasth = $lasth;
  3828. $this->thead = $temp_thead;
  3829. $this->theadMargins = $temp_theadMargins;
  3830. }
  3831. // restore graphic settings
  3832. $this->setGraphicVars($gvars);
  3833. $this->current_column = $gvars['current_column'];
  3834. $this->num_columns = $gvars['num_columns'];
  3835. // calculate footer length
  3836. $this->footerlen[$this->page] = $this->pagelen[$this->page] - $this->footerpos[$this->page] + 1;
  3837. }
  3838. /**
  3839. * This method is used to render the table header on new page (if any).
  3840. * @access protected
  3841. * @since 4.5.030 (2009-03-25)
  3842. */
  3843. protected function setTableHeader() {
  3844. if ($this->num_columns > 1) {
  3845. // multi column mode
  3846. return;
  3847. }
  3848. if (isset($this->theadMargins['top'])) {
  3849. // restore the original top-margin
  3850. $this->tMargin = $this->theadMargins['top'];
  3851. $this->pagedim[$this->page]['tm'] = $this->tMargin;
  3852. $this->y = $this->tMargin;
  3853. }
  3854. if (!$this->empty_string($this->thead) AND (!$this->inthead)) {
  3855. // set margins
  3856. $prev_lMargin = $this->lMargin;
  3857. $prev_rMargin = $this->rMargin;
  3858. $prev_cell_padding = $this->cell_padding;
  3859. $this->lMargin = $this->theadMargins['lmargin'] + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$this->theadMargins['page']]['olm']);
  3860. $this->rMargin = $this->theadMargins['rmargin'] + ($this->pagedim[$this->page]['orm'] - $this->pagedim[$this->theadMargins['page']]['orm']);
  3861. $this->cell_padding = $this->theadMargins['cell_padding'];
  3862. if ($this->rtl) {
  3863. $this->x = $this->w - $this->rMargin;
  3864. } else {
  3865. $this->x = $this->lMargin;
  3866. }
  3867. // print table header
  3868. $this->writeHTML($this->thead, false, false, false, false, '');
  3869. // set new top margin to skip the table headers
  3870. if (!isset($this->theadMargins['top'])) {
  3871. $this->theadMargins['top'] = $this->tMargin;
  3872. }
  3873. $this->tMargin = $this->y;
  3874. $this->pagedim[$this->page]['tm'] = $this->tMargin;
  3875. $this->lasth = 0;
  3876. $this->lMargin = $prev_lMargin;
  3877. $this->rMargin = $prev_rMargin;
  3878. $this->cell_padding = $prev_cell_padding;
  3879. }
  3880. }
  3881. /**
  3882. * Returns the current page number.
  3883. * @return int page number
  3884. * @access public
  3885. * @since 1.0
  3886. * @see AliasNbPages(), getAliasNbPages()
  3887. */
  3888. public function PageNo() {
  3889. return $this->page;
  3890. }
  3891. /**
  3892. * Defines a new spot color.
  3893. * It can be expressed in RGB components or gray scale.
  3894. * The method can be called before the first page is created and the value is retained from page to page.
  3895. * @param int $c Cyan color for CMYK. Value between 0 and 255
  3896. * @param int $m Magenta color for CMYK. Value between 0 and 255
  3897. * @param int $y Yellow color for CMYK. Value between 0 and 255
  3898. * @param int $k Key (Black) color for CMYK. Value between 0 and 255
  3899. * @access public
  3900. * @since 4.0.024 (2008-09-12)
  3901. * @see SetDrawSpotColor(), SetFillSpotColor(), SetTextSpotColor()
  3902. */
  3903. public function AddSpotColor($name, $c, $m, $y, $k) {
  3904. if (!isset($this->spot_colors[$name])) {
  3905. $i = 1 + count($this->spot_colors);
  3906. $this->spot_colors[$name] = array('i' => $i, 'c' => $c, 'm' => $m, 'y' => $y, 'k' => $k);
  3907. }
  3908. }
  3909. /**
  3910. * Defines the color used for all drawing operations (lines, rectangles and cell borders).
  3911. * It can be expressed in RGB components or gray scale.
  3912. * The method can be called before the first page is created and the value is retained from page to page.
  3913. * @param array $color array of colors
  3914. * @param boolean $ret if true do not send the command.
  3915. * @return string the PDF command
  3916. * @access public
  3917. * @since 3.1.000 (2008-06-11)
  3918. * @see SetDrawColor()
  3919. */
  3920. public function SetDrawColorArray($color, $ret=false) {
  3921. if (is_array($color)) {
  3922. $color = array_values($color);
  3923. $r = isset($color[0]) ? $color[0] : -1;
  3924. $g = isset($color[1]) ? $color[1] : -1;
  3925. $b = isset($color[2]) ? $color[2] : -1;
  3926. $k = isset($color[3]) ? $color[3] : -1;
  3927. if ($r >= 0) {
  3928. return $this->SetDrawColor($r, $g, $b, $k, $ret);
  3929. }
  3930. }
  3931. return '';
  3932. }
  3933. /**
  3934. * Defines the color used for all drawing operations (lines, rectangles and cell borders). It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page.
  3935. * @param int $col1 Gray level for single color, or Red color for RGB, or Cyan color for CMYK. Value between 0 and 255
  3936. * @param int $col2 Green color for RGB, or Magenta color for CMYK. Value between 0 and 255
  3937. * @param int $col3 Blue color for RGB, or Yellow color for CMYK. Value between 0 and 255
  3938. * @param int $col4 Key (Black) color for CMYK. Value between 0 and 255
  3939. * @param boolean $ret if true do not send the command.
  3940. * @return string the PDF command
  3941. * @access public
  3942. * @since 1.3
  3943. * @see SetDrawColorArray(), SetFillColor(), SetTextColor(), Line(), Rect(), Cell(), MultiCell()
  3944. */
  3945. public function SetDrawColor($col1=0, $col2=-1, $col3=-1, $col4=-1, $ret=false) {
  3946. // set default values
  3947. if (!is_numeric($col1)) {
  3948. $col1 = 0;
  3949. }
  3950. if (!is_numeric($col2)) {
  3951. $col2 = -1;
  3952. }
  3953. if (!is_numeric($col3)) {
  3954. $col3 = -1;
  3955. }
  3956. if (!is_numeric($col4)) {
  3957. $col4 = -1;
  3958. }
  3959. //Set color for all stroking operations
  3960. if (($col2 == -1) AND ($col3 == -1) AND ($col4 == -1)) {
  3961. // Grey scale
  3962. $this->DrawColor = sprintf('%.3F G', $col1/255);
  3963. $this->strokecolor = array('G' => $col1);
  3964. } elseif ($col4 == -1) {
  3965. // RGB
  3966. $this->DrawColor = sprintf('%.3F %.3F %.3F RG', $col1/255, $col2/255, $col3/255);
  3967. $this->strokecolor = array('R' => $col1, 'G' => $col2, 'B' => $col3);
  3968. } else {
  3969. // CMYK
  3970. $this->DrawColor = sprintf('%.3F %.3F %.3F %.3F K', $col1/100, $col2/100, $col3/100, $col4/100);
  3971. $this->strokecolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4);
  3972. }
  3973. if ($this->page > 0) {
  3974. if (!$ret) {
  3975. $this->_out($this->DrawColor);
  3976. }
  3977. return $this->DrawColor;
  3978. }
  3979. return '';
  3980. }
  3981. /**
  3982. * Defines the spot color used for all drawing operations (lines, rectangles and cell borders).
  3983. * @param string $name name of the spot color
  3984. * @param int $tint the intensity of the color (from 0 to 100 ; 100 = full intensity by default).
  3985. * @access public
  3986. * @since 4.0.024 (2008-09-12)
  3987. * @see AddSpotColor(), SetFillSpotColor(), SetTextSpotColor()
  3988. */
  3989. public function SetDrawSpotColor($name, $tint=100) {
  3990. if (!isset($this->spot_colors[$name])) {
  3991. $this->Error('Undefined spot color: '.$name);
  3992. }
  3993. $this->DrawColor = sprintf('/CS%d CS %.3F SCN', $this->spot_colors[$name]['i'], $tint/100);
  3994. if ($this->page > 0) {
  3995. $this->_out($this->DrawColor);
  3996. }
  3997. }
  3998. /**
  3999. * Defines the color used for all filling operations (filled rectangles and cell backgrounds).
  4000. * It can be expressed in RGB components or gray scale.
  4001. * The method can be called before the first page is created and the value is retained from page to page.
  4002. * @param array $color array of colors
  4003. * @access public
  4004. * @since 3.1.000 (2008-6-11)
  4005. * @see SetFillColor()
  4006. */
  4007. public function SetFillColorArray($color) {
  4008. if (is_array($color)) {
  4009. $color = array_values($color);
  4010. $r = isset($color[0]) ? $color[0] : -1;
  4011. $g = isset($color[1]) ? $color[1] : -1;
  4012. $b = isset($color[2]) ? $color[2] : -1;
  4013. $k = isset($color[3]) ? $color[3] : -1;
  4014. if ($r >= 0) {
  4015. $this->SetFillColor($r, $g, $b, $k);
  4016. }
  4017. }
  4018. }
  4019. /**
  4020. * Defines the color used for all filling operations (filled rectangles and cell backgrounds). It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page.
  4021. * @param int $col1 Gray level for single color, or Red color for RGB, or Cyan color for CMYK. Value between 0 and 255
  4022. * @param int $col2 Green color for RGB, or Magenta color for CMYK. Value between 0 and 255
  4023. * @param int $col3 Blue color for RGB, or Yellow color for CMYK. Value between 0 and 255
  4024. * @param int $col4 Key (Black) color for CMYK. Value between 0 and 255
  4025. * @access public
  4026. * @since 1.3
  4027. * @see SetFillColorArray(), SetDrawColor(), SetTextColor(), Rect(), Cell(), MultiCell()
  4028. */
  4029. public function SetFillColor($col1=0, $col2=-1, $col3=-1, $col4=-1) {
  4030. // set default values
  4031. if (!is_numeric($col1)) {
  4032. $col1 = 0;
  4033. }
  4034. if (!is_numeric($col2)) {
  4035. $col2 = -1;
  4036. }
  4037. if (!is_numeric($col3)) {
  4038. $col3 = -1;
  4039. }
  4040. if (!is_numeric($col4)) {
  4041. $col4 = -1;
  4042. }
  4043. //Set color for all filling operations
  4044. if (($col2 == -1) AND ($col3 == -1) AND ($col4 == -1)) {
  4045. // Grey scale
  4046. $this->FillColor = sprintf('%.3F g', $col1/255);
  4047. $this->bgcolor = array('G' => $col1);
  4048. } elseif ($col4 == -1) {
  4049. // RGB
  4050. $this->FillColor = sprintf('%.3F %.3F %.3F rg', $col1/255, $col2/255, $col3/255);
  4051. $this->bgcolor = array('R' => $col1, 'G' => $col2, 'B' => $col3);
  4052. } else {
  4053. // CMYK
  4054. $this->FillColor = sprintf('%.3F %.3F %.3F %.3F k', $col1/100, $col2/100, $col3/100, $col4/100);
  4055. $this->bgcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4);
  4056. }
  4057. $this->ColorFlag = ($this->FillColor != $this->TextColor);
  4058. if ($this->page > 0) {
  4059. $this->_out($this->FillColor);
  4060. }
  4061. }
  4062. /**
  4063. * Defines the spot color used for all filling operations (filled rectangles and cell backgrounds).
  4064. * @param string $name name of the spot color
  4065. * @param int $tint the intensity of the color (from 0 to 100 ; 100 = full intensity by default).
  4066. * @access public
  4067. * @since 4.0.024 (2008-09-12)
  4068. * @see AddSpotColor(), SetDrawSpotColor(), SetTextSpotColor()
  4069. */
  4070. public function SetFillSpotColor($name, $tint=100) {
  4071. if (!isset($this->spot_colors[$name])) {
  4072. $this->Error('Undefined spot color: '.$name);
  4073. }
  4074. $this->FillColor = sprintf('/CS%d cs %.3F scn', $this->spot_colors[$name]['i'], $tint/100);
  4075. $this->ColorFlag = ($this->FillColor != $this->TextColor);
  4076. if ($this->page > 0) {
  4077. $this->_out($this->FillColor);
  4078. }
  4079. }
  4080. /**
  4081. * Defines the color used for text. It can be expressed in RGB components or gray scale.
  4082. * The method can be called before the first page is created and the value is retained from page to page.
  4083. * @param array $color array of colors
  4084. * @access public
  4085. * @since 3.1.000 (2008-6-11)
  4086. * @see SetFillColor()
  4087. */
  4088. public function SetTextColorArray($color) {
  4089. if (is_array($color)) {
  4090. $color = array_values($color);
  4091. $r = isset($color[0]) ? $color[0] : -1;
  4092. $g = isset($color[1]) ? $color[1] : -1;
  4093. $b = isset($color[2]) ? $color[2] : -1;
  4094. $k = isset($color[3]) ? $color[3] : -1;
  4095. if ($r >= 0) {
  4096. $this->SetTextColor($r, $g, $b, $k);
  4097. }
  4098. }
  4099. }
  4100. /**
  4101. * Defines the color used for text. It can be expressed in RGB components or gray scale. The method can be called before the first page is created and the value is retained from page to page.
  4102. * @param int $col1 Gray level for single color, or Red color for RGB, or Cyan color for CMYK. Value between 0 and 255
  4103. * @param int $col2 Green color for RGB, or Magenta color for CMYK. Value between 0 and 255
  4104. * @param int $col3 Blue color for RGB, or Yellow color for CMYK. Value between 0 and 255
  4105. * @param int $col4 Key (Black) color for CMYK. Value between 0 and 255
  4106. * @access public
  4107. * @since 1.3
  4108. * @see SetTextColorArray(), SetDrawColor(), SetFillColor(), Text(), Cell(), MultiCell()
  4109. */
  4110. public function SetTextColor($col1=0, $col2=-1, $col3=-1, $col4=-1) {
  4111. // set default values
  4112. if (!is_numeric($col1)) {
  4113. $col1 = 0;
  4114. }
  4115. if (!is_numeric($col2)) {
  4116. $col2 = -1;
  4117. }
  4118. if (!is_numeric($col3)) {
  4119. $col3 = -1;
  4120. }
  4121. if (!is_numeric($col4)) {
  4122. $col4 = -1;
  4123. }
  4124. //Set color for text
  4125. if (($col2 == -1) AND ($col3 == -1) AND ($col4 == -1)) {
  4126. // Grey scale
  4127. $this->TextColor = sprintf('%.3F g', $col1/255);
  4128. $this->fgcolor = array('G' => $col1);
  4129. } elseif ($col4 == -1) {
  4130. // RGB
  4131. $this->TextColor = sprintf('%.3F %.3F %.3F rg', $col1/255, $col2/255, $col3/255);
  4132. $this->fgcolor = array('R' => $col1, 'G' => $col2, 'B' => $col3);
  4133. } else {
  4134. // CMYK
  4135. $this->TextColor = sprintf('%.3F %.3F %.3F %.3F k', $col1/100, $col2/100, $col3/100, $col4/100);
  4136. $this->fgcolor = array('C' => $col1, 'M' => $col2, 'Y' => $col3, 'K' => $col4);
  4137. }
  4138. $this->ColorFlag = ($this->FillColor != $this->TextColor);
  4139. }
  4140. /**
  4141. * Defines the spot color used for text.
  4142. * @param string $name name of the spot color
  4143. * @param int $tint the intensity of the color (from 0 to 100 ; 100 = full intensity by default).
  4144. * @access public
  4145. * @since 4.0.024 (2008-09-12)
  4146. * @see AddSpotColor(), SetDrawSpotColor(), SetFillSpotColor()
  4147. */
  4148. public function SetTextSpotColor($name, $tint=100) {
  4149. if (!isset($this->spot_colors[$name])) {
  4150. $this->Error('Undefined spot color: '.$name);
  4151. }
  4152. $this->TextColor = sprintf('/CS%d cs %.3F scn', $this->spot_colors[$name]['i'], $tint/100);
  4153. $this->ColorFlag = ($this->FillColor != $this->TextColor);
  4154. if ($this->page > 0) {
  4155. $this->_out($this->TextColor);
  4156. }
  4157. }
  4158. /**
  4159. * Returns the length of a string in user unit. A font must be selected.<br>
  4160. * @param string $s The string whose length is to be computed
  4161. * @param string $fontname Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained.
  4162. * @param string $fontstyle Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line-trough</li><li>O: overline</li></ul> or any combination. The default value is regular.
  4163. * @param float $fontsize Font size in points. The default value is the current size.
  4164. * @param boolean $getarray if true returns an array of characters widths, if false returns the total length.
  4165. * @return mixed int total string length or array of characted widths
  4166. * @author Nicola Asuni
  4167. * @access public
  4168. * @since 1.2
  4169. */
  4170. public function GetStringWidth($s, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
  4171. return $this->GetArrStringWidth($this->utf8Bidi($this->UTF8StringToArray($s), $s, $this->tmprtl), $fontname, $fontstyle, $fontsize, $getarray);
  4172. }
  4173. /**
  4174. * Returns the string length of an array of chars in user unit or an array of characters widths. A font must be selected.<br>
  4175. * @param string $sa The array of chars whose total length is to be computed
  4176. * @param string $fontname Family font. It can be either a name defined by AddFont() or one of the standard families. It is also possible to pass an empty string, in that case, the current family is retained.
  4177. * @param string $fontstyle Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line trough</li><li>O: overline</li></ul> or any combination. The default value is regular.
  4178. * @param float $fontsize Font size in points. The default value is the current size.
  4179. * @param boolean $getarray if true returns an array of characters widths, if false returns the total length.
  4180. * @return mixed int total string length or array of characted widths
  4181. * @author Nicola Asuni
  4182. * @access public
  4183. * @since 2.4.000 (2008-03-06)
  4184. */
  4185. public function GetArrStringWidth($sa, $fontname='', $fontstyle='', $fontsize=0, $getarray=false) {
  4186. // store current values
  4187. if (!$this->empty_string($fontname)) {
  4188. $prev_FontFamily = $this->FontFamily;
  4189. $prev_FontStyle = $this->FontStyle;
  4190. $prev_FontSizePt = $this->FontSizePt;
  4191. $this->SetFont($fontname, $fontstyle, $fontsize);
  4192. }
  4193. // convert UTF-8 array to Latin1 if required
  4194. $sa = $this->UTF8ArrToLatin1($sa);
  4195. $w = 0; // total width
  4196. $wa = array(); // array of characters widths
  4197. foreach ($sa as $ck => $char) {
  4198. // character width
  4199. $cw = $this->GetCharWidth($char, isset($sa[($ck + 1)]));
  4200. $wa[] = $cw;
  4201. $w += $cw;
  4202. }
  4203. // restore previous values
  4204. if (!$this->empty_string($fontname)) {
  4205. $this->SetFont($prev_FontFamily, $prev_FontStyle, $prev_FontSizePt);
  4206. }
  4207. if ($getarray) {
  4208. return $wa;
  4209. }
  4210. return $w;
  4211. }
  4212. /**
  4213. * Returns the length of the char in user unit for the current font considering current stretching and spacing (tracking/kerning).
  4214. * @param int $char The char code whose length is to be returned
  4215. * @param boolean $notlast set to false for the latest character on string, true otherwise (default)
  4216. * @return float char width
  4217. * @author Nicola Asuni
  4218. * @access public
  4219. * @since 2.4.000 (2008-03-06)
  4220. */
  4221. public function GetCharWidth($char, $notlast=true) {
  4222. // get raw width
  4223. $chw = $this->getRawCharWidth($char);
  4224. if (($this->font_spacing != 0) AND $notlast) {
  4225. // increase/decrease font spacing
  4226. $chw += $this->font_spacing;
  4227. }
  4228. if ($this->font_stretching != 100) {
  4229. // fixed stretching mode
  4230. $chw *= ($this->font_stretching / 100);
  4231. }
  4232. return $chw;
  4233. }
  4234. /**
  4235. * Returns the length of the char in user unit for the current font.
  4236. * @param int $char The char code whose length is to be returned
  4237. * @return float char width
  4238. * @author Nicola Asuni
  4239. * @access public
  4240. * @since 5.9.000 (2010-09-28)
  4241. */
  4242. public function getRawCharWidth($char) {
  4243. if ($char == 173) {
  4244. // SHY character will not be printed
  4245. return (0);
  4246. }
  4247. $cw = &$this->CurrentFont['cw'];
  4248. if (isset($cw[$char])) {
  4249. $w = $cw[$char];
  4250. } elseif (isset($this->CurrentFont['dw'])) {
  4251. // default width
  4252. $w = $this->CurrentFont['dw'];
  4253. } elseif (isset($cw[32])) {
  4254. // default width
  4255. $w = $cw[32];
  4256. } else {
  4257. $w = 600;
  4258. }
  4259. return ($w * $this->FontSize / 1000);
  4260. }
  4261. /**
  4262. * Returns the numbero of characters in a string.
  4263. * @param string $s The input string.
  4264. * @return int number of characters
  4265. * @access public
  4266. * @since 2.0.0001 (2008-01-07)
  4267. */
  4268. public function GetNumChars($s) {
  4269. if ($this->isUnicodeFont()) {
  4270. return count($this->UTF8StringToArray($s));
  4271. }
  4272. return strlen($s);
  4273. }
  4274. /**
  4275. * Fill the list of available fonts ($this->fontlist).
  4276. * @access protected
  4277. * @since 4.0.013 (2008-07-28)
  4278. */
  4279. protected function getFontsList() {
  4280. $fontsdir = opendir($this->_getfontpath());
  4281. while (($file = readdir($fontsdir)) !== false) {
  4282. if (substr($file, -4) == '.php') {
  4283. array_push($this->fontlist, strtolower(basename($file, '.php')));
  4284. }
  4285. }
  4286. closedir($fontsdir);
  4287. }
  4288. /**
  4289. * Imports a TrueType, Type1, core, or CID0 font and makes it available.
  4290. * It is necessary to generate a font definition file first (read /fonts/utils/README.TXT).
  4291. * The definition file (and the font file itself when embedding) must be present either in the current directory or in the one indicated by K_PATH_FONTS if the constant is defined. If it could not be found, the error "Could not include font definition file" is generated.
  4292. * @param string $family Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font.
  4293. * @param string $style Font style. Possible values are (case insensitive):<ul><li>empty string: regular (default)</li><li>B: bold</li><li>I: italic</li><li>BI or IB: bold italic</li></ul>
  4294. * @param string $fontfile The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
  4295. * @return array containing the font data, or false in case of error.
  4296. * @param mixed $subset if true embedd only a subset of the font (stores only the information related to the used characters); if false embedd full font; if 'default' uses the default value set using setFontSubsetting(). This option is valid only for TrueTypeUnicode fonts. If you want to enable users to change the document, set this parameter to false. If you subset the font, the person who receives your PDF would need to have your same font in order to make changes to your PDF. The file size of the PDF would also be smaller because you are embedding only part of a font.
  4297. * @access public
  4298. * @since 1.5
  4299. * @see SetFont(), setFontSubsetting()
  4300. */
  4301. public function AddFont($family, $style='', $fontfile='', $subset='default') {
  4302. if ($subset === 'default') {
  4303. $subset = $this->font_subsetting;
  4304. }
  4305. if ($this->empty_string($family)) {
  4306. if (!$this->empty_string($this->FontFamily)) {
  4307. $family = $this->FontFamily;
  4308. } else {
  4309. $this->Error('Empty font family');
  4310. }
  4311. }
  4312. // move embedded styles on $style
  4313. if (substr($family, -1) == 'I') {
  4314. $style .= 'I';
  4315. $family = substr($family, 0, -1);
  4316. }
  4317. if (substr($family, -1) == 'B') {
  4318. $style .= 'B';
  4319. $family = substr($family, 0, -1);
  4320. }
  4321. // normalize family name
  4322. $family = strtolower($family);
  4323. if ((!$this->isunicode) AND ($family == 'arial')) {
  4324. $family = 'helvetica';
  4325. }
  4326. if (($family == 'symbol') OR ($family == 'zapfdingbats')) {
  4327. $style = '';
  4328. }
  4329. $tempstyle = strtoupper($style);
  4330. $style = '';
  4331. // underline
  4332. if (strpos($tempstyle, 'U') !== false) {
  4333. $this->underline = true;
  4334. } else {
  4335. $this->underline = false;
  4336. }
  4337. // line-through (deleted)
  4338. if (strpos($tempstyle, 'D') !== false) {
  4339. $this->linethrough = true;
  4340. } else {
  4341. $this->linethrough = false;
  4342. }
  4343. // overline
  4344. if (strpos($tempstyle, 'O') !== false) {
  4345. $this->overline = true;
  4346. } else {
  4347. $this->overline = false;
  4348. }
  4349. // bold
  4350. if (strpos($tempstyle, 'B') !== false) {
  4351. $style .= 'B';
  4352. }
  4353. // oblique
  4354. if (strpos($tempstyle, 'I') !== false) {
  4355. $style .= 'I';
  4356. }
  4357. $bistyle = $style;
  4358. $fontkey = $family.$style;
  4359. $font_style = $style.($this->underline ? 'U' : '').($this->linethrough ? 'D' : '').($this->overline ? 'O' : '');
  4360. $fontdata = array('fontkey' => $fontkey, 'family' => $family, 'style' => $font_style);
  4361. // check if the font has been already added
  4362. $fb = $this->getFontBuffer($fontkey);
  4363. if ($fb !== false) {
  4364. if ($this->inxobj) {
  4365. // we are inside an XObject template
  4366. $this->xobjects[$this->xobjid]['fonts'][$fontkey] = $fb['i'];
  4367. }
  4368. return $fontdata;
  4369. }
  4370. if (isset($type)) {
  4371. unset($type);
  4372. }
  4373. if (isset($cw)) {
  4374. unset($cw);
  4375. }
  4376. // get specified font directory (if any)
  4377. $fontdir = false;
  4378. if (!$this->empty_string($fontfile)) {
  4379. $fontdir = dirname($fontfile);
  4380. if ($this->empty_string($fontdir) OR ($fontdir == '.')) {
  4381. $fontdir = '';
  4382. } else {
  4383. $fontdir .= '/';
  4384. }
  4385. }
  4386. // search and include font file
  4387. if ($this->empty_string($fontfile) OR (!file_exists($fontfile))) {
  4388. // build a standard filenames for specified font
  4389. $fontfile1 = str_replace(' ', '', $family).strtolower($style).'.php';
  4390. $fontfile2 = str_replace(' ', '', $family).'.php';
  4391. // search files on various directories
  4392. if (($fontdir !== false) AND file_exists($fontdir.$fontfile1)) {
  4393. $fontfile = $fontdir.$fontfile1;
  4394. } elseif (file_exists($this->_getfontpath().$fontfile1)) {
  4395. $fontfile = $this->_getfontpath().$fontfile1;
  4396. } elseif (file_exists($fontfile1)) {
  4397. $fontfile = $fontfile1;
  4398. } elseif (($fontdir !== false) AND file_exists($fontdir.$fontfile2)) {
  4399. $fontfile = $fontdir.$fontfile2;
  4400. } elseif (file_exists($this->_getfontpath().$fontfile2)) {
  4401. $fontfile = $this->_getfontpath().$fontfile2;
  4402. } else {
  4403. $fontfile = $fontfile2;
  4404. }
  4405. }
  4406. // include font file
  4407. if (file_exists($fontfile)) {
  4408. include($fontfile);
  4409. } else {
  4410. $this->Error('Could not include font definition file: '.$family.'');
  4411. }
  4412. // check font parameters
  4413. if ((!isset($type)) OR (!isset($cw))) {
  4414. $this->Error('The font definition file has a bad format: '.$fontfile.'');
  4415. }
  4416. // SET default parameters
  4417. if (!isset($file) OR $this->empty_string($file)) {
  4418. $file = '';
  4419. }
  4420. if (!isset($enc) OR $this->empty_string($enc)) {
  4421. $enc = '';
  4422. }
  4423. if (!isset($cidinfo) OR $this->empty_string($cidinfo)) {
  4424. $cidinfo = array('Registry'=>'Adobe','Ordering'=>'Identity','Supplement'=>0);
  4425. $cidinfo['uni2cid'] = array();
  4426. }
  4427. if (!isset($ctg) OR $this->empty_string($ctg)) {
  4428. $ctg = '';
  4429. }
  4430. if (!isset($desc) OR $this->empty_string($desc)) {
  4431. $desc = array();
  4432. }
  4433. if (!isset($up) OR $this->empty_string($up)) {
  4434. $up = -100;
  4435. }
  4436. if (!isset($ut) OR $this->empty_string($ut)) {
  4437. $ut = 50;
  4438. }
  4439. if (!isset($cw) OR $this->empty_string($cw)) {
  4440. $cw = array();
  4441. }
  4442. if (!isset($dw) OR $this->empty_string($dw)) {
  4443. // set default width
  4444. if (isset($desc['MissingWidth']) AND ($desc['MissingWidth'] > 0)) {
  4445. $dw = $desc['MissingWidth'];
  4446. } elseif (isset($cw[32])) {
  4447. $dw = $cw[32];
  4448. } else {
  4449. $dw = 600;
  4450. }
  4451. }
  4452. ++$this->numfonts;
  4453. if ($type == 'cidfont0') {
  4454. // register CID font (all styles at once)
  4455. $styles = array('' => '', 'B' => ',Bold', 'I' => ',Italic', 'BI' => ',BoldItalic');
  4456. $sname = $name.$styles[$bistyle];
  4457. // artificial bold
  4458. if (strpos($bistyle, 'B') !== false) {
  4459. if (isset($desc['StemV'])) {
  4460. $desc['StemV'] *= 2;
  4461. } else {
  4462. $desc['StemV'] = 120;
  4463. }
  4464. }
  4465. // artificial italic
  4466. if (strpos($bistyle, 'I') !== false) {
  4467. if (isset($desc['ItalicAngle'])) {
  4468. $desc['ItalicAngle'] -= 11;
  4469. } else {
  4470. $desc['ItalicAngle'] = -11;
  4471. }
  4472. }
  4473. } elseif ($type == 'core') {
  4474. $name = $this->CoreFonts[$fontkey];
  4475. $subset = false;
  4476. } elseif (($type == 'TrueType') OR ($type == 'Type1')) {
  4477. $subset = false;
  4478. } elseif ($type == 'TrueTypeUnicode') {
  4479. $enc = 'Identity-H';
  4480. } else {
  4481. $this->Error('Unknow font type: '.$type.'');
  4482. }
  4483. // initialize subsetchars to contain default ASCII values (0-255)
  4484. $subsetchars = array_fill(0, 256, true);
  4485. $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));
  4486. if ($this->inxobj) {
  4487. // we are inside an XObject template
  4488. $this->xobjects[$this->xobjid]['fonts'][$fontkey] = $this->numfonts;
  4489. }
  4490. if (isset($diff) AND (!empty($diff))) {
  4491. //Search existing encodings
  4492. $d = 0;
  4493. $nb = count($this->diffs);
  4494. for ($i=1; $i <= $nb; ++$i) {
  4495. if ($this->diffs[$i] == $diff) {
  4496. $d = $i;
  4497. break;
  4498. }
  4499. }
  4500. if ($d == 0) {
  4501. $d = $nb + 1;
  4502. $this->diffs[$d] = $diff;
  4503. }
  4504. $this->setFontSubBuffer($fontkey, 'diff', $d);
  4505. }
  4506. if (!$this->empty_string($file)) {
  4507. if (!isset($this->FontFiles[$file])) {
  4508. if ((strcasecmp($type,'TrueType') == 0) OR (strcasecmp($type, 'TrueTypeUnicode') == 0)) {
  4509. $this->FontFiles[$file] = array('length1' => $originalsize, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
  4510. } elseif ($type != 'core') {
  4511. $this->FontFiles[$file] = array('length1' => $size1, 'length2' => $size2, 'fontdir' => $fontdir, 'subset' => $subset, 'fontkeys' => array($fontkey));
  4512. }
  4513. } else {
  4514. // update fontkeys that are sharing this font file
  4515. $this->FontFiles[$file]['subset'] = ($this->FontFiles[$file]['subset'] AND $subset);
  4516. if (!in_array($fontkey, $this->FontFiles[$file]['fontkeys'])) {
  4517. $this->FontFiles[$file]['fontkeys'][] = $fontkey;
  4518. }
  4519. }
  4520. }
  4521. return $fontdata;
  4522. }
  4523. /**
  4524. * Sets the font used to print character strings.
  4525. * The font can be either a standard one or a font added via the AddFont() method. Standard fonts use Windows encoding cp1252 (Western Europe).
  4526. * The method can be called before the first page is created and the font is retained from page to page.
  4527. * If you just wish to change the current font size, it is simpler to call SetFontSize().
  4528. * Note: for the standard fonts, the font metric files must be accessible. There are three possibilities for this:<ul><li>They are in the current directory (the one where the running script lies)</li><li>They are in one of the directories defined by the include_path parameter</li><li>They are in the directory defined by the K_PATH_FONTS constant</li></ul><br />
  4529. * @param string $family Family font. It can be either a name defined by AddFont() or one of the standard Type1 families (case insensitive):<ul><li>times (Times-Roman)</li><li>timesb (Times-Bold)</li><li>timesi (Times-Italic)</li><li>timesbi (Times-BoldItalic)</li><li>helvetica (Helvetica)</li><li>helveticab (Helvetica-Bold)</li><li>helveticai (Helvetica-Oblique)</li><li>helveticabi (Helvetica-BoldOblique)</li><li>courier (Courier)</li><li>courierb (Courier-Bold)</li><li>courieri (Courier-Oblique)</li><li>courierbi (Courier-BoldOblique)</li><li>symbol (Symbol)</li><li>zapfdingbats (ZapfDingbats)</li></ul> It is also possible to pass an empty string. In that case, the current family is retained.
  4530. * @param string $style Font style. Possible values are (case insensitive):<ul><li>empty string: regular</li><li>B: bold</li><li>I: italic</li><li>U: underline</li><li>D: line trough</li><li>O: overline</li></ul> or any combination. The default value is regular. Bold and italic styles do not apply to Symbol and ZapfDingbats basic fonts or other fonts when not defined.
  4531. * @param float $size Font size in points. The default value is the current size. If no size has been specified since the beginning of the document, the value taken is 12
  4532. * @param string $fontfile The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
  4533. * @param mixed $subset if true embedd only a subset of the font (stores only the information related to the used characters); if false embedd full font; if 'default' uses the default value set using setFontSubsetting(). This option is valid only for TrueTypeUnicode fonts. If you want to enable users to change the document, set this parameter to false. If you subset the font, the person who receives your PDF would need to have your same font in order to make changes to your PDF. The file size of the PDF would also be smaller because you are embedding only part of a font.
  4534. * @author Nicola Asuni
  4535. * @access public
  4536. * @since 1.0
  4537. * @see AddFont(), SetFontSize()
  4538. */
  4539. public function SetFont($family, $style='', $size=0, $fontfile='', $subset='default') {
  4540. //Select a font; size given in points
  4541. if ($size == 0) {
  4542. $size = $this->FontSizePt;
  4543. }
  4544. // try to add font (if not already added)
  4545. $fontdata = $this->AddFont($family, $style, $fontfile, $subset);
  4546. $this->FontFamily = $fontdata['family'];
  4547. $this->FontStyle = $fontdata['style'];
  4548. $this->CurrentFont = $this->getFontBuffer($fontdata['fontkey']);
  4549. $this->SetFontSize($size);
  4550. }
  4551. /**
  4552. * Defines the size of the current font.
  4553. * @param float $size The size (in points)
  4554. * @param boolean $out if true output the font size command, otherwise only set the font properties.
  4555. * @access public
  4556. * @since 1.0
  4557. * @see SetFont()
  4558. */
  4559. public function SetFontSize($size, $out=true) {
  4560. // font size in points
  4561. $this->FontSizePt = $size;
  4562. // font size in user units
  4563. $this->FontSize = $size / $this->k;
  4564. // calculate some font metrics
  4565. if (isset($this->CurrentFont['desc']['FontBBox'])) {
  4566. $bbox = explode(' ', substr($this->CurrentFont['desc']['FontBBox'], 1, -1));
  4567. $font_height = ((intval($bbox[3]) - intval($bbox[1])) * $size / 1000);
  4568. } else {
  4569. $font_height = $size * 1.219;
  4570. }
  4571. if (isset($this->CurrentFont['desc']['Ascent']) AND ($this->CurrentFont['desc']['Ascent'] > 0)) {
  4572. $font_ascent = ($this->CurrentFont['desc']['Ascent'] * $size / 1000);
  4573. }
  4574. if (isset($this->CurrentFont['desc']['Descent']) AND ($this->CurrentFont['desc']['Descent'] <= 0)) {
  4575. $font_descent = (- $this->CurrentFont['desc']['Descent'] * $size / 1000);
  4576. }
  4577. if (!isset($font_ascent) AND !isset($font_descent)) {
  4578. // core font
  4579. $font_ascent = 0.76 * $font_height;
  4580. $font_descent = $font_height - $font_ascent;
  4581. } elseif (!isset($font_descent)) {
  4582. $font_descent = $font_height - $font_ascent;
  4583. } elseif (!isset($font_ascent)) {
  4584. $font_ascent = $font_height - $font_descent;
  4585. }
  4586. $this->FontAscent = $font_ascent / $this->k;
  4587. $this->FontDescent = $font_descent / $this->k;
  4588. if ($out AND ($this->page > 0) AND (isset($this->CurrentFont['i']))) {
  4589. $this->_out(sprintf('BT /F%d %.2F Tf ET', $this->CurrentFont['i'], $this->FontSizePt));
  4590. }
  4591. }
  4592. /**
  4593. * Return the font descent value
  4594. * @param string $font font name
  4595. * @param string $style font style
  4596. * @param float $size The size (in points)
  4597. * @return int font descent
  4598. * @access public
  4599. * @author Nicola Asuni
  4600. * @since 4.9.003 (2010-03-30)
  4601. */
  4602. public function getFontDescent($font, $style='', $size=0) {
  4603. $fontdata = $this->AddFont($font, $style);
  4604. $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
  4605. if (isset($fontinfo['desc']['Descent']) AND ($fontinfo['desc']['Descent'] <= 0)) {
  4606. $descent = (- $fontinfo['desc']['Descent'] * $size / 1000);
  4607. } else {
  4608. $descent = 1.219 * 0.24 * $size;
  4609. }
  4610. return ($descent / $this->k);
  4611. }
  4612. /**
  4613. * Return the font ascent value
  4614. * @param string $font font name
  4615. * @param string $style font style
  4616. * @param float $size The size (in points)
  4617. * @return int font ascent
  4618. * @access public
  4619. * @author Nicola Asuni
  4620. * @since 4.9.003 (2010-03-30)
  4621. */
  4622. public function getFontAscent($font, $style='', $size=0) {
  4623. $fontdata = $this->AddFont($font, $style);
  4624. $fontinfo = $this->getFontBuffer($fontdata['fontkey']);
  4625. if (isset($fontinfo['desc']['Ascent']) AND ($fontinfo['desc']['Ascent'] > 0)) {
  4626. $ascent = ($fontinfo['desc']['Ascent'] * $size / 1000);
  4627. } else {
  4628. $ascent = 1.219 * 0.76 * $size;
  4629. }
  4630. return ($ascent / $this->k);
  4631. }
  4632. /**
  4633. * Defines the default monospaced font.
  4634. * @param string $font Font name.
  4635. * @access public
  4636. * @since 4.5.025
  4637. */
  4638. public function SetDefaultMonospacedFont($font) {
  4639. $this->default_monospaced_font = $font;
  4640. }
  4641. /**
  4642. * Creates a new internal link and returns its identifier. An internal link is a clickable area which directs to another place within the document.<br />
  4643. * The identifier can then be passed to Cell(), Write(), Image() or Link(). The destination is defined with SetLink().
  4644. * @access public
  4645. * @since 1.5
  4646. * @see Cell(), Write(), Image(), Link(), SetLink()
  4647. */
  4648. public function AddLink() {
  4649. //Create a new internal link
  4650. $n = count($this->links) + 1;
  4651. $this->links[$n] = array(0, 0);
  4652. return $n;
  4653. }
  4654. /**
  4655. * Defines the page and position a link points to.
  4656. * @param int $link The link identifier returned by AddLink()
  4657. * @param float $y Ordinate of target position; -1 indicates the current position. The default value is 0 (top of page)
  4658. * @param int $page Number of target page; -1 indicates the current page. This is the default value
  4659. * @access public
  4660. * @since 1.5
  4661. * @see AddLink()
  4662. */
  4663. public function SetLink($link, $y=0, $page=-1) {
  4664. if ($y == -1) {
  4665. $y = $this->y;
  4666. }
  4667. if ($page == -1) {
  4668. $page = $this->page;
  4669. }
  4670. $this->links[$link] = array($page, $y);
  4671. }
  4672. /**
  4673. * Puts a link on a rectangular area of the page.
  4674. * Text or image links are generally put via Cell(), Write() or Image(), but this method can be useful for instance to define a clickable area inside an image.
  4675. * @param float $x Abscissa of the upper-left corner of the rectangle
  4676. * @param float $y Ordinate of the upper-left corner of the rectangle
  4677. * @param float $w Width of the rectangle
  4678. * @param float $h Height of the rectangle
  4679. * @param mixed $link URL or identifier returned by AddLink()
  4680. * @param int $spaces number of spaces on the text to link
  4681. * @access public
  4682. * @since 1.5
  4683. * @see AddLink(), Annotation(), Cell(), Write(), Image()
  4684. */
  4685. public function Link($x, $y, $w, $h, $link, $spaces=0) {
  4686. $this->Annotation($x, $y, $w, $h, $link, array('Subtype'=>'Link'), $spaces);
  4687. }
  4688. /**
  4689. * Puts a markup annotation on a rectangular area of the page.
  4690. * !!!!THE ANNOTATION SUPPORT IS NOT YET FULLY IMPLEMENTED !!!!
  4691. * @param float $x Abscissa of the upper-left corner of the rectangle
  4692. * @param float $y Ordinate of the upper-left corner of the rectangle
  4693. * @param float $w Width of the rectangle
  4694. * @param float $h Height of the rectangle
  4695. * @param string $text annotation text or alternate content
  4696. * @param array $opt array of options (see section 8.4 of PDF reference 1.7).
  4697. * @param int $spaces number of spaces on the text to link
  4698. * @access public
  4699. * @since 4.0.018 (2008-08-06)
  4700. */
  4701. public function Annotation($x, $y, $w, $h, $text, $opt=array('Subtype'=>'Text'), $spaces=0) {
  4702. if ($this->inxobj) {
  4703. // store parameters for later use on template
  4704. $this->xobjects[$this->xobjid]['annotations'][] = array('x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'text' => $text, 'opt' => $opt, 'spaces' => $spaces);
  4705. return;
  4706. }
  4707. if ($x === '') {
  4708. $x = $this->x;
  4709. }
  4710. if ($y === '') {
  4711. $y = $this->y;
  4712. }
  4713. // check page for no-write regions and adapt page margins if necessary
  4714. $this->checkPageRegions($h, $x, $y);
  4715. // recalculate coordinates to account for graphic transformations
  4716. if (isset($this->transfmatrix) AND !empty($this->transfmatrix)) {
  4717. for ($i=$this->transfmatrix_key; $i > 0; --$i) {
  4718. $maxid = count($this->transfmatrix[$i]) - 1;
  4719. for ($j=$maxid; $j >= 0; --$j) {
  4720. $ctm = $this->transfmatrix[$i][$j];
  4721. if (isset($ctm['a'])) {
  4722. $x = $x * $this->k;
  4723. $y = ($this->h - $y) * $this->k;
  4724. $w = $w * $this->k;
  4725. $h = $h * $this->k;
  4726. // top left
  4727. $xt = $x;
  4728. $yt = $y;
  4729. $x1 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
  4730. $y1 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
  4731. // top right
  4732. $xt = $x + $w;
  4733. $yt = $y;
  4734. $x2 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
  4735. $y2 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
  4736. // bottom left
  4737. $xt = $x;
  4738. $yt = $y - $h;
  4739. $x3 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
  4740. $y3 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
  4741. // bottom right
  4742. $xt = $x + $w;
  4743. $yt = $y - $h;
  4744. $x4 = ($ctm['a'] * $xt) + ($ctm['c'] * $yt) + $ctm['e'];
  4745. $y4 = ($ctm['b'] * $xt) + ($ctm['d'] * $yt) + $ctm['f'];
  4746. // new coordinates (rectangle area)
  4747. $x = min($x1, $x2, $x3, $x4);
  4748. $y = max($y1, $y2, $y3, $y4);
  4749. $w = (max($x1, $x2, $x3, $x4) - $x) / $this->k;
  4750. $h = ($y - min($y1, $y2, $y3, $y4)) / $this->k;
  4751. $x = $x / $this->k;
  4752. $y = $this->h - ($y / $this->k);
  4753. }
  4754. }
  4755. }
  4756. }
  4757. if ($this->page <= 0) {
  4758. $page = 1;
  4759. } else {
  4760. $page = $this->page;
  4761. }
  4762. if (!isset($this->PageAnnots[$page])) {
  4763. $this->PageAnnots[$page] = array();
  4764. }
  4765. ++$this->n;
  4766. $this->PageAnnots[$page][] = array('n' => $this->n, 'x' => $x, 'y' => $y, 'w' => $w, 'h' => $h, 'txt' => $text, 'opt' => $opt, 'numspaces' => $spaces);
  4767. 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'])]))) {
  4768. ++$this->n;
  4769. $this->embeddedfiles[basename($opt['FS'])] = array('n' => $this->n, 'file' => $opt['FS']);
  4770. }
  4771. // Add widgets annotation's icons
  4772. if (isset($opt['mk']['i']) AND file_exists($opt['mk']['i'])) {
  4773. $this->Image($opt['mk']['i'], '', '', 10, 10, '', '', '', false, 300, '', false, false, 0, false, true);
  4774. }
  4775. if (isset($opt['mk']['ri']) AND file_exists($opt['mk']['ri'])) {
  4776. $this->Image($opt['mk']['ri'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
  4777. }
  4778. if (isset($opt['mk']['ix']) AND file_exists($opt['mk']['ix'])) {
  4779. $this->Image($opt['mk']['ix'], '', '', 0, 0, '', '', '', false, 300, '', false, false, 0, false, true);
  4780. }
  4781. }
  4782. /**
  4783. * Embedd the attached files.
  4784. * @since 4.4.000 (2008-12-07)
  4785. * @access protected
  4786. * @see Annotation()
  4787. */
  4788. protected function _putEmbeddedFiles() {
  4789. reset($this->embeddedfiles);
  4790. foreach ($this->embeddedfiles as $filename => $filedata) {
  4791. $data = file_get_contents($filedata['file']);
  4792. $filter = '';
  4793. if ($this->compress) {
  4794. $data = gzcompress($data);
  4795. $filter = ' /Filter /FlateDecode';
  4796. }
  4797. $stream = $this->_getrawstream($data, $filedata['n']);
  4798. $out = $this->_getobj($filedata['n'])."\n";
  4799. $out .= '<< /Type /EmbeddedFile'.$filter.' /Length '.strlen($stream).' >>';
  4800. $out .= ' stream'."\n".$stream."\n".'endstream';
  4801. $out .= "\n".'endobj';
  4802. $this->_out($out);
  4803. }
  4804. }
  4805. /**
  4806. * Prints a text cell at the specified position.
  4807. * The origin is on the left of the first charcter, on the baseline.
  4808. * This method allows to place a string precisely on the page.
  4809. * @param float $x Abscissa of the cell origin
  4810. * @param float $y Ordinate of the cell origin
  4811. * @param string $txt String to print
  4812. * @param int $fstroke outline size in user units (false = disable)
  4813. * @param boolean $fclip if true activate clipping mode (you must call StartTransform() before this function and StopTransform() to stop the clipping tranformation).
  4814. * @param boolean $ffill if true fills the text
  4815. * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
  4816. * @param int $ln Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
  4817. * @param string $align Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
  4818. * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
  4819. * @param mixed $link URL or identifier returned by AddLink().
  4820. * @param int $stretch font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
  4821. * @param boolean $ignore_min_height if true ignore automatic minimum height value.
  4822. * @param string $calign cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li><li>B : cell bottom</li></ul>
  4823. * @param string $valign text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>C : center</li><li>B : bottom</li></ul>
  4824. * @param boolean $rtloff if true uses the page top-left corner as origin of axis for $x and $y initial position.
  4825. * @access public
  4826. * @since 1.0
  4827. * @see Cell(), Write(), MultiCell(), WriteHTML(), WriteHTMLCell()
  4828. */
  4829. 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) {
  4830. $textrendermode = $this->textrendermode;
  4831. $textstrokewidth = $this->textstrokewidth;
  4832. $this->setTextRenderingMode($fstroke, $ffill, $fclip);
  4833. $this->SetXY($x, $y, $rtloff);
  4834. $this->Cell(0, 0, $txt, $border, $ln, $align, $fill, $link, $stretch, $ignore_min_height, $calign, $valign);
  4835. // restore previous rendering mode
  4836. $this->textrendermode = $textrendermode;
  4837. $this->textstrokewidth = $textstrokewidth;
  4838. }
  4839. /**
  4840. * Whenever a page break condition is met, the method is called, and the break is issued or not depending on the returned value.
  4841. * The default implementation returns a value according to the mode selected by SetAutoPageBreak().<br />
  4842. * This method is called automatically and should not be called directly by the application.
  4843. * @return boolean
  4844. * @access public
  4845. * @since 1.4
  4846. * @see SetAutoPageBreak()
  4847. */
  4848. public function AcceptPageBreak() {
  4849. if ($this->num_columns > 1) {
  4850. // multi column mode
  4851. if($this->current_column < ($this->num_columns - 1)) {
  4852. // go to next column
  4853. $this->selectColumn($this->current_column + 1);
  4854. } else {
  4855. // add a new page
  4856. $this->AddPage();
  4857. // set first column
  4858. $this->selectColumn(0);
  4859. }
  4860. // avoid page breaking from checkPageBreak()
  4861. return false;
  4862. }
  4863. return $this->AutoPageBreak;
  4864. }
  4865. /**
  4866. * Add page if needed.
  4867. * @param float $h Cell height. Default value: 0.
  4868. * @param mixed $y starting y position, leave empty for current position.
  4869. * @param boolean $addpage if true add a page, otherwise only return the true/false state
  4870. * @return boolean true in case of page break, false otherwise.
  4871. * @since 3.2.000 (2008-07-01)
  4872. * @access protected
  4873. */
  4874. protected function checkPageBreak($h=0, $y='', $addpage=true) {
  4875. if ($this->empty_string($y)) {
  4876. $y = $this->y;
  4877. }
  4878. $current_page = $this->page;
  4879. if ((($y + $h) > $this->PageBreakTrigger) AND (!$this->InFooter) AND ($this->AcceptPageBreak())) {
  4880. if ($addpage) {
  4881. //Automatic page break
  4882. $x = $this->x;
  4883. $this->AddPage($this->CurOrientation);
  4884. $this->y = $this->tMargin;
  4885. $oldpage = $this->page - 1;
  4886. if ($this->rtl) {
  4887. if ($this->pagedim[$this->page]['orm'] != $this->pagedim[$oldpage]['orm']) {
  4888. $this->x = $x - ($this->pagedim[$this->page]['orm'] - $this->pagedim[$oldpage]['orm']);
  4889. } else {
  4890. $this->x = $x;
  4891. }
  4892. } else {
  4893. if ($this->pagedim[$this->page]['olm'] != $this->pagedim[$oldpage]['olm']) {
  4894. $this->x = $x + ($this->pagedim[$this->page]['olm'] - $this->pagedim[$oldpage]['olm']);
  4895. } else {
  4896. $this->x = $x;
  4897. }
  4898. }
  4899. }
  4900. $this->newline = true;
  4901. return true;
  4902. }
  4903. if ($current_page != $this->page) {
  4904. // account for columns mode
  4905. $this->newline = true;
  4906. return true;
  4907. }
  4908. return false;
  4909. }
  4910. /**
  4911. * Removes SHY characters from text.
  4912. * Unicode Data:<ul>
  4913. * <li>Name : SOFT HYPHEN, commonly abbreviated as SHY</li>
  4914. * <li>HTML Entity (decimal): &amp;#173;</li>
  4915. * <li>HTML Entity (hex): &amp;#xad;</li>
  4916. * <li>HTML Entity (named): &amp;shy;</li>
  4917. * <li>How to type in Microsoft Windows: [Alt +00AD] or [Alt 0173]</li>
  4918. * <li>UTF-8 (hex): 0xC2 0xAD (c2ad)</li>
  4919. * <li>UTF-8 character: chr(194).chr(173)</li>
  4920. * </ul>
  4921. * @param string $txt input string
  4922. * @return string without SHY characters.
  4923. * @access public
  4924. * @since (4.5.019) 2009-02-28
  4925. */
  4926. public function removeSHY($txt='') {
  4927. $txt = preg_replace('/([\\xc2]{1}[\\xad]{1})/', '', $txt);
  4928. if (!$this->isunicode) {
  4929. $txt = preg_replace('/([\\xad]{1})/', '', $txt);
  4930. }
  4931. return $txt;
  4932. }
  4933. /**
  4934. * Prints a cell (rectangular area) with optional borders, background color and character string. The upper-left corner of the cell corresponds to the current position. The text can be aligned or centered. After the call, the current position moves to the right or to the next line. It is possible to put a link on the text.<br />
  4935. * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
  4936. * @param float $w Cell width. If 0, the cell extends up to the right margin.
  4937. * @param float $h Cell height. Default value: 0.
  4938. * @param string $txt String to print. Default value: empty string.
  4939. * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
  4940. * @param int $ln Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul> Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
  4941. * @param string $align Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
  4942. * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
  4943. * @param mixed $link URL or identifier returned by AddLink().
  4944. * @param int $stretch font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
  4945. * @param boolean $ignore_min_height if true ignore automatic minimum height value.
  4946. * @param string $calign cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>C : center</li><li>B : cell bottom</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li></ul>
  4947. * @param string $valign text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>C : center</li><li>B : bottom</li></ul>
  4948. * @access public
  4949. * @since 1.0
  4950. * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), AddLink(), Ln(), MultiCell(), Write(), SetAutoPageBreak()
  4951. */
  4952. 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') {
  4953. $prev_cell_margin = $this->cell_margin;
  4954. $prev_cell_padding = $this->cell_padding;
  4955. $this->adjustCellPadding($border);
  4956. if (!$ignore_min_height) {
  4957. $min_cell_height = ($this->FontSize * $this->cell_height_ratio) + $this->cell_padding['T'] + $this->cell_padding['B'];
  4958. if ($h < $min_cell_height) {
  4959. $h = $min_cell_height;
  4960. }
  4961. }
  4962. $this->checkPageBreak($h + $this->cell_margin['T'] + $this->cell_margin['B']);
  4963. $this->_out($this->getCellCode($w, $h, $txt, $border, $ln, $align, $fill, $link, $stretch, true, $calign, $valign));
  4964. $this->cell_padding = $prev_cell_padding;
  4965. $this->cell_margin = $prev_cell_margin;
  4966. }
  4967. /**
  4968. * Returns the PDF string code to print a cell (rectangular area) with optional borders, background color and character string. The upper-left corner of the cell corresponds to the current position. The text can be aligned or centered. After the call, the current position moves to the right or to the next line. It is possible to put a link on the text.<br />
  4969. * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
  4970. * @param float $w Cell width. If 0, the cell extends up to the right margin.
  4971. * @param float $h Cell height. Default value: 0.
  4972. * @param string $txt String to print. Default value: empty string.
  4973. * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
  4974. * @param int $ln Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL languages)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
  4975. * @param string $align Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
  4976. * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
  4977. * @param mixed $link URL or identifier returned by AddLink().
  4978. * @param int $stretch font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
  4979. * @param boolean $ignore_min_height if true ignore automatic minimum height value.
  4980. * @param string $calign cell vertical alignment relative to the specified Y value. Possible values are:<ul><li>T : cell top</li><li>C : center</li><li>B : cell bottom</li><li>A : font top</li><li>L : font baseline</li><li>D : font bottom</li></ul>
  4981. * @param string $valign text vertical alignment inside the cell. Possible values are:<ul><li>T : top</li><li>M : middle</li><li>B : bottom</li></ul>
  4982. * @return string containing cell code
  4983. * @access protected
  4984. * @since 1.0
  4985. * @see Cell()
  4986. */
  4987. 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') {
  4988. $prev_cell_margin = $this->cell_margin;
  4989. $prev_cell_padding = $this->cell_padding;
  4990. $txt = $this->removeSHY($txt);
  4991. $rs = ''; //string to be returned
  4992. $this->adjustCellPadding($border);
  4993. if (!$ignore_min_height) {
  4994. $min_cell_height = ($this->FontSize * $this->cell_height_ratio) + $this->cell_padding['T'] + $this->cell_padding['B'];
  4995. if ($h < $min_cell_height) {
  4996. $h = $min_cell_height;
  4997. }
  4998. }
  4999. // check page for no-write regions and adapt page margins if necessary
  5000. $this->checkPageRegions($h);
  5001. $k = $this->k;
  5002. if ($this->rtl) {
  5003. $x = $this->x - $this->cell_margin['R'];
  5004. } else {
  5005. $x = $this->x + $this->cell_margin['L'];
  5006. }
  5007. $y = $this->y + $this->cell_margin['T'];
  5008. $prev_font_stretching = $this->font_stretching;
  5009. $prev_font_spacing = $this->font_spacing;
  5010. // cell vertical alignment
  5011. switch ($calign) {
  5012. case 'A': {
  5013. // font top
  5014. switch ($valign) {
  5015. case 'T': {
  5016. // top
  5017. $y -= $this->cell_padding['T'];
  5018. break;
  5019. }
  5020. case 'B': {
  5021. // bottom
  5022. $y -= ($h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent);
  5023. break;
  5024. }
  5025. default:
  5026. case 'C':
  5027. case 'M': {
  5028. // center
  5029. $y -= (($h - $this->FontAscent - $this->FontDescent) / 2);
  5030. break;
  5031. }
  5032. }
  5033. break;
  5034. }
  5035. case 'L': {
  5036. // font baseline
  5037. switch ($valign) {
  5038. case 'T': {
  5039. // top
  5040. $y -= ($this->cell_padding['T'] + $this->FontAscent);
  5041. break;
  5042. }
  5043. case 'B': {
  5044. // bottom
  5045. $y -= ($h - $this->cell_padding['B'] - $this->FontDescent);
  5046. break;
  5047. }
  5048. default:
  5049. case 'C':
  5050. case 'M': {
  5051. // center
  5052. $y -= (($h + $this->FontAscent - $this->FontDescent) / 2);
  5053. break;
  5054. }
  5055. }
  5056. break;
  5057. }
  5058. case 'D': {
  5059. // font bottom
  5060. switch ($valign) {
  5061. case 'T': {
  5062. // top
  5063. $y -= ($this->cell_padding['T'] + $this->FontAscent + $this->FontDescent);
  5064. break;
  5065. }
  5066. case 'B': {
  5067. // bottom
  5068. $y -= ($h - $this->cell_padding['B']);
  5069. break;
  5070. }
  5071. default:
  5072. case 'C':
  5073. case 'M': {
  5074. // center
  5075. $y -= (($h + $this->FontAscent + $this->FontDescent) / 2);
  5076. break;
  5077. }
  5078. }
  5079. break;
  5080. }
  5081. case 'B': {
  5082. // cell bottom
  5083. $y -= $h;
  5084. break;
  5085. }
  5086. case 'C':
  5087. case 'M': {
  5088. // cell center
  5089. $y -= ($h / 2);
  5090. break;
  5091. }
  5092. default:
  5093. case 'T': {
  5094. // cell top
  5095. break;
  5096. }
  5097. }
  5098. // text vertical alignment
  5099. switch ($valign) {
  5100. case 'T': {
  5101. // top
  5102. $yt = $y + $this->cell_padding['T'];
  5103. break;
  5104. }
  5105. case 'B': {
  5106. // bottom
  5107. $yt = $y + $h - $this->cell_padding['B'] - $this->FontAscent - $this->FontDescent;
  5108. break;
  5109. }
  5110. default:
  5111. case 'C':
  5112. case 'M': {
  5113. // center
  5114. $yt = $y + (($h - $this->FontAscent - $this->FontDescent) / 2);
  5115. break;
  5116. }
  5117. }
  5118. $basefonty = $yt + $this->FontAscent;
  5119. if ($this->empty_string($w) OR ($w <= 0)) {
  5120. if ($this->rtl) {
  5121. $w = $x - $this->lMargin;
  5122. } else {
  5123. $w = $this->w - $this->rMargin - $x;
  5124. }
  5125. }
  5126. $s = '';
  5127. // fill and borders
  5128. if (is_string($border) AND (strlen($border) == 4)) {
  5129. // full border
  5130. $border = 1;
  5131. }
  5132. if ($fill OR ($border == 1)) {
  5133. if ($fill) {
  5134. $op = ($border == 1) ? 'B' : 'f';
  5135. } else {
  5136. $op = 'S';
  5137. }
  5138. if ($this->rtl) {
  5139. $xk = (($x - $w) * $k);
  5140. } else {
  5141. $xk = ($x * $k);
  5142. }
  5143. $s .= sprintf('%.2F %.2F %.2F %.2F re %s ', $xk, (($this->h - $y) * $k), ($w * $k), (-$h * $k), $op);
  5144. }
  5145. // draw borders
  5146. $s .= $this->getCellBorder($x, $y, $w, $h, $border);
  5147. if ($txt != '') {
  5148. $txt2 = $txt;
  5149. if ($this->isunicode) {
  5150. if (($this->CurrentFont['type'] == 'core') OR ($this->CurrentFont['type'] == 'TrueType') OR ($this->CurrentFont['type'] == 'Type1')) {
  5151. $txt2 = $this->UTF8ToLatin1($txt2);
  5152. } else {
  5153. $unicode = $this->UTF8StringToArray($txt); // array of UTF-8 unicode values
  5154. $unicode = $this->utf8Bidi($unicode, '', $this->tmprtl);
  5155. if (defined('K_THAI_TOPCHARS') AND (K_THAI_TOPCHARS == true)) {
  5156. // ---- Fix for bug #2977340 "Incorrect Thai characters position arrangement" ----
  5157. // NOTE: this doesn't work with HTML justification
  5158. // Symbols that could overlap on the font top (only works in LTR)
  5159. $topchar = array(3611, 3613, 3615, 3650, 3651, 3652); // chars that extends on top
  5160. $topsym = array(3633, 3636, 3637, 3638, 3639, 3655, 3656, 3657, 3658, 3659, 3660, 3661, 3662); // symbols with top position
  5161. $numchars = count($unicode); // number of chars
  5162. $unik = 0;
  5163. $uniblock = array();
  5164. $uniblock[$unik] = array();
  5165. $uniblock[$unik][] = $unicode[0];
  5166. // resolve overlapping conflicts by splitting the string in several parts
  5167. for ($i = 1; $i < $numchars; ++$i) {
  5168. // check if symbols overlaps at top
  5169. if (in_array($unicode[$i], $topsym) AND (in_array($unicode[($i - 1)], $topsym) OR in_array($unicode[($i - 1)], $topchar))) {
  5170. // move symbols to another array
  5171. ++$unik;
  5172. $uniblock[$unik] = array();
  5173. $uniblock[$unik][] = $unicode[$i];
  5174. ++$unik;
  5175. $uniblock[$unik] = array();
  5176. $unicode[$i] = 0x200b; // Unicode Character 'ZERO WIDTH SPACE' (DEC:8203, U+200B)
  5177. } else {
  5178. $uniblock[$unik][] = $unicode[$i];
  5179. }
  5180. }
  5181. // ---- END OF Fix for bug #2977340
  5182. }
  5183. $txt2 = $this->arrUTF8ToUTF16BE($unicode, false);
  5184. }
  5185. }
  5186. $txt2 = $this->_escape($txt2);
  5187. // get current text width (considering general font stretching and spacing)
  5188. $txwidth = $this->GetStringWidth($txt);
  5189. $width = $txwidth;
  5190. // check for stretch mode
  5191. if ($stretch > 0) {
  5192. // calculate ratio between cell width and text width
  5193. if ($width <= 0) {
  5194. $ratio = 1;
  5195. } else {
  5196. $ratio = (($w - $this->cell_padding['L'] - $this->cell_padding['R']) / $width);
  5197. }
  5198. // check if stretching is required
  5199. if (($ratio < 1) OR (($ratio > 1) AND (($stretch % 2) == 0))) {
  5200. // the text will be stretched to fit cell width
  5201. if ($stretch > 2) {
  5202. // set new character spacing
  5203. $this->font_spacing += ($w - $this->cell_padding['L'] - $this->cell_padding['R'] - $width) / (max(($this->GetNumChars($txt) - 1), 1) * ($this->font_stretching / 100));
  5204. } else {
  5205. // set new horizontal stretching
  5206. $this->font_stretching *= $ratio;
  5207. }
  5208. // recalculate text width (the text fills the entire cell)
  5209. $width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
  5210. // reset alignment
  5211. $align = '';
  5212. }
  5213. }
  5214. if ($this->font_stretching != 100) {
  5215. // apply font stretching
  5216. $rs .= sprintf('BT %.2F Tz ET ', $this->font_stretching);
  5217. }
  5218. if ($this->font_spacing != 0) {
  5219. // increase/decrease font spacing
  5220. $rs .= sprintf('BT %.2F Tc ET ', ($this->font_spacing * $this->k));
  5221. }
  5222. if ($this->ColorFlag) {
  5223. $s .= 'q '.$this->TextColor.' ';
  5224. }
  5225. // rendering mode
  5226. $s .= sprintf('BT %d Tr %.2F w ET ', $this->textrendermode, $this->textstrokewidth);
  5227. // count number of spaces
  5228. $ns = substr_count($txt, chr(32));
  5229. // Justification
  5230. $spacewidth = 0;
  5231. if (($align == 'J') AND ($ns > 0)) {
  5232. if ($this->isUnicodeFont()) {
  5233. // get string width without spaces
  5234. $width = $this->GetStringWidth(str_replace(' ', '', $txt));
  5235. // calculate average space width
  5236. $spacewidth = -1000 * ($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1) / $this->FontSize;
  5237. if ($this->font_stretching != 100) {
  5238. // word spacing is affected by stretching
  5239. $spacewidth /= ($this->font_stretching / 100);
  5240. }
  5241. // set word position to be used with TJ operator
  5242. $txt2 = str_replace(chr(0).chr(32), ') '.sprintf('%.3F', $spacewidth).' (', $txt2);
  5243. $unicode_justification = true;
  5244. } else {
  5245. // get string width
  5246. $width = $txwidth;
  5247. // new space width
  5248. $spacewidth = (($w - $width - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1)) * $this->k;
  5249. if ($this->font_stretching != 100) {
  5250. // word spacing (Tw) is affected by stretching
  5251. $spacewidth /= ($this->font_stretching / 100);
  5252. }
  5253. // set word spacing
  5254. $rs .= sprintf('BT %.3F Tw ET ', $spacewidth);
  5255. }
  5256. $width = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
  5257. }
  5258. // replace carriage return characters
  5259. $txt2 = str_replace("\r", ' ', $txt2);
  5260. switch ($align) {
  5261. case 'C': {
  5262. $dx = ($w - $width) / 2;
  5263. break;
  5264. }
  5265. case 'R': {
  5266. if ($this->rtl) {
  5267. $dx = $this->cell_padding['R'];
  5268. } else {
  5269. $dx = $w - $width - $this->cell_padding['R'];
  5270. }
  5271. break;
  5272. }
  5273. case 'L': {
  5274. if ($this->rtl) {
  5275. $dx = $w - $width - $this->cell_padding['L'];
  5276. } else {
  5277. $dx = $this->cell_padding['L'];
  5278. }
  5279. break;
  5280. }
  5281. case 'J':
  5282. default: {
  5283. if ($this->rtl) {
  5284. $dx = $this->cell_padding['R'];
  5285. } else {
  5286. $dx = $this->cell_padding['L'];
  5287. }
  5288. break;
  5289. }
  5290. }
  5291. if ($this->rtl) {
  5292. $xdx = $x - $dx - $width;
  5293. } else {
  5294. $xdx = $x + $dx;
  5295. }
  5296. $xdk = $xdx * $k;
  5297. // print text
  5298. $s .= sprintf('BT %.2F %.2F Td [(%s)] TJ ET', $xdk, (($this->h - $basefonty) * $k), $txt2);
  5299. if (isset($uniblock)) {
  5300. // print overlapping characters as separate string
  5301. $xshift = 0; // horizontal shift
  5302. $ty = (($this->h - $basefonty + (0.2 * $this->FontSize)) * $k);
  5303. $spw = (($w - $txwidth - $this->cell_padding['L'] - $this->cell_padding['R']) / ($ns?$ns:1));
  5304. foreach ($uniblock as $uk => $uniarr) {
  5305. if (($uk % 2) == 0) {
  5306. // x space to skip
  5307. if ($spacewidth != 0) {
  5308. // justification shift
  5309. $xshift += (count(array_keys($uniarr, 32)) * $spw);
  5310. }
  5311. $xshift += $this->GetArrStringWidth($uniarr); // + shift justification
  5312. } else {
  5313. // character to print
  5314. $topchr = $this->arrUTF8ToUTF16BE($uniarr, false);
  5315. $topchr = $this->_escape($topchr);
  5316. $s .= sprintf(' BT %.2F %.2F Td [(%s)] TJ ET', ($xdk + ($xshift * $k)), $ty, $topchr);
  5317. }
  5318. }
  5319. }
  5320. if ($this->underline) {
  5321. $s .= ' '.$this->_dounderlinew($xdx, $basefonty, $width);
  5322. }
  5323. if ($this->linethrough) {
  5324. $s .= ' '.$this->_dolinethroughw($xdx, $basefonty, $width);
  5325. }
  5326. if ($this->overline) {
  5327. $s .= ' '.$this->_dooverlinew($xdx, $basefonty, $width);
  5328. }
  5329. if ($this->ColorFlag) {
  5330. $s .= ' Q';
  5331. }
  5332. if ($link) {
  5333. $this->Link($xdx, $yt, $width, ($this->FontAscent + $this->FontDescent), $link, $ns);
  5334. }
  5335. }
  5336. // output cell
  5337. if ($s) {
  5338. // output cell
  5339. $rs .= $s;
  5340. if ($this->font_spacing != 0) {
  5341. // reset font spacing mode
  5342. $rs .= ' BT 0 Tc ET';
  5343. }
  5344. if ($this->font_stretching != 100) {
  5345. // reset font stretching mode
  5346. $rs .= ' BT 100 Tz ET';
  5347. }
  5348. }
  5349. // reset word spacing
  5350. if (!$this->isUnicodeFont() AND ($align == 'J')) {
  5351. $rs .= ' BT 0 Tw ET';
  5352. }
  5353. // reset stretching and spacing
  5354. $this->font_stretching = $prev_font_stretching;
  5355. $this->font_spacing = $prev_font_spacing;
  5356. $this->lasth = $h;
  5357. if ($ln > 0) {
  5358. //Go to the beginning of the next line
  5359. $this->y = $y + $h + $this->cell_margin['B'];
  5360. if ($ln == 1) {
  5361. if ($this->rtl) {
  5362. $this->x = $this->w - $this->rMargin;
  5363. } else {
  5364. $this->x = $this->lMargin;
  5365. }
  5366. }
  5367. } else {
  5368. // go left or right by case
  5369. if ($this->rtl) {
  5370. $this->x = $x - $w - $this->cell_margin['L'];
  5371. } else {
  5372. $this->x = $x + $w + $this->cell_margin['R'];
  5373. }
  5374. }
  5375. $gstyles = ''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor."\n";
  5376. $rs = $gstyles.$rs;
  5377. $this->cell_padding = $prev_cell_padding;
  5378. $this->cell_margin = $prev_cell_margin;
  5379. return $rs;
  5380. }
  5381. /**
  5382. * Returns the code to draw the cell border
  5383. * @param float $x X coordinate.
  5384. * @param float $y Y coordinate.
  5385. * @param float $w Cell width.
  5386. * @param float $h Cell height.
  5387. * @param mixed $brd Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
  5388. * @param string $mode border position respect the square edge: normal: centered; ext: external; int: internal;
  5389. * @return string containing cell border code
  5390. * @access protected
  5391. * @see SetLineStyle()
  5392. * @since 5.7.000 (2010-08-02)
  5393. */
  5394. protected function getCellBorder($x, $y, $w, $h, $brd) {
  5395. $s = ''; // string to be returned
  5396. if (empty($brd)) {
  5397. return $s;
  5398. }
  5399. if ($brd == 1) {
  5400. $brd = array('LRTB' => true);
  5401. }
  5402. // calculate coordinates for border
  5403. $k = $this->k;
  5404. if ($this->rtl) {
  5405. $xeL = ($x - $w) * $k;
  5406. $xeR = $x * $k;
  5407. } else {
  5408. $xeL = $x * $k;
  5409. $xeR = ($x + $w) * $k;
  5410. }
  5411. $yeL = (($this->h - ($y + $h)) * $k);
  5412. $yeT = (($this->h - $y) * $k);
  5413. $xeT = $xeL;
  5414. $xeB = $xeR;
  5415. $yeR = $yeT;
  5416. $yeB = $yeL;
  5417. if (is_string($brd)) {
  5418. // convert string to array
  5419. $slen = strlen($brd);
  5420. $newbrd = array();
  5421. for ($i = 0; $i < $slen; ++$i) {
  5422. $newbrd[$brd{$i}] = array('cap' => 'square', 'join' => 'miter');
  5423. }
  5424. $brd = $newbrd;
  5425. }
  5426. if (isset($brd['mode'])) {
  5427. $mode = $brd['mode'];
  5428. unset($brd['mode']);
  5429. } else {
  5430. $mode = 'normal';
  5431. }
  5432. foreach ($brd as $border => $style) {
  5433. if (is_array($style) AND !empty($style)) {
  5434. // apply border style
  5435. $prev_style = $this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' ';
  5436. $s .= $this->SetLineStyle($style, true)."\n";
  5437. }
  5438. switch ($mode) {
  5439. case 'ext': {
  5440. $off = (($this->LineWidth / 2) * $k);
  5441. $xL = $xeL - $off;
  5442. $xR = $xeR + $off;
  5443. $yT = $yeT + $off;
  5444. $yL = $yeL - $off;
  5445. $xT = $xL;
  5446. $xB = $xR;
  5447. $yR = $yT;
  5448. $yB = $yL;
  5449. $w += $this->LineWidth;
  5450. $h += $this->LineWidth;
  5451. break;
  5452. }
  5453. case 'int': {
  5454. $off = ($this->LineWidth / 2) * $k;
  5455. $xL = $xeL + $off;
  5456. $xR = $xeR - $off;
  5457. $yT = $yeT - $off;
  5458. $yL = $yeL + $off;
  5459. $xT = $xL;
  5460. $xB = $xR;
  5461. $yR = $yT;
  5462. $yB = $yL;
  5463. $w -= $this->LineWidth;
  5464. $h -= $this->LineWidth;
  5465. break;
  5466. }
  5467. case 'normal':
  5468. default: {
  5469. $xL = $xeL;
  5470. $xT = $xeT;
  5471. $xB = $xeB;
  5472. $xR = $xeR;
  5473. $yL = $yeL;
  5474. $yT = $yeT;
  5475. $yB = $yeB;
  5476. $yR = $yeR;
  5477. break;
  5478. }
  5479. }
  5480. // draw borders by case
  5481. if (strlen($border) == 4) {
  5482. $s .= sprintf('%.2F %.2F %.2F %.2F re S ', $xT, $yT, ($w * $k), (-$h * $k));
  5483. } elseif (strlen($border) == 3) {
  5484. if (strpos($border,'B') === false) { // LTR
  5485. $s .= sprintf('%.2F %.2F m ', $xL, $yL);
  5486. $s .= sprintf('%.2F %.2F l ', $xT, $yT);
  5487. $s .= sprintf('%.2F %.2F l ', $xR, $yR);
  5488. $s .= sprintf('%.2F %.2F l ', $xB, $yB);
  5489. $s .= 'S ';
  5490. } elseif (strpos($border,'L') === false) { // TRB
  5491. $s .= sprintf('%.2F %.2F m ', $xT, $yT);
  5492. $s .= sprintf('%.2F %.2F l ', $xR, $yR);
  5493. $s .= sprintf('%.2F %.2F l ', $xB, $yB);
  5494. $s .= sprintf('%.2F %.2F l ', $xL, $yL);
  5495. $s .= 'S ';
  5496. } elseif (strpos($border,'T') === false) { // RBL
  5497. $s .= sprintf('%.2F %.2F m ', $xR, $yR);
  5498. $s .= sprintf('%.2F %.2F l ', $xB, $yB);
  5499. $s .= sprintf('%.2F %.2F l ', $xL, $yL);
  5500. $s .= sprintf('%.2F %.2F l ', $xT, $yT);
  5501. $s .= 'S ';
  5502. } elseif (strpos($border,'R') === false) { // BLT
  5503. $s .= sprintf('%.2F %.2F m ', $xB, $yB);
  5504. $s .= sprintf('%.2F %.2F l ', $xL, $yL);
  5505. $s .= sprintf('%.2F %.2F l ', $xT, $yT);
  5506. $s .= sprintf('%.2F %.2F l ', $xR, $yR);
  5507. $s .= 'S ';
  5508. }
  5509. } elseif (strlen($border) == 2) {
  5510. if ((strpos($border,'L') !== false) AND (strpos($border,'T') !== false)) { // LT
  5511. $s .= sprintf('%.2F %.2F m ', $xL, $yL);
  5512. $s .= sprintf('%.2F %.2F l ', $xT, $yT);
  5513. $s .= sprintf('%.2F %.2F l ', $xR, $yR);
  5514. $s .= 'S ';
  5515. } elseif ((strpos($border,'T') !== false) AND (strpos($border,'R') !== false)) { // TR
  5516. $s .= sprintf('%.2F %.2F m ', $xT, $yT);
  5517. $s .= sprintf('%.2F %.2F l ', $xR, $yR);
  5518. $s .= sprintf('%.2F %.2F l ', $xB, $yB);
  5519. $s .= 'S ';
  5520. } elseif ((strpos($border,'R') !== false) AND (strpos($border,'B') !== false)) { // RB
  5521. $s .= sprintf('%.2F %.2F m ', $xR, $yR);
  5522. $s .= sprintf('%.2F %.2F l ', $xB, $yB);
  5523. $s .= sprintf('%.2F %.2F l ', $xL, $yL);
  5524. $s .= 'S ';
  5525. } elseif ((strpos($border,'B') !== false) AND (strpos($border,'L') !== false)) { // BL
  5526. $s .= sprintf('%.2F %.2F m ', $xB, $yB);
  5527. $s .= sprintf('%.2F %.2F l ', $xL, $yL);
  5528. $s .= sprintf('%.2F %.2F l ', $xT, $yT);
  5529. $s .= 'S ';
  5530. } elseif ((strpos($border,'L') !== false) AND (strpos($border,'R') !== false)) { // LR
  5531. $s .= sprintf('%.2F %.2F m ', $xL, $yL);
  5532. $s .= sprintf('%.2F %.2F l ', $xT, $yT);
  5533. $s .= 'S ';
  5534. $s .= sprintf('%.2F %.2F m ', $xR, $yR);
  5535. $s .= sprintf('%.2F %.2F l ', $xB, $yB);
  5536. $s .= 'S ';
  5537. } elseif ((strpos($border,'T') !== false) AND (strpos($border,'B') !== false)) { // TB
  5538. $s .= sprintf('%.2F %.2F m ', $xT, $yT);
  5539. $s .= sprintf('%.2F %.2F l ', $xR, $yR);
  5540. $s .= 'S ';
  5541. $s .= sprintf('%.2F %.2F m ', $xB, $yB);
  5542. $s .= sprintf('%.2F %.2F l ', $xL, $yL);
  5543. $s .= 'S ';
  5544. }
  5545. } else { // strlen($border) == 1
  5546. if (strpos($border,'L') !== false) { // L
  5547. $s .= sprintf('%.2F %.2F m ', $xL, $yL);
  5548. $s .= sprintf('%.2F %.2F l ', $xT, $yT);
  5549. $s .= 'S ';
  5550. } elseif (strpos($border,'T') !== false) { // T
  5551. $s .= sprintf('%.2F %.2F m ', $xT, $yT);
  5552. $s .= sprintf('%.2F %.2F l ', $xR, $yR);
  5553. $s .= 'S ';
  5554. } elseif (strpos($border,'R') !== false) { // R
  5555. $s .= sprintf('%.2F %.2F m ', $xR, $yR);
  5556. $s .= sprintf('%.2F %.2F l ', $xB, $yB);
  5557. $s .= 'S ';
  5558. } elseif (strpos($border,'B') !== false) { // B
  5559. $s .= sprintf('%.2F %.2F m ', $xB, $yB);
  5560. $s .= sprintf('%.2F %.2F l ', $xL, $yL);
  5561. $s .= 'S ';
  5562. }
  5563. }
  5564. if (is_array($style) AND !empty($style)) {
  5565. // reset border style to previous value
  5566. $s .= "\n".$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor."\n";
  5567. }
  5568. }
  5569. return $s;
  5570. }
  5571. /**
  5572. * This method allows printing text with line breaks.
  5573. * They can be automatic (as soon as the text reaches the right border of the cell) or explicit (via the \n character). As many cells as necessary are output, one below the other.<br />
  5574. * Text can be aligned, centered or justified. The cell block can be framed and the background painted.
  5575. * @param float $w Width of cells. If 0, they extend up to the right margin of the page.
  5576. * @param float $h Cell minimum height. The cell extends automatically if needed.
  5577. * @param string $txt String to print
  5578. * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
  5579. * @param string $align Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align</li><li>C: center</li><li>R: right align</li><li>J: justification (default value when $ishtml=false)</li></ul>
  5580. * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
  5581. * @param int $ln Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right</li><li>1: to the beginning of the next line [DEFAULT]</li><li>2: below</li></ul>
  5582. * @param float $x x position in user units
  5583. * @param float $y y position in user units
  5584. * @param boolean $reseth if true reset the last cell height (default true).
  5585. * @param int $stretch font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
  5586. * @param boolean $ishtml set to true if $txt is HTML content (default = false).
  5587. * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width.
  5588. * @param float $maxh maximum height. It should be >= $h and less then remaining space to the bottom of the page, or 0 for disable this feature. This feature works only when $ishtml=false.
  5589. * @param string $valign Vertical alignment of text (requires $maxh = $h > 0). Possible values are:<ul><li>T: TOP</li><li>M: middle</li><li>B: bottom</li></ul>. This feature works only when $ishtml=false.
  5590. * @param boolean $fitcell if true attempt to fit all the text within the cell by reducing the font size.
  5591. * @return int Return the number of cells or 1 for html mode.
  5592. * @access public
  5593. * @since 1.3
  5594. * @see SetFont(), SetDrawColor(), SetFillColor(), SetTextColor(), SetLineWidth(), Cell(), Write(), SetAutoPageBreak()
  5595. */
  5596. 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) {
  5597. $prev_cell_margin = $this->cell_margin;
  5598. $prev_cell_padding = $this->cell_padding;
  5599. // adjust internal padding
  5600. $this->adjustCellPadding($border);
  5601. $mc_padding = $this->cell_padding;
  5602. $mc_margin = $this->cell_margin;
  5603. $this->cell_padding['T'] = 0;
  5604. $this->cell_padding['B'] = 0;
  5605. $this->setCellMargins(0, 0, 0, 0);
  5606. if ($this->empty_string($this->lasth) OR $reseth) {
  5607. // reset row height
  5608. $this->resetLastH();
  5609. }
  5610. if (!$this->empty_string($y)) {
  5611. $this->SetY($y);
  5612. } else {
  5613. $y = $this->GetY();
  5614. }
  5615. $resth = 0;
  5616. if ((!$this->InFooter) AND (($y + $h + $mc_margin['T'] + $mc_margin['B']) > $this->PageBreakTrigger)) {
  5617. // spit cell in more pages/columns
  5618. $newh = $this->PageBreakTrigger - $y;
  5619. $resth = $h - $newh; // cell to be printed on the next page/column
  5620. $h = $newh;
  5621. }
  5622. // get current page number
  5623. $startpage = $this->page;
  5624. // get current column
  5625. $startcolumn = $this->current_column;
  5626. if (!$this->empty_string($x)) {
  5627. $this->SetX($x);
  5628. } else {
  5629. $x = $this->GetX();
  5630. }
  5631. // check page for no-write regions and adapt page margins if necessary
  5632. $this->checkPageRegions(0, $x, $y);
  5633. // apply margins
  5634. $oy = $y + $mc_margin['T'];
  5635. if ($this->rtl) {
  5636. $ox = $this->w - $x - $mc_margin['R'];
  5637. } else {
  5638. $ox = $x + $mc_margin['L'];
  5639. }
  5640. $this->x = $ox;
  5641. $this->y = $oy;
  5642. // set width
  5643. if ($this->empty_string($w) OR ($w <= 0)) {
  5644. if ($this->rtl) {
  5645. $w = $this->x - $this->lMargin - $mc_margin['L'];
  5646. } else {
  5647. $w = $this->w - $this->x - $this->rMargin - $mc_margin['R'];
  5648. }
  5649. }
  5650. // store original margin values
  5651. $lMargin = $this->lMargin;
  5652. $rMargin = $this->rMargin;
  5653. if ($this->rtl) {
  5654. $this->rMargin = $this->w - $this->x;
  5655. $this->lMargin = $this->x - $w;
  5656. } else {
  5657. $this->lMargin = $this->x;
  5658. $this->rMargin = $this->w - $this->x - $w;
  5659. }
  5660. if ($autopadding) {
  5661. // add top padding
  5662. $this->y += $mc_padding['T'];
  5663. }
  5664. if ($ishtml) { // ******* Write HTML text
  5665. $this->writeHTML($txt, true, 0, $reseth, true, $align);
  5666. $nl = 1;
  5667. } else { // ******* Write simple text
  5668. // vertical alignment
  5669. if ($maxh > 0) {
  5670. // get text height
  5671. $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
  5672. if ($fitcell) {
  5673. $prev_FontSizePt = $this->FontSizePt;
  5674. // try to reduce font size to fit text on cell (use a quick search algorithm)
  5675. $fmin = 1;
  5676. $fmax = $this->FontSizePt;
  5677. $prev_text_height = $text_height;
  5678. $maxit = 100; // max number of iterations
  5679. while ($maxit > 0) {
  5680. $fmid = (($fmax + $fmin) / 2);
  5681. $this->SetFontSize($fmid, false);
  5682. $this->resetLastH();
  5683. $text_height = $this->getStringHeight($w, $txt, $reseth, $autopadding, $mc_padding, $border);
  5684. if (($text_height == $maxh) OR (($text_height < $maxh) AND ($fmin >= ($fmax - 0.01)))) {
  5685. break;
  5686. } elseif ($text_height < $maxh) {
  5687. $fmin = $fmid;
  5688. } else {
  5689. $fmax = $fmid;
  5690. }
  5691. --$maxit;
  5692. }
  5693. $this->SetFontSize($this->FontSizePt);
  5694. }
  5695. if ($text_height < $maxh) {
  5696. if ($valign == 'M') {
  5697. // text vertically centered
  5698. $this->y += (($maxh - $text_height) / 2);
  5699. } elseif ($valign == 'B') {
  5700. // text vertically aligned on bottom
  5701. $this->y += ($maxh - $text_height);
  5702. }
  5703. }
  5704. }
  5705. $nl = $this->Write($this->lasth, $txt, '', 0, $align, true, $stretch, false, true, $maxh, 0, $mc_margin);
  5706. if ($fitcell) {
  5707. // restore font size
  5708. $this->SetFontSize($prev_FontSizePt);
  5709. }
  5710. }
  5711. if ($autopadding) {
  5712. // add bottom padding
  5713. $this->y += $mc_padding['B'];
  5714. }
  5715. // Get end-of-text Y position
  5716. $currentY = $this->y;
  5717. // get latest page number
  5718. $endpage = $this->page;
  5719. if ($resth > 0) {
  5720. $skip = ($endpage - $startpage);
  5721. $tmpresth = $resth;
  5722. while ($tmpresth > 0) {
  5723. if ($skip <= 0) {
  5724. // add a page (or trig AcceptPageBreak() for multicolumn mode)
  5725. $this->checkPageBreak($this->PageBreakTrigger + 1);
  5726. }
  5727. if ($this->num_columns > 1) {
  5728. $tmpresth -= ($this->h - $this->y - $this->bMargin);
  5729. } else {
  5730. $tmpresth -= ($this->h - $this->tMargin - $this->bMargin);
  5731. }
  5732. --$skip;
  5733. }
  5734. $currentY = $this->y;
  5735. $endpage = $this->page;
  5736. }
  5737. // get latest column
  5738. $endcolumn = $this->current_column;
  5739. if ($this->num_columns == 0) {
  5740. $this->num_columns = 1;
  5741. }
  5742. // get border modes
  5743. $border_start = $this->getBorderMode($border, $position='start');
  5744. $border_end = $this->getBorderMode($border, $position='end');
  5745. $border_middle = $this->getBorderMode($border, $position='middle');
  5746. // design borders around HTML cells.
  5747. for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
  5748. $ccode = '';
  5749. $this->setPage($page);
  5750. if ($this->num_columns < 2) {
  5751. // single-column mode
  5752. $this->SetX($x);
  5753. $this->y = $this->tMargin;
  5754. }
  5755. // account for margin changes
  5756. if ($page > $startpage) {
  5757. if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
  5758. $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
  5759. } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
  5760. $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
  5761. }
  5762. }
  5763. if ($startpage == $endpage) {
  5764. // single page
  5765. for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
  5766. $this->selectColumn($column);
  5767. if ($this->rtl) {
  5768. $this->x -= $mc_margin['R'];
  5769. } else {
  5770. $this->x += $mc_margin['L'];
  5771. }
  5772. if ($startcolumn == $endcolumn) { // single column
  5773. $cborder = $border;
  5774. $h = max($h, ($currentY - $oy));
  5775. $this->y = $oy;
  5776. } elseif ($column == $startcolumn) { // first column
  5777. $cborder = $border_start;
  5778. $this->y = $oy;
  5779. $h = $this->h - $this->y - $this->bMargin;
  5780. } elseif ($column == $endcolumn) { // end column
  5781. $cborder = $border_end;
  5782. $h = $currentY - $this->y;
  5783. if ($resth > $h) {
  5784. $h = $resth;
  5785. }
  5786. } else { // middle column
  5787. $cborder = $border_middle;
  5788. $h = $this->h - $this->y - $this->bMargin;
  5789. $resth -= $h;
  5790. }
  5791. $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
  5792. } // end for each column
  5793. } elseif ($page == $startpage) { // first page
  5794. for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
  5795. $this->selectColumn($column);
  5796. if ($this->rtl) {
  5797. $this->x -= $mc_margin['R'];
  5798. } else {
  5799. $this->x += $mc_margin['L'];
  5800. }
  5801. if ($column == $startcolumn) { // first column
  5802. $cborder = $border_start;
  5803. $this->y = $oy;
  5804. $h = $this->h - $this->y - $this->bMargin;
  5805. } else { // middle column
  5806. $cborder = $border_middle;
  5807. $h = $this->h - $this->y - $this->bMargin;
  5808. $resth -= $h;
  5809. }
  5810. $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
  5811. } // end for each column
  5812. } elseif ($page == $endpage) { // last page
  5813. for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
  5814. $this->selectColumn($column);
  5815. if ($this->rtl) {
  5816. $this->x -= $mc_margin['R'];
  5817. } else {
  5818. $this->x += $mc_margin['L'];
  5819. }
  5820. if ($column == $endcolumn) {
  5821. // end column
  5822. $cborder = $border_end;
  5823. $h = $currentY - $this->y;
  5824. if ($resth > $h) {
  5825. $h = $resth;
  5826. }
  5827. } else {
  5828. // middle column
  5829. $cborder = $border_middle;
  5830. $h = $this->h - $this->y - $this->bMargin;
  5831. $resth -= $h;
  5832. }
  5833. $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
  5834. } // end for each column
  5835. } else { // middle page
  5836. for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
  5837. $this->selectColumn($column);
  5838. if ($this->rtl) {
  5839. $this->x -= $mc_margin['R'];
  5840. } else {
  5841. $this->x += $mc_margin['L'];
  5842. }
  5843. $cborder = $border_middle;
  5844. $h = $this->h - $this->y - $this->bMargin;
  5845. $resth -= $h;
  5846. $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
  5847. } // end for each column
  5848. }
  5849. if ($cborder OR $fill) {
  5850. // draw border and fill
  5851. if ($this->inxobj) {
  5852. // we are inside an XObject template
  5853. if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
  5854. $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
  5855. $pagemark = &$this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
  5856. } else {
  5857. $pagemark = &$this->xobjects[$this->xobjid]['intmrk'];
  5858. }
  5859. $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
  5860. $pstart = substr($pagebuff, 0, $pagemark);
  5861. $pend = substr($pagebuff, $pagemark);
  5862. $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
  5863. $pagemark += strlen($ccode);
  5864. } else {
  5865. if (end($this->transfmrk[$this->page]) !== false) {
  5866. $pagemarkkey = key($this->transfmrk[$this->page]);
  5867. $pagemark = &$this->transfmrk[$this->page][$pagemarkkey];
  5868. } elseif ($this->InFooter) {
  5869. $pagemark = &$this->footerpos[$this->page];
  5870. } else {
  5871. $pagemark = &$this->intmrk[$this->page];
  5872. }
  5873. $pagebuff = $this->getPageBuffer($this->page);
  5874. $pstart = substr($pagebuff, 0, $pagemark);
  5875. $pend = substr($pagebuff, $pagemark);
  5876. $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
  5877. $pagemark += strlen($ccode);
  5878. }
  5879. }
  5880. } // end for each page
  5881. // Get end-of-cell Y position
  5882. $currentY = $this->GetY();
  5883. // restore original margin values
  5884. $this->SetLeftMargin($lMargin);
  5885. $this->SetRightMargin($rMargin);
  5886. if ($ln > 0) {
  5887. //Go to the beginning of the next line
  5888. $this->SetY($currentY + $mc_margin['B']);
  5889. if ($ln == 2) {
  5890. $this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
  5891. }
  5892. } else {
  5893. // go left or right by case
  5894. $this->setPage($startpage);
  5895. $this->y = $y;
  5896. $this->SetX($x + $w + $mc_margin['L'] + $mc_margin['R']);
  5897. }
  5898. $this->setContentMark();
  5899. $this->cell_padding = $prev_cell_padding;
  5900. $this->cell_margin = $prev_cell_margin;
  5901. return $nl;
  5902. }
  5903. /**
  5904. * Get the border mode accounting for multicell position (opens bottom side of multicell crossing pages)
  5905. * @param mixed $brd Indicates if borders must be drawn around the cell block. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul>or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
  5906. * @param string multicell position: 'start', 'middle', 'end'
  5907. * @return border mode array
  5908. * @access protected
  5909. * @since 4.4.002 (2008-12-09)
  5910. */
  5911. protected function getBorderMode($brd, $position='start') {
  5912. if ((!$this->opencell) OR empty($brd)) {
  5913. return $brd;
  5914. }
  5915. if ($brd == 1) {
  5916. $brd = 'LTRB';
  5917. }
  5918. if (is_string($brd)) {
  5919. // convert string to array
  5920. $slen = strlen($brd);
  5921. $newbrd = array();
  5922. for ($i = 0; $i < $slen; ++$i) {
  5923. $newbrd[$brd{$i}] = array('cap' => 'square', 'join' => 'miter');
  5924. }
  5925. $brd = $newbrd;
  5926. }
  5927. foreach ($brd as $border => $style) {
  5928. switch ($position) {
  5929. case 'start': {
  5930. if (strpos($border, 'B') !== false) {
  5931. // remove bottom line
  5932. $newkey = str_replace('B', '', $border);
  5933. if (strlen($newkey) > 0) {
  5934. $brd[$newkey] = $style;
  5935. }
  5936. unset($brd[$border]);
  5937. }
  5938. break;
  5939. }
  5940. case 'middle': {
  5941. if (strpos($border, 'B') !== false) {
  5942. // remove bottom line
  5943. $newkey = str_replace('B', '', $border);
  5944. if (strlen($newkey) > 0) {
  5945. $brd[$newkey] = $style;
  5946. }
  5947. unset($brd[$border]);
  5948. $border = $newkey;
  5949. }
  5950. if (strpos($border, 'T') !== false) {
  5951. // remove bottom line
  5952. $newkey = str_replace('T', '', $border);
  5953. if (strlen($newkey) > 0) {
  5954. $brd[$newkey] = $style;
  5955. }
  5956. unset($brd[$border]);
  5957. }
  5958. break;
  5959. }
  5960. case 'end': {
  5961. if (strpos($border, 'T') !== false) {
  5962. // remove bottom line
  5963. $newkey = str_replace('T', '', $border);
  5964. if (strlen($newkey) > 0) {
  5965. $brd[$newkey] = $style;
  5966. }
  5967. unset($brd[$border]);
  5968. }
  5969. break;
  5970. }
  5971. }
  5972. }
  5973. return $brd;
  5974. }
  5975. /**
  5976. * This method return the estimated number of lines for print a simple text string using Multicell() method.
  5977. * @param string $txt String for calculating his height
  5978. * @param float $w Width of cells. If 0, they extend up to the right margin of the page.
  5979. * @param boolean $reseth if true reset the last cell height (default false).
  5980. * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width (default true).
  5981. * @param float $cellpadding Internal cell padding, if empty uses default cell padding.
  5982. * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
  5983. * @return float Return the minimal height needed for multicell method for printing the $txt param.
  5984. * @author Alexander Escalona Fernández, Nicola Asuni
  5985. * @access public
  5986. * @since 4.5.011
  5987. */
  5988. public function getNumLines($txt, $w=0, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
  5989. if ($txt === '') {
  5990. // empty string
  5991. return 1;
  5992. }
  5993. // adjust internal padding
  5994. $prev_cell_padding = $this->cell_padding;
  5995. $prev_lasth = $this->lasth;
  5996. if (is_array($cellpadding)) {
  5997. $this->cell_padding = $cellpadding;
  5998. }
  5999. $this->adjustCellPadding($border);
  6000. if ($this->empty_string($w) OR ($w <= 0)) {
  6001. if ($this->rtl) {
  6002. $w = $this->x - $this->lMargin;
  6003. } else {
  6004. $w = $this->w - $this->rMargin - $this->x;
  6005. }
  6006. }
  6007. $wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
  6008. if ($reseth) {
  6009. // reset row height
  6010. $this->resetLastH();
  6011. }
  6012. $lines = 1;
  6013. $sum = 0;
  6014. $chars = $this->utf8Bidi($this->UTF8StringToArray($txt), $txt, $this->tmprtl);
  6015. $charsWidth = $this->GetArrStringWidth($chars, '', '', 0, true);
  6016. $length = count($chars);
  6017. $lastSeparator = -1;
  6018. for ($i = 0; $i < $length; ++$i) {
  6019. $charWidth = $charsWidth[$i];
  6020. if (preg_match($this->re_spaces, $this->unichr($chars[$i]))) {
  6021. $lastSeparator = $i;
  6022. }
  6023. if ((($sum + $charWidth) > $wmax) OR ($chars[$i] == 10)) {
  6024. ++$lines;
  6025. if ($lastSeparator != -1) {
  6026. $i = $lastSeparator;
  6027. $lastSeparator = -1;
  6028. $sum = 0;
  6029. } else {
  6030. $sum = $charWidth;
  6031. }
  6032. } else {
  6033. $sum += $charWidth;
  6034. }
  6035. }
  6036. if ($chars[($length - 1)] == 10) {
  6037. --$lines;
  6038. }
  6039. $this->cell_padding = $prev_cell_padding;
  6040. $this->lasth = $prev_lasth;
  6041. return $lines;
  6042. }
  6043. /**
  6044. * This method return the estimated needed height for print a simple text string in Multicell() method.
  6045. * Generally, if you want to know the exact height for a block of content you can use the following alternative technique:
  6046. * <pre>
  6047. * // store current object
  6048. * $pdf->startTransaction();
  6049. * // store starting values
  6050. * $start_y = $pdf->GetY();
  6051. * $start_page = $pdf->getPage();
  6052. * // call your printing functions with your parameters
  6053. * // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  6054. * $pdf->MultiCell($w=0, $h=0, $txt, $border=1, $align='L', $fill=false, $ln=1, $x='', $y='', $reseth=true, $stretch=0, $ishtml=false, $autopadding=true, $maxh=0);
  6055. * // - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
  6056. * // get the new Y
  6057. * $end_y = $pdf->GetY();
  6058. * $end_page = $pdf->getPage();
  6059. * // calculate height
  6060. * $height = 0;
  6061. * if ($end_page == $start_page) {
  6062. * $height = $end_y - $start_y;
  6063. * } else {
  6064. * for ($page=$start_page; $page <= $end_page; ++$page) {
  6065. * $this->setPage($page);
  6066. * if ($page == $start_page) {
  6067. * // first page
  6068. * $height = $this->h - $start_y - $this->bMargin;
  6069. * } elseif ($page == $end_page) {
  6070. * // last page
  6071. * $height = $end_y - $this->tMargin;
  6072. * } else {
  6073. * $height = $this->h - $this->tMargin - $this->bMargin;
  6074. * }
  6075. * }
  6076. * }
  6077. * // restore previous object
  6078. * $pdf = $pdf->rollbackTransaction();
  6079. * </pre>
  6080. * @param float $w Width of cells. If 0, they extend up to the right margin of the page.
  6081. * @param string $txt String for calculating his height
  6082. * @param boolean $reseth if true reset the last cell height (default false).
  6083. * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width (default true).
  6084. * @param float $cellpadding Internal cell padding, if empty uses default cell padding.
  6085. * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
  6086. * @return float Return the minimal height needed for multicell method for printing the $txt param.
  6087. * @author Nicola Asuni, Alexander Escalona Fernández
  6088. * @access public
  6089. */
  6090. public function getStringHeight($w, $txt, $reseth=false, $autopadding=true, $cellpadding='', $border=0) {
  6091. // adjust internal padding
  6092. $prev_cell_padding = $this->cell_padding;
  6093. $prev_lasth = $this->lasth;
  6094. if (is_array($cellpadding)) {
  6095. $this->cell_padding = $cellpadding;
  6096. }
  6097. $this->adjustCellPadding($border);
  6098. $lines = $this->getNumLines($txt, $w, $reseth, $autopadding, $cellpadding, $border);
  6099. $height = $lines * ($this->FontSize * $this->cell_height_ratio);
  6100. if ($autopadding) {
  6101. // add top and bottom padding
  6102. $height += ($this->cell_padding['T'] + $this->cell_padding['B']);
  6103. }
  6104. $this->cell_padding = $prev_cell_padding;
  6105. $this->lasth = $prev_lasth;
  6106. return $height;
  6107. }
  6108. /**
  6109. * This method prints text from the current position.<br />
  6110. * @param float $h Line height
  6111. * @param string $txt String to print
  6112. * @param mixed $link URL or identifier returned by AddLink()
  6113. * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
  6114. * @param string $align Allows to center or align the text. Possible values are:<ul><li>L or empty string: left align (default value)</li><li>C: center</li><li>R: right align</li><li>J: justify</li></ul>
  6115. * @param boolean $ln if true set cursor at the bottom of the line, otherwise set cursor at the top of the line.
  6116. * @param int $stretch font stretch mode: <ul><li>0 = disabled</li><li>1 = horizontal scaling only if text is larger than cell width</li><li>2 = forced horizontal scaling to fit cell width</li><li>3 = character spacing only if text is larger than cell width</li><li>4 = forced character spacing to fit cell width</li></ul> General font stretching and scaling values will be preserved when possible.
  6117. * @param boolean $firstline if true prints only the first line and return the remaining string.
  6118. * @param boolean $firstblock if true the string is the starting of a line.
  6119. * @param float $maxh maximum height. The remaining unprinted text will be returned. It should be >= $h and less then remaining space to the bottom of the page, or 0 for disable this feature.
  6120. * @param float $wadj first line width will be reduced by this amount (used in HTML mode).
  6121. * @param array $margin margin array of the parent container
  6122. * @return mixed Return the number of cells or the remaining string if $firstline = true.
  6123. * @access public
  6124. * @since 1.5
  6125. */
  6126. public function Write($h, $txt, $link='', $fill=false, $align='', $ln=false, $stretch=0, $firstline=false, $firstblock=false, $maxh=0, $wadj=0, $margin='') {
  6127. // check page for no-write regions and adapt page margins if necessary
  6128. $this->checkPageRegions($h);
  6129. if (strlen($txt) == 0) {
  6130. // fix empty text
  6131. $txt = ' ';
  6132. }
  6133. if ($margin === '') {
  6134. // set default margins
  6135. $margin = $this->cell_margin;
  6136. }
  6137. // remove carriage returns
  6138. $s = str_replace("\r", '', $txt);
  6139. // check if string contains arabic text
  6140. if (preg_match($this->unicode->uni_RE_PATTERN_ARABIC, $s)) {
  6141. $arabic = true;
  6142. } else {
  6143. $arabic = false;
  6144. }
  6145. // check if string contains RTL text
  6146. if ($arabic OR ($this->tmprtl == 'R') OR preg_match($this->unicode->uni_RE_PATTERN_RTL, $s)) {
  6147. $rtlmode = true;
  6148. } else {
  6149. $rtlmode = false;
  6150. }
  6151. // get a char width
  6152. $chrwidth = $this->GetCharWidth('.');
  6153. // get array of unicode values
  6154. $chars = $this->UTF8StringToArray($s);
  6155. // get array of chars
  6156. $uchars = $this->UTF8ArrayToUniArray($chars);
  6157. // get the number of characters
  6158. $nb = count($chars);
  6159. // replacement for SHY character (minus symbol)
  6160. $shy_replacement = 45;
  6161. $shy_replacement_char = $this->unichr($shy_replacement);
  6162. // widht for SHY replacement
  6163. $shy_replacement_width = $this->GetCharWidth($shy_replacement);
  6164. // max Y
  6165. $maxy = $this->y + $maxh - $h - $this->cell_padding['T'] - $this->cell_padding['B'];
  6166. // calculate remaining line width ($w)
  6167. if ($this->rtl) {
  6168. $w = $this->x - $this->lMargin;
  6169. } else {
  6170. $w = $this->w - $this->rMargin - $this->x;
  6171. }
  6172. // max column width
  6173. $wmax = $w - $wadj;
  6174. if (!$firstline) {
  6175. $wmax -= ($this->cell_padding['L'] + $this->cell_padding['R']);
  6176. }
  6177. if ((!$firstline) AND (($chrwidth > $wmax) OR ($this->GetCharWidth($chars[0]) > $wmax))) {
  6178. // a single character do not fit on column
  6179. return '';
  6180. }
  6181. // minimum row height
  6182. $row_height = max($h, $this->FontSize * $this->cell_height_ratio);
  6183. $start_page = $this->page;
  6184. $i = 0; // character position
  6185. $j = 0; // current starting position
  6186. $sep = -1; // position of the last blank space
  6187. $shy = false; // true if the last blank is a soft hypen (SHY)
  6188. $l = 0; // current string length
  6189. $nl = 0; //number of lines
  6190. $linebreak = false;
  6191. $pc = 0; // previous character
  6192. // for each character
  6193. while ($i < $nb) {
  6194. if (($maxh > 0) AND ($this->y >= $maxy) ) {
  6195. break;
  6196. }
  6197. //Get the current character
  6198. $c = $chars[$i];
  6199. if ($c == 10) { // 10 = "\n" = new line
  6200. //Explicit line break
  6201. if ($align == 'J') {
  6202. if ($this->rtl) {
  6203. $talign = 'R';
  6204. } else {
  6205. $talign = 'L';
  6206. }
  6207. } else {
  6208. $talign = $align;
  6209. }
  6210. $tmpstr = $this->UniArrSubString($uchars, $j, $i);
  6211. if ($firstline) {
  6212. $startx = $this->x;
  6213. $tmparr = array_slice($chars, $j, ($i - $j));
  6214. if ($rtlmode) {
  6215. $tmparr = $this->utf8Bidi($tmparr, $tmpstr, $this->tmprtl);
  6216. }
  6217. $linew = $this->GetArrStringWidth($tmparr);
  6218. unset($tmparr);
  6219. if ($this->rtl) {
  6220. $this->endlinex = $startx - $linew;
  6221. } else {
  6222. $this->endlinex = $startx + $linew;
  6223. }
  6224. $w = $linew;
  6225. $tmpcellpadding = $this->cell_padding;
  6226. if ($maxh == 0) {
  6227. $this->SetCellPadding(0);
  6228. }
  6229. }
  6230. if ($firstblock AND $this->isRTLTextDir()) {
  6231. $tmpstr = $this->stringRightTrim($tmpstr);
  6232. }
  6233. // Skip newlines at the begining of a page or column
  6234. if (!empty($tmpstr) OR ($this->y < ($this->PageBreakTrigger - $row_height))) {
  6235. $this->Cell($w, $h, $tmpstr, 0, 1, $talign, $fill, $link, $stretch);
  6236. }
  6237. unset($tmpstr);
  6238. if ($firstline) {
  6239. $this->cell_padding = $tmpcellpadding;
  6240. return ($this->UniArrSubString($uchars, $i));
  6241. }
  6242. ++$nl;
  6243. $j = $i + 1;
  6244. $l = 0;
  6245. $sep = -1;
  6246. $shy = false;
  6247. // account for margin changes
  6248. if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND (!$this->InFooter)) {
  6249. $this->AcceptPageBreak();
  6250. if ($this->rtl) {
  6251. $this->x -= $margin['R'];
  6252. } else {
  6253. $this->x += $margin['L'];
  6254. }
  6255. $this->lMargin += $margin['L'];
  6256. $this->rMargin += $margin['R'];
  6257. }
  6258. $w = $this->getRemainingWidth();
  6259. $wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
  6260. } else {
  6261. // 160 is the non-breaking space.
  6262. // 173 is SHY (Soft Hypen).
  6263. // \p{Z} or \p{Separator}: any kind of Unicode whitespace or invisible separator.
  6264. // \p{Lo} or \p{Other_Letter}: a Unicode letter or ideograph that does not have lowercase and uppercase variants.
  6265. // \p{Lo} is needed because Chinese characters are packed next to each other without spaces in between.
  6266. if (($c != 160) AND (($c == 173) OR preg_match($this->re_spaces, $this->unichr($c)))) {
  6267. // update last blank space position
  6268. $sep = $i;
  6269. // check if is a SHY
  6270. if ($c == 173) {
  6271. $shy = true;
  6272. if ($pc == 45) {
  6273. $tmp_shy_replacement_width = 0;
  6274. $tmp_shy_replacement_char = '';
  6275. } else {
  6276. $tmp_shy_replacement_width = $shy_replacement_width;
  6277. $tmp_shy_replacement_char = $shy_replacement_char;
  6278. }
  6279. } else {
  6280. $shy = false;
  6281. }
  6282. }
  6283. // update string length
  6284. if ($this->isUnicodeFont() AND ($arabic)) {
  6285. // with bidirectional algorithm some chars may be changed affecting the line length
  6286. // *** very slow ***
  6287. $l = $this->GetArrStringWidth($this->utf8Bidi(array_slice($chars, $j, ($i - $j)), '', $this->tmprtl));
  6288. } else {
  6289. $l += $this->GetCharWidth($c);
  6290. }
  6291. if (($l > $wmax) OR (($c == 173) AND (($l + $tmp_shy_replacement_width) > $wmax)) ) {
  6292. // we have reached the end of column
  6293. if ($sep == -1) {
  6294. // check if the line was already started
  6295. if (($this->rtl AND ($this->x <= ($this->w - $this->rMargin - $chrwidth)))
  6296. OR ((!$this->rtl) AND ($this->x >= ($this->lMargin + $chrwidth)))) {
  6297. // print a void cell and go to next line
  6298. $this->Cell($w, $h, '', 0, 1);
  6299. $linebreak = true;
  6300. if ($firstline) {
  6301. return ($this->UniArrSubString($uchars, $j));
  6302. }
  6303. } else {
  6304. // truncate the word because do not fit on column
  6305. $tmpstr = $this->UniArrSubString($uchars, $j, $i);
  6306. if ($firstline) {
  6307. $startx = $this->x;
  6308. $tmparr = array_slice($chars, $j, ($i - $j));
  6309. if ($rtlmode) {
  6310. $tmparr = $this->utf8Bidi($tmparr, $tmpstr, $this->tmprtl);
  6311. }
  6312. $linew = $this->GetArrStringWidth($tmparr);
  6313. unset($tmparr);
  6314. if ($this->rtl) {
  6315. $this->endlinex = $startx - $linew;
  6316. } else {
  6317. $this->endlinex = $startx + $linew;
  6318. }
  6319. $w = $linew;
  6320. $tmpcellpadding = $this->cell_padding;
  6321. if ($maxh == 0) {
  6322. $this->SetCellPadding(0);
  6323. }
  6324. }
  6325. if ($firstblock AND $this->isRTLTextDir()) {
  6326. $tmpstr = $this->stringRightTrim($tmpstr);
  6327. }
  6328. $this->Cell($w, $h, $tmpstr, 0, 1, $align, $fill, $link, $stretch);
  6329. unset($tmpstr);
  6330. if ($firstline) {
  6331. $this->cell_padding = $tmpcellpadding;
  6332. return ($this->UniArrSubString($uchars, $i));
  6333. }
  6334. $j = $i;
  6335. --$i;
  6336. }
  6337. } else {
  6338. // word wrapping
  6339. if ($this->rtl AND (!$firstblock) AND ($sep < $i)) {
  6340. $endspace = 1;
  6341. } else {
  6342. $endspace = 0;
  6343. }
  6344. if ($shy) {
  6345. // add hypen (minus symbol) at the end of the line
  6346. $shy_width = $tmp_shy_replacement_width;
  6347. if ($this->rtl) {
  6348. $shy_char_left = $tmp_shy_replacement_char;
  6349. $shy_char_right = '';
  6350. } else {
  6351. $shy_char_left = '';
  6352. $shy_char_right = $tmp_shy_replacement_char;
  6353. }
  6354. } else {
  6355. $shy_width = 0;
  6356. $shy_char_left = '';
  6357. $shy_char_right = '';
  6358. }
  6359. $tmpstr = $this->UniArrSubString($uchars, $j, ($sep + $endspace));
  6360. if ($firstline) {
  6361. $startx = $this->x;
  6362. $tmparr = array_slice($chars, $j, (($sep + $endspace) - $j));
  6363. if ($rtlmode) {
  6364. $tmparr = $this->utf8Bidi($tmparr, $tmpstr, $this->tmprtl);
  6365. }
  6366. $linew = $this->GetArrStringWidth($tmparr);
  6367. unset($tmparr);
  6368. if ($this->rtl) {
  6369. $this->endlinex = $startx - $linew - $shy_width;
  6370. } else {
  6371. $this->endlinex = $startx + $linew + $shy_width;
  6372. }
  6373. $w = $linew;
  6374. $tmpcellpadding = $this->cell_padding;
  6375. if ($maxh == 0) {
  6376. $this->SetCellPadding(0);
  6377. }
  6378. }
  6379. // print the line
  6380. if ($firstblock AND $this->isRTLTextDir()) {
  6381. $tmpstr = $this->stringRightTrim($tmpstr);
  6382. }
  6383. $this->Cell($w, $h, $shy_char_left.$tmpstr.$shy_char_right, 0, 1, $align, $fill, $link, $stretch);
  6384. unset($tmpstr);
  6385. if ($firstline) {
  6386. // return the remaining text
  6387. $this->cell_padding = $tmpcellpadding;
  6388. return ($this->UniArrSubString($uchars, ($sep + $endspace)));
  6389. }
  6390. $i = $sep;
  6391. $sep = -1;
  6392. $shy = false;
  6393. $j = ($i+1);
  6394. }
  6395. // account for margin changes
  6396. if ((($this->y + $this->lasth) > $this->PageBreakTrigger) AND (!$this->InFooter)) {
  6397. $this->AcceptPageBreak();
  6398. if ($this->rtl) {
  6399. $this->x -= $margin['R'];
  6400. } else {
  6401. $this->x += $margin['L'];
  6402. }
  6403. $this->lMargin += $margin['L'];
  6404. $this->rMargin += $margin['R'];
  6405. }
  6406. $w = $this->getRemainingWidth();
  6407. $wmax = $w - $this->cell_padding['L'] - $this->cell_padding['R'];
  6408. if ($linebreak) {
  6409. $linebreak = false;
  6410. } else {
  6411. ++$nl;
  6412. $l = 0;
  6413. }
  6414. }
  6415. }
  6416. // save last character
  6417. $pc = $c;
  6418. ++$i;
  6419. } // end while i < nb
  6420. // print last substring (if any)
  6421. if ($l > 0) {
  6422. switch ($align) {
  6423. case 'J':
  6424. case 'C': {
  6425. $w = $w;
  6426. break;
  6427. }
  6428. case 'L': {
  6429. if ($this->rtl) {
  6430. $w = $w;
  6431. } else {
  6432. $w = $l;
  6433. }
  6434. break;
  6435. }
  6436. case 'R': {
  6437. if ($this->rtl) {
  6438. $w = $l;
  6439. } else {
  6440. $w = $w;
  6441. }
  6442. break;
  6443. }
  6444. default: {
  6445. $w = $l;
  6446. break;
  6447. }
  6448. }
  6449. $tmpstr = $this->UniArrSubString($uchars, $j, $nb);
  6450. if ($firstline) {
  6451. $startx = $this->x;
  6452. $tmparr = array_slice($chars, $j, ($nb - $j));
  6453. if ($rtlmode) {
  6454. $tmparr = $this->utf8Bidi($tmparr, $tmpstr, $this->tmprtl);
  6455. }
  6456. $linew = $this->GetArrStringWidth($tmparr);
  6457. unset($tmparr);
  6458. if ($this->rtl) {
  6459. $this->endlinex = $startx - $linew;
  6460. } else {
  6461. $this->endlinex = $startx + $linew;
  6462. }
  6463. $w = $linew;
  6464. $tmpcellpadding = $this->cell_padding;
  6465. if ($maxh == 0) {
  6466. $this->SetCellPadding(0);
  6467. }
  6468. }
  6469. if ($firstblock AND $this->isRTLTextDir()) {
  6470. $tmpstr = $this->stringRightTrim($tmpstr);
  6471. }
  6472. $this->Cell($w, $h, $tmpstr, 0, $ln, $align, $fill, $link, $stretch);
  6473. unset($tmpstr);
  6474. if ($firstline) {
  6475. $this->cell_padding = $tmpcellpadding;
  6476. return ($this->UniArrSubString($uchars, $nb));
  6477. }
  6478. ++$nl;
  6479. }
  6480. if ($firstline) {
  6481. return '';
  6482. }
  6483. return $nl;
  6484. }
  6485. /**
  6486. * Returns the remaining width between the current position and margins.
  6487. * @return int Return the remaining width
  6488. * @access protected
  6489. */
  6490. protected function getRemainingWidth() {
  6491. $this->checkPageRegions();
  6492. if ($this->rtl) {
  6493. return ($this->x - $this->lMargin);
  6494. } else {
  6495. return ($this->w - $this->rMargin - $this->x);
  6496. }
  6497. }
  6498. /**
  6499. * Extract a slice of the $strarr array and return it as string.
  6500. * @param string $strarr The input array of characters.
  6501. * @param int $start the starting element of $strarr.
  6502. * @param int $end first element that will not be returned.
  6503. * @return Return part of a string
  6504. * @access public
  6505. */
  6506. public function UTF8ArrSubString($strarr, $start='', $end='') {
  6507. if (strlen($start) == 0) {
  6508. $start = 0;
  6509. }
  6510. if (strlen($end) == 0) {
  6511. $end = count($strarr);
  6512. }
  6513. $string = '';
  6514. for ($i=$start; $i < $end; ++$i) {
  6515. $string .= $this->unichr($strarr[$i]);
  6516. }
  6517. return $string;
  6518. }
  6519. /**
  6520. * Extract a slice of the $uniarr array and return it as string.
  6521. * @param string $uniarr The input array of characters.
  6522. * @param int $start the starting element of $strarr.
  6523. * @param int $end first element that will not be returned.
  6524. * @return Return part of a string
  6525. * @access public
  6526. * @since 4.5.037 (2009-04-07)
  6527. */
  6528. public function UniArrSubString($uniarr, $start='', $end='') {
  6529. if (strlen($start) == 0) {
  6530. $start = 0;
  6531. }
  6532. if (strlen($end) == 0) {
  6533. $end = count($uniarr);
  6534. }
  6535. $string = '';
  6536. for ($i=$start; $i < $end; ++$i) {
  6537. $string .= $uniarr[$i];
  6538. }
  6539. return $string;
  6540. }
  6541. /**
  6542. * Convert an array of UTF8 values to array of unicode characters
  6543. * @param string $ta The input array of UTF8 values.
  6544. * @return Return array of unicode characters
  6545. * @access public
  6546. * @since 4.5.037 (2009-04-07)
  6547. */
  6548. public function UTF8ArrayToUniArray($ta) {
  6549. return array_map(array($this, 'unichr'), $ta);
  6550. }
  6551. /**
  6552. * Returns the unicode caracter specified by UTF-8 value
  6553. * @param int $c UTF-8 value
  6554. * @return Returns the specified character.
  6555. * @author Miguel Perez, Nicola Asuni
  6556. * @access public
  6557. * @since 2.3.000 (2008-03-05)
  6558. */
  6559. public function unichr($c) {
  6560. if (!$this->isunicode) {
  6561. return chr($c);
  6562. } elseif ($c <= 0x7F) {
  6563. // one byte
  6564. return chr($c);
  6565. } elseif ($c <= 0x7FF) {
  6566. // two bytes
  6567. return chr(0xC0 | $c >> 6).chr(0x80 | $c & 0x3F);
  6568. } elseif ($c <= 0xFFFF) {
  6569. // three bytes
  6570. return chr(0xE0 | $c >> 12).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F);
  6571. } elseif ($c <= 0x10FFFF) {
  6572. // four bytes
  6573. return chr(0xF0 | $c >> 18).chr(0x80 | $c >> 12 & 0x3F).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F);
  6574. } else {
  6575. return '';
  6576. }
  6577. }
  6578. /**
  6579. * Return the image type given the file name or array returned by getimagesize() function.
  6580. * @param string $imgfile image file name
  6581. * @param array $iminfo array of image information returned by getimagesize() function.
  6582. * @return string image type
  6583. * @since 4.8.017 (2009-11-27)
  6584. */
  6585. public function getImageFileType($imgfile, $iminfo=array()) {
  6586. $type = '';
  6587. if (isset($iminfo['mime']) AND !empty($iminfo['mime'])) {
  6588. $mime = explode('/', $iminfo['mime']);
  6589. if ((count($mime) > 1) AND ($mime[0] == 'image') AND (!empty($mime[1]))) {
  6590. $type = strtolower(trim($mime[1]));
  6591. }
  6592. }
  6593. if (empty($type)) {
  6594. $fileinfo = pathinfo($imgfile);
  6595. if (isset($fileinfo['extension']) AND (!$this->empty_string($fileinfo['extension']))) {
  6596. $type = strtolower(trim($fileinfo['extension']));
  6597. }
  6598. }
  6599. if ($type == 'jpg') {
  6600. $type = 'jpeg';
  6601. }
  6602. return $type;
  6603. }
  6604. /**
  6605. * Set the block dimensions accounting for page breaks and page/column fitting
  6606. * @param float $w width
  6607. * @param float $h height
  6608. * @param float $x X coordinate
  6609. * @param float $y Y coodiante
  6610. * @param boolean $fitonpage if true the block is resized to not exceed page dimensions.
  6611. * @access protected
  6612. * @since 5.5.009 (2010-07-05)
  6613. */
  6614. protected function fitBlock(&$w, &$h, &$x, &$y, $fitonpage=false) {
  6615. // resize the block to be vertically contained on a single page or single column
  6616. if ($fitonpage OR $this->AutoPageBreak) {
  6617. $ratio_wh = ($w / $h);
  6618. if ($h > ($this->PageBreakTrigger - $this->tMargin)) {
  6619. $h = $this->PageBreakTrigger - $this->tMargin;
  6620. $w = ($h * $ratio_wh);
  6621. }
  6622. // resize the block to be horizontally contained on a single page or single column
  6623. if ($fitonpage) {
  6624. $maxw = ($this->w - $this->lMargin - $this->rMargin);
  6625. if ($w > $maxw) {
  6626. $w = $maxw;
  6627. $h = ($w / $ratio_wh);
  6628. }
  6629. }
  6630. }
  6631. // Check whether we need a new page or new column first as this does not fit
  6632. $prev_x = $this->x;
  6633. $prev_y = $this->y;
  6634. if ($this->checkPageBreak($h, $y) OR ($this->y < $prev_y)) {
  6635. $y = $this->y;
  6636. if ($this->rtl) {
  6637. $x += ($prev_x - $this->x);
  6638. } else {
  6639. $x += ($this->x - $prev_x);
  6640. }
  6641. }
  6642. // resize the block to be contained on the remaining available page or column space
  6643. if ($fitonpage) {
  6644. $ratio_wh = ($w / $h);
  6645. if (($y + $h) > $this->PageBreakTrigger) {
  6646. $h = $this->PageBreakTrigger - $y;
  6647. $w = ($h * $ratio_wh);
  6648. }
  6649. if ((!$this->rtl) AND (($x + $w) > ($this->w - $this->rMargin))) {
  6650. $w = $this->w - $this->rMargin - $x;
  6651. $h = ($w / $ratio_wh);
  6652. } elseif (($this->rtl) AND (($x - $w) < ($this->lMargin))) {
  6653. $w = $x - $this->lMargin;
  6654. $h = ($w / $ratio_wh);
  6655. }
  6656. }
  6657. }
  6658. /**
  6659. * Puts an image in the page.
  6660. * The upper-left corner must be given.
  6661. * The dimensions can be specified in different ways:<ul>
  6662. * <li>explicit width and height (expressed in user unit)</li>
  6663. * <li>one explicit dimension, the other being calculated automatically in order to keep the original proportions</li>
  6664. * <li>no explicit dimension, in which case the image is put at 72 dpi</li></ul>
  6665. * Supported formats are JPEG and PNG images whitout GD library and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;
  6666. * The format can be specified explicitly or inferred from the file extension.<br />
  6667. * It is possible to put a link on the image.<br />
  6668. * Remark: if an image is used several times, only one copy will be embedded in the file.<br />
  6669. * @param string $file Name of the file containing the image.
  6670. * @param float $x Abscissa of the upper-left corner (LTR) or upper-right corner (RTL).
  6671. * @param float $y Ordinate of the upper-left corner (LTR) or upper-right corner (RTL).
  6672. * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
  6673. * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
  6674. * @param string $type Image format. Possible values are (case insensitive): JPEG and PNG (whitout GD library) and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;. If not specified, the type is inferred from the file extension.
  6675. * @param mixed $link URL or identifier returned by AddLink().
  6676. * @param string $align Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
  6677. * @param mixed $resize If true resize (reduce) the image to fit $w and $h (requires GD or ImageMagick library); if false do not resize; if 2 force resize in all cases (upscaling and downscaling).
  6678. * @param int $dpi dot-per-inch resolution used on resize
  6679. * @param string $palign Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
  6680. * @param boolean $ismask true if this image is a mask, false otherwise
  6681. * @param mixed $imgmask image object returned by this function or false
  6682. * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
  6683. * @param boolean $fitbox If true scale image dimensions proportionally to fit within the ($w, $h) box.
  6684. * @param boolean $hidden if true do not display the image.
  6685. * @param boolean $fitonpage if true the image is resized to not exceed page dimensions.
  6686. * @return image information
  6687. * @access public
  6688. * @since 1.1
  6689. */
  6690. 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) {
  6691. if ($x === '') {
  6692. $x = $this->x;
  6693. }
  6694. if ($y === '') {
  6695. $y = $this->y;
  6696. }
  6697. // check page for no-write regions and adapt page margins if necessary
  6698. $this->checkPageRegions($h, $x, $y);
  6699. $cached_file = false; // true when the file is cached
  6700. // get image dimensions
  6701. $imsize = @getimagesize($file);
  6702. if ($imsize === FALSE) {
  6703. // try to encode spaces on filename
  6704. $file = str_replace(' ', '%20', $file);
  6705. $imsize = @getimagesize($file);
  6706. if ($imsize === FALSE) {
  6707. if (function_exists('curl_init')) {
  6708. // try to get remote file data using cURL
  6709. $cs = curl_init(); // curl session
  6710. curl_setopt($cs, CURLOPT_URL, $file);
  6711. curl_setopt($cs, CURLOPT_BINARYTRANSFER, true);
  6712. curl_setopt($cs, CURLOPT_FAILONERROR, true);
  6713. curl_setopt($cs, CURLOPT_RETURNTRANSFER, true);
  6714. curl_setopt($cs, CURLOPT_CONNECTTIMEOUT, 5);
  6715. curl_setopt($cs, CURLOPT_TIMEOUT, 30);
  6716. $imgdata = curl_exec($cs);
  6717. curl_close($cs);
  6718. if($imgdata !== FALSE) {
  6719. // copy image to cache
  6720. $file = tempnam(K_PATH_CACHE, 'img_');
  6721. $fp = fopen($file, 'w');
  6722. fwrite($fp, $imgdata);
  6723. fclose($fp);
  6724. unset($imgdata);
  6725. $cached_file = true;
  6726. $imsize = @getimagesize($file);
  6727. if ($imsize === FALSE) {
  6728. unlink($file);
  6729. $cached_file = false;
  6730. }
  6731. }
  6732. } elseif (($w > 0) AND ($h > 0)) {
  6733. // get measures from specified data
  6734. $pw = $this->getHTMLUnitToUnits($w, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
  6735. $ph = $this->getHTMLUnitToUnits($h, 0, $this->pdfunit, true) * $this->imgscale * $this->k;
  6736. $imsize = array($pw, $ph);
  6737. }
  6738. }
  6739. }
  6740. if ($imsize === FALSE) {
  6741. $this->Error('[Image] Unable to get image: '.$file);
  6742. }
  6743. // get original image width and height in pixels
  6744. list($pixw, $pixh) = $imsize;
  6745. // calculate image width and height on document
  6746. if (($w <= 0) AND ($h <= 0)) {
  6747. // convert image size to document unit
  6748. $w = $this->pixelsToUnits($pixw);
  6749. $h = $this->pixelsToUnits($pixh);
  6750. } elseif ($w <= 0) {
  6751. $w = $h * $pixw / $pixh;
  6752. } elseif ($h <= 0) {
  6753. $h = $w * $pixh / $pixw;
  6754. } elseif ($fitbox AND ($w > 0) AND ($h > 0)) {
  6755. // scale image dimensions proportionally to fit within the ($w, $h) box
  6756. if ((($w * $pixh) / ($h * $pixw)) < 1) {
  6757. $h = $w * $pixh / $pixw;
  6758. } else {
  6759. $w = $h * $pixw / $pixh;
  6760. }
  6761. }
  6762. // fit the image on available space
  6763. $this->fitBlock($w, $h, $x, $y, $fitonpage);
  6764. // calculate new minimum dimensions in pixels
  6765. $neww = round($w * $this->k * $dpi / $this->dpi);
  6766. $newh = round($h * $this->k * $dpi / $this->dpi);
  6767. // check if resize is necessary (resize is used only to reduce the image)
  6768. $newsize = ($neww * $newh);
  6769. $pixsize = ($pixw * $pixh);
  6770. if (intval($resize) == 2) {
  6771. $resize = true;
  6772. } elseif ($newsize >= $pixsize) {
  6773. $resize = false;
  6774. }
  6775. // check if image has been already added on document
  6776. $newimage = true;
  6777. if (in_array($file, $this->imagekeys)) {
  6778. $newimage = false;
  6779. // get existing image data
  6780. $info = $this->getImageBuffer($file);
  6781. // check if the newer image is larger
  6782. $oldsize = ($info['w'] * $info['h']);
  6783. if ((($oldsize < $newsize) AND ($resize)) OR (($oldsize < $pixsize) AND (!$resize))) {
  6784. $newimage = true;
  6785. }
  6786. }
  6787. if ($newimage) {
  6788. //First use of image, get info
  6789. $type = strtolower($type);
  6790. if ($type == '') {
  6791. $type = $this->getImageFileType($file, $imsize);
  6792. } elseif ($type == 'jpg') {
  6793. $type = 'jpeg';
  6794. }
  6795. $mqr = $this->get_mqr();
  6796. $this->set_mqr(false);
  6797. // Specific image handlers
  6798. $mtd = '_parse'.$type;
  6799. // GD image handler function
  6800. $gdfunction = 'imagecreatefrom'.$type;
  6801. $info = false;
  6802. if ((method_exists($this, $mtd)) AND (!($resize AND function_exists($gdfunction)))) {
  6803. // TCPDF image functions
  6804. $info = $this->$mtd($file);
  6805. if ($info == 'pngalpha') {
  6806. return $this->ImagePngAlpha($file, $x, $y, $pixw, $pixh, $w, $h, 'PNG', $link, $align, $resize, $dpi, $palign);
  6807. }
  6808. }
  6809. if (!$info) {
  6810. if (function_exists($gdfunction)) {
  6811. // GD library
  6812. $img = $gdfunction($file);
  6813. if ($resize) {
  6814. $imgr = imagecreatetruecolor($neww, $newh);
  6815. if (($type == 'gif') OR ($type == 'png')) {
  6816. $imgr = $this->_setGDImageTransparency($imgr, $img);
  6817. }
  6818. imagecopyresampled($imgr, $img, 0, 0, 0, 0, $neww, $newh, $pixw, $pixh);
  6819. if (($type == 'gif') OR ($type == 'png')) {
  6820. $info = $this->_toPNG($imgr);
  6821. } else {
  6822. $info = $this->_toJPEG($imgr);
  6823. }
  6824. } else {
  6825. if (($type == 'gif') OR ($type == 'png')) {
  6826. $info = $this->_toPNG($img);
  6827. } else {
  6828. $info = $this->_toJPEG($img);
  6829. }
  6830. }
  6831. } elseif (extension_loaded('imagick')) {
  6832. // ImageMagick library
  6833. $img = new Imagick();
  6834. if ($type == 'SVG') {
  6835. // get SVG file content
  6836. $svgimg = file_get_contents($file);
  6837. // get width and height
  6838. $regs = array();
  6839. if (preg_match('/<svg([^\>]*)>/si', $svgimg, $regs)) {
  6840. $svgtag = $regs[1];
  6841. $tmp = array();
  6842. if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
  6843. $ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
  6844. $owu = sprintf('%.3F', ($ow * $dpi / 72)).$this->pdfunit;
  6845. $svgtag = preg_replace('/[\s]+width[\s]*=[\s]*"[^"]*"/si', ' width="'.$owu.'"', $svgtag, 1);
  6846. } else {
  6847. $ow = $w;
  6848. }
  6849. $tmp = array();
  6850. if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $svgtag, $tmp)) {
  6851. $oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
  6852. $ohu = sprintf('%.3F', ($oh * $dpi / 72)).$this->pdfunit;
  6853. $svgtag = preg_replace('/[\s]+height[\s]*=[\s]*"[^"]*"/si', ' height="'.$ohu.'"', $svgtag, 1);
  6854. } else {
  6855. $oh = $h;
  6856. }
  6857. $tmp = array();
  6858. if (!preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $svgtag, $tmp)) {
  6859. $vbw = ($ow * $this->imgscale * $this->k);
  6860. $vbh = ($oh * $this->imgscale * $this->k);
  6861. $vbox = sprintf(' viewBox="0 0 %.3F %.3F" ', $vbw, $vbh);
  6862. $svgtag = $vbox.$svgtag;
  6863. }
  6864. $svgimg = preg_replace('/<svg([^\>]*)>/si', '<svg'.$svgtag.'>', $svgimg, 1);
  6865. }
  6866. $img->readImageBlob($svgimg);
  6867. } else {
  6868. $img->readImage($file);
  6869. }
  6870. if ($resize) {
  6871. $img->resizeImage($neww, $newh, 10, 1, false);
  6872. }
  6873. $img->setCompressionQuality($this->jpeg_quality);
  6874. $img->setImageFormat('jpeg');
  6875. $tempname = tempnam(K_PATH_CACHE, 'jpg_');
  6876. $img->writeImage($tempname);
  6877. $info = $this->_parsejpeg($tempname);
  6878. unlink($tempname);
  6879. $img->destroy();
  6880. } else {
  6881. return;
  6882. }
  6883. }
  6884. if ($info === false) {
  6885. //If false, we cannot process image
  6886. return;
  6887. }
  6888. $this->set_mqr($mqr);
  6889. if ($ismask) {
  6890. // force grayscale
  6891. $info['cs'] = 'DeviceGray';
  6892. }
  6893. $info['i'] = $this->numimages;
  6894. if (!in_array($file, $this->imagekeys)) {
  6895. ++$info['i'];
  6896. }
  6897. if ($imgmask !== false) {
  6898. $info['masked'] = $imgmask;
  6899. }
  6900. // add image to document
  6901. $this->setImageBuffer($file, $info);
  6902. }
  6903. if ($cached_file) {
  6904. // remove cached file
  6905. unlink($file);
  6906. }
  6907. // set alignment
  6908. $this->img_rb_y = $y + $h;
  6909. // set alignment
  6910. if ($this->rtl) {
  6911. if ($palign == 'L') {
  6912. $ximg = $this->lMargin;
  6913. } elseif ($palign == 'C') {
  6914. $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
  6915. } elseif ($palign == 'R') {
  6916. $ximg = $this->w - $this->rMargin - $w;
  6917. } else {
  6918. $ximg = $x - $w;
  6919. }
  6920. $this->img_rb_x = $ximg;
  6921. } else {
  6922. if ($palign == 'L') {
  6923. $ximg = $this->lMargin;
  6924. } elseif ($palign == 'C') {
  6925. $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
  6926. } elseif ($palign == 'R') {
  6927. $ximg = $this->w - $this->rMargin - $w;
  6928. } else {
  6929. $ximg = $x;
  6930. }
  6931. $this->img_rb_x = $ximg + $w;
  6932. }
  6933. if ($ismask OR $hidden) {
  6934. // image is not displayed
  6935. return $info['i'];
  6936. }
  6937. $xkimg = $ximg * $this->k;
  6938. $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']));
  6939. if (!empty($border)) {
  6940. $bx = $this->x;
  6941. $by = $this->y;
  6942. $this->x = $ximg;
  6943. if ($this->rtl) {
  6944. $this->x += $w;
  6945. }
  6946. $this->y = $y;
  6947. $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
  6948. $this->x = $bx;
  6949. $this->y = $by;
  6950. }
  6951. if ($link) {
  6952. $this->Link($ximg, $y, $w, $h, $link, 0);
  6953. }
  6954. // set pointer to align the next text/objects
  6955. switch($align) {
  6956. case 'T': {
  6957. $this->y = $y;
  6958. $this->x = $this->img_rb_x;
  6959. break;
  6960. }
  6961. case 'M': {
  6962. $this->y = $y + round($h/2);
  6963. $this->x = $this->img_rb_x;
  6964. break;
  6965. }
  6966. case 'B': {
  6967. $this->y = $this->img_rb_y;
  6968. $this->x = $this->img_rb_x;
  6969. break;
  6970. }
  6971. case 'N': {
  6972. $this->SetY($this->img_rb_y);
  6973. break;
  6974. }
  6975. default:{
  6976. break;
  6977. }
  6978. }
  6979. $this->endlinex = $this->img_rb_x;
  6980. if ($this->inxobj) {
  6981. // we are inside an XObject template
  6982. $this->xobjects[$this->xobjid]['images'][] = $info['i'];
  6983. }
  6984. return $info['i'];
  6985. }
  6986. /**
  6987. * Sets the current active configuration setting of magic_quotes_runtime (if the set_magic_quotes_runtime function exist)
  6988. * @param boolean $mqr FALSE for off, TRUE for on.
  6989. * @since 4.6.025 (2009-08-17)
  6990. */
  6991. public function set_mqr($mqr) {
  6992. if(!defined('PHP_VERSION_ID')) {
  6993. $version = PHP_VERSION;
  6994. define('PHP_VERSION_ID', (($version{0} * 10000) + ($version{2} * 100) + $version{4}));
  6995. }
  6996. if (PHP_VERSION_ID < 50300) {
  6997. @set_magic_quotes_runtime($mqr);
  6998. }
  6999. }
  7000. /**
  7001. * Gets the current active configuration setting of magic_quotes_runtime (if the get_magic_quotes_runtime function exist)
  7002. * @return Returns 0 if magic quotes runtime is off or get_magic_quotes_runtime doesn't exist, 1 otherwise.
  7003. * @since 4.6.025 (2009-08-17)
  7004. */
  7005. public function get_mqr() {
  7006. if(!defined('PHP_VERSION_ID')) {
  7007. $version = PHP_VERSION;
  7008. define('PHP_VERSION_ID', (($version{0} * 10000) + ($version{2} * 100) + $version{4}));
  7009. }
  7010. if (PHP_VERSION_ID < 50300) {
  7011. return @get_magic_quotes_runtime();
  7012. }
  7013. return 0;
  7014. }
  7015. /**
  7016. * Convert the loaded image to a JPEG and then return a structure for the PDF creator.
  7017. * This function requires GD library and write access to the directory defined on K_PATH_CACHE constant.
  7018. * @param string $file Image file name.
  7019. * @param image $image Image object.
  7020. * return image JPEG image object.
  7021. * @access protected
  7022. */
  7023. protected function _toJPEG($image) {
  7024. $tempname = tempnam(K_PATH_CACHE, 'jpg_');
  7025. imagejpeg($image, $tempname, $this->jpeg_quality);
  7026. imagedestroy($image);
  7027. $retvars = $this->_parsejpeg($tempname);
  7028. // tidy up by removing temporary image
  7029. unlink($tempname);
  7030. return $retvars;
  7031. }
  7032. /**
  7033. * Convert the loaded image to a PNG and then return a structure for the PDF creator.
  7034. * This function requires GD library and write access to the directory defined on K_PATH_CACHE constant.
  7035. * @param string $file Image file name.
  7036. * @param image $image Image object.
  7037. * return image PNG image object.
  7038. * @access protected
  7039. * @since 4.9.016 (2010-04-20)
  7040. */
  7041. protected function _toPNG($image) {
  7042. $tempname = tempnam(K_PATH_CACHE, 'jpg_');
  7043. imagepng($image, $tempname);
  7044. imagedestroy($image);
  7045. $retvars = $this->_parsepng($tempname);
  7046. // tidy up by removing temporary image
  7047. unlink($tempname);
  7048. return $retvars;
  7049. }
  7050. /**
  7051. * Set the transparency for the given GD image.
  7052. * @param image $new_image GD image object
  7053. * @param image $image GD image object.
  7054. * return GD image object.
  7055. * @access protected
  7056. * @since 4.9.016 (2010-04-20)
  7057. */
  7058. protected function _setGDImageTransparency($new_image, $image) {
  7059. // transparency index
  7060. $tid = imagecolortransparent($image);
  7061. // default transparency color
  7062. $tcol = array('red' => 255, 'green' => 255, 'blue' => 255);
  7063. if ($tid >= 0) {
  7064. // get the colors for the transparency index
  7065. $tcol = imagecolorsforindex($image, $tid);
  7066. }
  7067. $tid = imagecolorallocate($new_image, $tcol['red'], $tcol['green'], $tcol['blue']);
  7068. imagefill($new_image, 0, 0, $tid);
  7069. imagecolortransparent($new_image, $tid);
  7070. return $new_image;
  7071. }
  7072. /**
  7073. * Extract info from a JPEG file without using the GD library.
  7074. * @param string $file image file to parse
  7075. * @return array structure containing the image data
  7076. * @access protected
  7077. */
  7078. protected function _parsejpeg($file) {
  7079. $a = getimagesize($file);
  7080. if (empty($a)) {
  7081. $this->Error('Missing or incorrect image file: '.$file);
  7082. }
  7083. if ($a[2] != 2) {
  7084. $this->Error('Not a JPEG file: '.$file);
  7085. }
  7086. if ((!isset($a['channels'])) OR ($a['channels'] == 3)) {
  7087. $colspace = 'DeviceRGB';
  7088. } elseif ($a['channels'] == 4) {
  7089. $colspace = 'DeviceCMYK';
  7090. } else {
  7091. $colspace = 'DeviceGray';
  7092. }
  7093. $bpc = isset($a['bits']) ? $a['bits'] : 8;
  7094. $data = file_get_contents($file);
  7095. return array('w' => $a[0], 'h' => $a[1], 'cs' => $colspace, 'bpc' => $bpc, 'f' => 'DCTDecode', 'data' => $data);
  7096. }
  7097. /**
  7098. * Extract info from a PNG file without using the GD library.
  7099. * @param string $file image file to parse
  7100. * @return array structure containing the image data
  7101. * @access protected
  7102. */
  7103. protected function _parsepng($file) {
  7104. $f = fopen($file, 'rb');
  7105. if ($f === false) {
  7106. $this->Error('Can\'t open image file: '.$file);
  7107. }
  7108. //Check signature
  7109. if (fread($f, 8) != chr(137).'PNG'.chr(13).chr(10).chr(26).chr(10)) {
  7110. $this->Error('Not a PNG file: '.$file);
  7111. }
  7112. //Read header chunk
  7113. fread($f, 4);
  7114. if (fread($f, 4) != 'IHDR') {
  7115. $this->Error('Incorrect PNG file: '.$file);
  7116. }
  7117. $w = $this->_freadint($f);
  7118. $h = $this->_freadint($f);
  7119. $bpc = ord(fread($f, 1));
  7120. if ($bpc > 8) {
  7121. //$this->Error('16-bit depth not supported: '.$file);
  7122. fclose($f);
  7123. return false;
  7124. }
  7125. $ct = ord(fread($f, 1));
  7126. if ($ct == 0) {
  7127. $colspace = 'DeviceGray';
  7128. } elseif ($ct == 2) {
  7129. $colspace = 'DeviceRGB';
  7130. } elseif ($ct == 3) {
  7131. $colspace = 'Indexed';
  7132. } else {
  7133. // alpha channel
  7134. fclose($f);
  7135. return 'pngalpha';
  7136. }
  7137. if (ord(fread($f, 1)) != 0) {
  7138. //$this->Error('Unknown compression method: '.$file);
  7139. fclose($f);
  7140. return false;
  7141. }
  7142. if (ord(fread($f, 1)) != 0) {
  7143. //$this->Error('Unknown filter method: '.$file);
  7144. fclose($f);
  7145. return false;
  7146. }
  7147. if (ord(fread($f, 1)) != 0) {
  7148. //$this->Error('Interlacing not supported: '.$file);
  7149. fclose($f);
  7150. return false;
  7151. }
  7152. fread($f, 4);
  7153. $parms = '/DecodeParms << /Predictor 15 /Colors '.($ct == 2 ? 3 : 1).' /BitsPerComponent '.$bpc.' /Columns '.$w.' >>';
  7154. //Scan chunks looking for palette, transparency and image data
  7155. $pal = '';
  7156. $trns = '';
  7157. $data = '';
  7158. do {
  7159. $n = $this->_freadint($f);
  7160. $type = fread($f, 4);
  7161. if ($type == 'PLTE') {
  7162. //Read palette
  7163. $pal = $this->rfread($f, $n);
  7164. fread($f, 4);
  7165. } elseif ($type == 'tRNS') {
  7166. //Read transparency info
  7167. $t = $this->rfread($f, $n);
  7168. if ($ct == 0) {
  7169. $trns = array(ord(substr($t, 1, 1)));
  7170. } elseif ($ct == 2) {
  7171. $trns = array(ord(substr($t, 1, 1)), ord(substr($t, 3, 1)), ord(substr($t, 5, 1)));
  7172. } else {
  7173. $pos = strpos($t, chr(0));
  7174. if ($pos !== false) {
  7175. $trns = array($pos);
  7176. }
  7177. }
  7178. fread($f, 4);
  7179. } elseif ($type == 'IDAT') {
  7180. //Read image data block
  7181. $data .= $this->rfread($f, $n);
  7182. fread($f, 4);
  7183. } elseif ($type == 'IEND') {
  7184. break;
  7185. } else {
  7186. $this->rfread($f, $n + 4);
  7187. }
  7188. } while ($n);
  7189. if (($colspace == 'Indexed') AND (empty($pal))) {
  7190. //$this->Error('Missing palette in '.$file);
  7191. fclose($f);
  7192. return false;
  7193. }
  7194. fclose($f);
  7195. return array('w' => $w, 'h' => $h, 'cs' => $colspace, 'bpc' => $bpc, 'f' => 'FlateDecode', 'parms' => $parms, 'pal' => $pal, 'trns' => $trns, 'data' => $data);
  7196. }
  7197. /**
  7198. * Binary-safe and URL-safe file read.
  7199. * Reads up to length bytes from the file pointer referenced by handle. Reading stops as soon as one of the following conditions is met: length bytes have been read; EOF (end of file) is reached.
  7200. * @param resource $handle
  7201. * @param int $length
  7202. * @return Returns the read string or FALSE in case of error.
  7203. * @author Nicola Asuni
  7204. * @access protected
  7205. * @since 4.5.027 (2009-03-16)
  7206. */
  7207. protected function rfread($handle, $length) {
  7208. $data = fread($handle, $length);
  7209. if ($data === false) {
  7210. return false;
  7211. }
  7212. $rest = $length - strlen($data);
  7213. if ($rest > 0) {
  7214. $data .= $this->rfread($handle, $rest);
  7215. }
  7216. return $data;
  7217. }
  7218. /**
  7219. * Extract info from a PNG image with alpha channel using the GD library.
  7220. * @param string $file Name of the file containing the image.
  7221. * @param float $x Abscissa of the upper-left corner.
  7222. * @param float $y Ordinate of the upper-left corner.
  7223. * @param float $wpx Original width of the image in pixels.
  7224. * @param float $hpx original height of the image in pixels.
  7225. * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
  7226. * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
  7227. * @param string $type Image format. Possible values are (case insensitive): JPEG and PNG (whitout GD library) and all images supported by GD: GD, GD2, GD2PART, GIF, JPEG, PNG, BMP, XBM, XPM;. If not specified, the type is inferred from the file extension.
  7228. * @param mixed $link URL or identifier returned by AddLink().
  7229. * @param string $align Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
  7230. * @param boolean $resize If true resize (reduce) the image to fit $w and $h (requires GD library).
  7231. * @param int $dpi dot-per-inch resolution used on resize
  7232. * @param string $palign Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
  7233. * @author Nicola Asuni
  7234. * @access protected
  7235. * @since 4.3.007 (2008-12-04)
  7236. * @see Image()
  7237. */
  7238. protected function ImagePngAlpha($file, $x, $y, $wpx, $hpx, $w, $h, $type, $link, $align, $resize, $dpi, $palign) {
  7239. // create temp image file (without alpha channel)
  7240. $tempfile_plain = tempnam(K_PATH_CACHE, 'mskp_');
  7241. // create temp alpha file
  7242. $tempfile_alpha = tempnam(K_PATH_CACHE, 'mska_');
  7243. if (extension_loaded('imagick')) { // ImageMagick
  7244. // ImageMagick library
  7245. $img = new Imagick();
  7246. $img->readImage($file);
  7247. // clone image object
  7248. $imga = $img->clone();
  7249. // extract alpha channel
  7250. $img->separateImageChannel(imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE);
  7251. $img->negateImage(true);
  7252. $img->setImageFormat('png');
  7253. $img->writeImage($tempfile_alpha);
  7254. // remove alpha channel
  7255. $imga->separateImageChannel(imagick::CHANNEL_ALL & ~(imagick::CHANNEL_ALPHA | imagick::CHANNEL_OPACITY | imagick::CHANNEL_MATTE));
  7256. $imga->setImageFormat('png');
  7257. $imga->writeImage($tempfile_plain);
  7258. } else { // GD library
  7259. // generate images
  7260. $img = imagecreatefrompng($file);
  7261. $imgalpha = imagecreate($wpx, $hpx);
  7262. // generate gray scale palette (0 -> 255)
  7263. for ($c = 0; $c < 256; ++$c) {
  7264. ImageColorAllocate($imgalpha, $c, $c, $c);
  7265. }
  7266. // extract alpha channel
  7267. for ($xpx = 0; $xpx < $wpx; ++$xpx) {
  7268. for ($ypx = 0; $ypx < $hpx; ++$ypx) {
  7269. $color = imagecolorat($img, $xpx, $ypx);
  7270. $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)
  7271. $alpha = (((127 - $alpha) / 127) * 255); // GD alpha is only 7 bit (0 -> 127)
  7272. $alpha = $this->getGDgamma($alpha); // correct gamma
  7273. imagesetpixel($imgalpha, $xpx, $ypx, $alpha);
  7274. }
  7275. }
  7276. imagepng($imgalpha, $tempfile_alpha);
  7277. imagedestroy($imgalpha);
  7278. // extract image without alpha channel
  7279. $imgplain = imagecreatetruecolor($wpx, $hpx);
  7280. imagecopy($imgplain, $img, 0, 0, 0, 0, $wpx, $hpx);
  7281. imagepng($imgplain, $tempfile_plain);
  7282. imagedestroy($imgplain);
  7283. }
  7284. // embed mask image
  7285. $imgmask = $this->Image($tempfile_alpha, $x, $y, $w, $h, 'PNG', '', '', $resize, $dpi, '', true, false);
  7286. // embed image, masked with previously embedded mask
  7287. $this->Image($tempfile_plain, $x, $y, $w, $h, $type, $link, $align, $resize, $dpi, $palign, false, $imgmask);
  7288. // remove temp files
  7289. unlink($tempfile_alpha);
  7290. unlink($tempfile_plain);
  7291. }
  7292. /**
  7293. * Correct the gamma value to be used with GD library
  7294. * @param float $v the gamma value to be corrected
  7295. * @access protected
  7296. * @since 4.3.007 (2008-12-04)
  7297. */
  7298. protected function getGDgamma($v) {
  7299. return (pow(($v / 255), 2.2) * 255);
  7300. }
  7301. /**
  7302. * Performs a line break.
  7303. * The current abscissa goes back to the left margin and the ordinate increases by the amount passed in parameter.
  7304. * @param float $h The height of the break. By default, the value equals the height of the last printed cell.
  7305. * @param boolean $cell if true add the current left (or right o for RTL) padding to the X coordinate
  7306. * @access public
  7307. * @since 1.0
  7308. * @see Cell()
  7309. */
  7310. public function Ln($h='', $cell=false) {
  7311. 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'])) {
  7312. // revove vertical space from the top of the column
  7313. return;
  7314. }
  7315. if ($cell) {
  7316. if ($this->rtl) {
  7317. $cellpadding = $this->cell_padding['R'];
  7318. } else {
  7319. $cellpadding = $this->cell_padding['L'];
  7320. }
  7321. } else {
  7322. $cellpadding = 0;
  7323. }
  7324. if ($this->rtl) {
  7325. $this->x = $this->w - $this->rMargin - $cellpadding;
  7326. } else {
  7327. $this->x = $this->lMargin + $cellpadding;
  7328. }
  7329. if (is_string($h)) {
  7330. $this->y += $this->lasth;
  7331. } else {
  7332. $this->y += $h;
  7333. }
  7334. $this->newline = true;
  7335. }
  7336. /**
  7337. * Returns the relative X value of current position.
  7338. * The value is relative to the left border for LTR languages and to the right border for RTL languages.
  7339. * @return float
  7340. * @access public
  7341. * @since 1.2
  7342. * @see SetX(), GetY(), SetY()
  7343. */
  7344. public function GetX() {
  7345. //Get x position
  7346. if ($this->rtl) {
  7347. return ($this->w - $this->x);
  7348. } else {
  7349. return $this->x;
  7350. }
  7351. }
  7352. /**
  7353. * Returns the absolute X value of current position.
  7354. * @return float
  7355. * @access public
  7356. * @since 1.2
  7357. * @see SetX(), GetY(), SetY()
  7358. */
  7359. public function GetAbsX() {
  7360. return $this->x;
  7361. }
  7362. /**
  7363. * Returns the ordinate of the current position.
  7364. * @return float
  7365. * @access public
  7366. * @since 1.0
  7367. * @see SetY(), GetX(), SetX()
  7368. */
  7369. public function GetY() {
  7370. return $this->y;
  7371. }
  7372. /**
  7373. * Defines the abscissa of the current position.
  7374. * If the passed value is negative, it is relative to the right of the page (or left if language is RTL).
  7375. * @param float $x The value of the abscissa.
  7376. * @param boolean $rtloff if true always uses the page top-left corner as origin of axis.
  7377. * @access public
  7378. * @since 1.2
  7379. * @see GetX(), GetY(), SetY(), SetXY()
  7380. */
  7381. public function SetX($x, $rtloff=false) {
  7382. if (!$rtloff AND $this->rtl) {
  7383. if ($x >= 0) {
  7384. $this->x = $this->w - $x;
  7385. } else {
  7386. $this->x = abs($x);
  7387. }
  7388. } else {
  7389. if ($x >= 0) {
  7390. $this->x = $x;
  7391. } else {
  7392. $this->x = $this->w + $x;
  7393. }
  7394. }
  7395. if ($this->x < 0) {
  7396. $this->x = 0;
  7397. }
  7398. if ($this->x > $this->w) {
  7399. $this->x = $this->w;
  7400. }
  7401. }
  7402. /**
  7403. * Moves the current abscissa back to the left margin and sets the ordinate.
  7404. * If the passed value is negative, it is relative to the bottom of the page.
  7405. * @param float $y The value of the ordinate.
  7406. * @param bool $resetx if true (default) reset the X position.
  7407. * @param boolean $rtloff if true always uses the page top-left corner as origin of axis.
  7408. * @access public
  7409. * @since 1.0
  7410. * @see GetX(), GetY(), SetY(), SetXY()
  7411. */
  7412. public function SetY($y, $resetx=true, $rtloff=false) {
  7413. if ($resetx) {
  7414. //reset x
  7415. if (!$rtloff AND $this->rtl) {
  7416. $this->x = $this->w - $this->rMargin;
  7417. } else {
  7418. $this->x = $this->lMargin;
  7419. }
  7420. }
  7421. if ($y >= 0) {
  7422. $this->y = $y;
  7423. } else {
  7424. $this->y = $this->h + $y;
  7425. }
  7426. if ($this->y < 0) {
  7427. $this->y = 0;
  7428. }
  7429. if ($this->y > $this->h) {
  7430. $this->y = $this->h;
  7431. }
  7432. }
  7433. /**
  7434. * Defines the abscissa and ordinate of the current position.
  7435. * If the passed values are negative, they are relative respectively to the right and bottom of the page.
  7436. * @param float $x The value of the abscissa.
  7437. * @param float $y The value of the ordinate.
  7438. * @param boolean $rtloff if true always uses the page top-left corner as origin of axis.
  7439. * @access public
  7440. * @since 1.2
  7441. * @see SetX(), SetY()
  7442. */
  7443. public function SetXY($x, $y, $rtloff=false) {
  7444. $this->SetY($y, false, $rtloff);
  7445. $this->SetX($x, $rtloff);
  7446. }
  7447. /**
  7448. * Send the document to a given destination: string, local file or browser.
  7449. * In the last case, the plug-in may be used (if present) or a download ("Save as" dialog box) may be forced.<br />
  7450. * The method first calls Close() if necessary to terminate the document.
  7451. * @param string $name The name of the file when saved. Note that special characters are removed and blanks characters are replaced with the underscore character.
  7452. * @param string $dest Destination where to send the document. It can take one of the following values:<ul><li>I: send the file inline to the browser (default). The plug-in is used if available. The name given by name is used when one selects the "Save as" option on the link generating the PDF.</li><li>D: send to the browser and force a file download with the name given by name.</li><li>F: save to a local server file with the name given by name.</li><li>S: return the document as a string. name is ignored.</li><li>FI: equivalent to F + I option</li><li>FD: equivalent to F + D option</li></ul>
  7453. * @access public
  7454. * @since 1.0
  7455. * @see Close()
  7456. */
  7457. public function Output($name='doc.pdf', $dest='I') {
  7458. //Output PDF to some destination
  7459. //Finish document if necessary
  7460. if ($this->state < 3) {
  7461. $this->Close();
  7462. }
  7463. //Normalize parameters
  7464. if (is_bool($dest)) {
  7465. $dest = $dest ? 'D' : 'F';
  7466. }
  7467. $dest = strtoupper($dest);
  7468. if ($dest{0} != 'F') {
  7469. $name = preg_replace('/[\s]+/', '_', $name);
  7470. $name = preg_replace('/[^a-zA-Z0-9_\.-]/', '', $name);
  7471. }
  7472. if ($this->sign) {
  7473. // *** apply digital signature to the document ***
  7474. // get the document content
  7475. $pdfdoc = $this->getBuffer();
  7476. // remove last newline
  7477. $pdfdoc = substr($pdfdoc, 0, -1);
  7478. // Remove the original buffer
  7479. if (isset($this->diskcache) AND $this->diskcache) {
  7480. // remove buffer file from cache
  7481. unlink($this->buffer);
  7482. }
  7483. unset($this->buffer);
  7484. // remove filler space
  7485. $byterange_string_len = strlen($this->byterange_string);
  7486. // define the ByteRange
  7487. $byte_range = array();
  7488. $byte_range[0] = 0;
  7489. $byte_range[1] = strpos($pdfdoc, $this->byterange_string) + $byterange_string_len + 10;
  7490. $byte_range[2] = $byte_range[1] + $this->signature_max_length + 2;
  7491. $byte_range[3] = strlen($pdfdoc) - $byte_range[2];
  7492. $pdfdoc = substr($pdfdoc, 0, $byte_range[1]).substr($pdfdoc, $byte_range[2]);
  7493. // replace the ByteRange
  7494. $byterange = sprintf('/ByteRange[0 %u %u %u]', $byte_range[1], $byte_range[2], $byte_range[3]);
  7495. $byterange .= str_repeat(' ', ($byterange_string_len - strlen($byterange)));
  7496. $pdfdoc = str_replace($this->byterange_string, $byterange, $pdfdoc);
  7497. // write the document to a temporary folder
  7498. $tempdoc = tempnam(K_PATH_CACHE, 'tmppdf_');
  7499. $f = fopen($tempdoc, 'wb');
  7500. if (!$f) {
  7501. $this->Error('Unable to create temporary file: '.$tempdoc);
  7502. }
  7503. $pdfdoc_length = strlen($pdfdoc);
  7504. fwrite($f, $pdfdoc, $pdfdoc_length);
  7505. fclose($f);
  7506. // get digital signature via openssl library
  7507. $tempsign = tempnam(K_PATH_CACHE, 'tmpsig_');
  7508. if (empty($this->signature_data['extracerts'])) {
  7509. openssl_pkcs7_sign($tempdoc, $tempsign, $this->signature_data['signcert'], array($this->signature_data['privkey'], $this->signature_data['password']), array(), PKCS7_BINARY | PKCS7_DETACHED);
  7510. } else {
  7511. 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']);
  7512. }
  7513. unlink($tempdoc);
  7514. // read signature
  7515. $signature = file_get_contents($tempsign);
  7516. unlink($tempsign);
  7517. // extract signature
  7518. $signature = substr($signature, $pdfdoc_length);
  7519. $signature = substr($signature, (strpos($signature, "%%EOF\n\n------") + 13));
  7520. $tmparr = explode("\n\n", $signature);
  7521. $signature = $tmparr[1];
  7522. unset($tmparr);
  7523. // decode signature
  7524. $signature = base64_decode(trim($signature));
  7525. // convert signature to hex
  7526. $signature = current(unpack('H*', $signature));
  7527. $signature = str_pad($signature, $this->signature_max_length, '0');
  7528. // Add signature to the document
  7529. $pdfdoc = substr($pdfdoc, 0, $byte_range[1]).'<'.$signature.'>'.substr($pdfdoc, $byte_range[1]);
  7530. $this->diskcache = false;
  7531. $this->buffer = &$pdfdoc;
  7532. $this->bufferlen = strlen($pdfdoc);
  7533. }
  7534. switch($dest) {
  7535. case 'I': {
  7536. // Send PDF to the standard output
  7537. if (ob_get_contents()) {
  7538. $this->Error('Some data has already been output, can\'t send PDF file');
  7539. }
  7540. if (php_sapi_name() != 'cli') {
  7541. //We send to a browser
  7542. header('Content-Type: application/pdf');
  7543. if (headers_sent()) {
  7544. $this->Error('Some data has already been output to browser, can\'t send PDF file');
  7545. }
  7546. header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
  7547. header('Pragma: public');
  7548. header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
  7549. header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
  7550. header('Content-Length: '.$this->bufferlen);
  7551. header('Content-Disposition: inline; filename="'.basename($name).'";');
  7552. }
  7553. echo $this->getBuffer();
  7554. break;
  7555. }
  7556. case 'D': {
  7557. // Download PDF as file
  7558. if (ob_get_contents()) {
  7559. $this->Error('Some data has already been output, can\'t send PDF file');
  7560. }
  7561. header('Content-Description: File Transfer');
  7562. if (headers_sent()) {
  7563. $this->Error('Some data has already been output to browser, can\'t send PDF file');
  7564. }
  7565. header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
  7566. header('Pragma: public');
  7567. header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
  7568. header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
  7569. // force download dialog
  7570. header('Content-Type: application/force-download');
  7571. header('Content-Type: application/octet-stream', false);
  7572. header('Content-Type: application/download', false);
  7573. header('Content-Type: application/pdf', false);
  7574. // use the Content-Disposition header to supply a recommended filename
  7575. header('Content-Disposition: attachment; filename="'.basename($name).'";');
  7576. header('Content-Transfer-Encoding: binary');
  7577. header('Content-Length: '.$this->bufferlen);
  7578. echo $this->getBuffer();
  7579. break;
  7580. }
  7581. case 'F':
  7582. case 'FI':
  7583. case 'FD': {
  7584. // Save PDF to a local file
  7585. if ($this->diskcache) {
  7586. copy($this->buffer, $name);
  7587. } else {
  7588. $f = fopen($name, 'wb');
  7589. if (!$f) {
  7590. $this->Error('Unable to create output file: '.$name);
  7591. }
  7592. fwrite($f, $this->getBuffer(), $this->bufferlen);
  7593. fclose($f);
  7594. }
  7595. if ($dest == 'FI') {
  7596. // send headers to browser
  7597. header('Content-Type: application/pdf');
  7598. header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
  7599. header('Pragma: public');
  7600. header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
  7601. header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
  7602. header('Content-Length: '.filesize($name));
  7603. header('Content-Disposition: inline; filename="'.basename($name).'";');
  7604. // send document to the browser
  7605. echo file_get_contents($name);
  7606. } elseif ($dest == 'FD') {
  7607. // send headers to browser
  7608. if (ob_get_contents()) {
  7609. $this->Error('Some data has already been output, can\'t send PDF file');
  7610. }
  7611. header('Content-Description: File Transfer');
  7612. if (headers_sent()) {
  7613. $this->Error('Some data has already been output to browser, can\'t send PDF file');
  7614. }
  7615. header('Cache-Control: public, must-revalidate, max-age=0'); // HTTP/1.1
  7616. header('Pragma: public');
  7617. header('Expires: Sat, 26 Jul 1997 05:00:00 GMT'); // Date in the past
  7618. header('Last-Modified: '.gmdate('D, d M Y H:i:s').' GMT');
  7619. // force download dialog
  7620. header('Content-Type: application/force-download');
  7621. header('Content-Type: application/octet-stream', false);
  7622. header('Content-Type: application/download', false);
  7623. header('Content-Type: application/pdf', false);
  7624. // use the Content-Disposition header to supply a recommended filename
  7625. header('Content-Disposition: attachment; filename="'.basename($name).'";');
  7626. header('Content-Transfer-Encoding: binary');
  7627. header('Content-Length: '.filesize($name));
  7628. // send document to the browser
  7629. echo file_get_contents($name);
  7630. }
  7631. break;
  7632. }
  7633. case 'S': {
  7634. // Returns PDF as a string
  7635. return $this->getBuffer();
  7636. }
  7637. default: {
  7638. $this->Error('Incorrect output destination: '.$dest);
  7639. }
  7640. }
  7641. return '';
  7642. }
  7643. /**
  7644. * Unset all class variables except the following critical variables: internal_encoding, state, bufferlen, buffer and diskcache.
  7645. * @param boolean $destroyall if true destroys all class variables, otherwise preserves critical variables.
  7646. * @param boolean $preserve_objcopy if true preserves the objcopy variable
  7647. * @access public
  7648. * @since 4.5.016 (2009-02-24)
  7649. */
  7650. public function _destroy($destroyall=false, $preserve_objcopy=false) {
  7651. if ($destroyall AND isset($this->diskcache) AND $this->diskcache AND (!$preserve_objcopy) AND (!$this->empty_string($this->buffer))) {
  7652. // remove buffer file from cache
  7653. unlink($this->buffer);
  7654. }
  7655. foreach (array_keys(get_object_vars($this)) as $val) {
  7656. if ($destroyall OR (
  7657. ($val != 'internal_encoding')
  7658. AND ($val != 'state')
  7659. AND ($val != 'bufferlen')
  7660. AND ($val != 'buffer')
  7661. AND ($val != 'diskcache')
  7662. AND ($val != 'sign')
  7663. AND ($val != 'signature_data')
  7664. AND ($val != 'signature_max_length')
  7665. AND ($val != 'byterange_string')
  7666. )) {
  7667. if ((!$preserve_objcopy OR ($val != 'objcopy')) AND isset($this->$val)) {
  7668. unset($this->$val);
  7669. }
  7670. }
  7671. }
  7672. }
  7673. /**
  7674. * Check for locale-related bug
  7675. * @access protected
  7676. */
  7677. protected function _dochecks() {
  7678. //Check for locale-related bug
  7679. if (1.1 == 1) {
  7680. $this->Error('Don\'t alter the locale before including class file');
  7681. }
  7682. //Check for decimal separator
  7683. if (sprintf('%.1F', 1.0) != '1.0') {
  7684. setlocale(LC_NUMERIC, 'C');
  7685. }
  7686. }
  7687. /**
  7688. * Return fonts path
  7689. * @return string
  7690. * @access protected
  7691. */
  7692. protected function _getfontpath() {
  7693. if (!defined('K_PATH_FONTS') AND is_dir(dirname(__FILE__).'/fonts')) {
  7694. define('K_PATH_FONTS', dirname(__FILE__).'/fonts/');
  7695. }
  7696. return defined('K_PATH_FONTS') ? K_PATH_FONTS : '';
  7697. }
  7698. /**
  7699. * Output pages.
  7700. * @access protected
  7701. */
  7702. protected function _putpages() {
  7703. $nb = $this->numpages;
  7704. if (!empty($this->AliasNbPages)) {
  7705. $nbs = $this->formatPageNumber($nb);
  7706. $nbu = $this->UTF8ToUTF16BE($nbs, false); // replacement for unicode font
  7707. $alias_a = $this->_escape($this->AliasNbPages);
  7708. $alias_au = $this->_escape('{'.$this->AliasNbPages.'}');
  7709. if ($this->isunicode) {
  7710. $alias_b = $this->_escape($this->UTF8ToLatin1($this->AliasNbPages));
  7711. $alias_bu = $this->_escape($this->UTF8ToLatin1('{'.$this->AliasNbPages.'}'));
  7712. $alias_c = $this->_escape($this->utf8StrRev($this->AliasNbPages, false, $this->tmprtl));
  7713. $alias_cu = $this->_escape($this->utf8StrRev('{'.$this->AliasNbPages.'}', false, $this->tmprtl));
  7714. }
  7715. }
  7716. if (!empty($this->AliasNumPage)) {
  7717. $alias_pa = $this->_escape($this->AliasNumPage);
  7718. $alias_pau = $this->_escape('{'.$this->AliasNumPage.'}');
  7719. if ($this->isunicode) {
  7720. $alias_pb = $this->_escape($this->UTF8ToLatin1($this->AliasNumPage));
  7721. $alias_pbu = $this->_escape($this->UTF8ToLatin1('{'.$this->AliasNumPage.'}'));
  7722. $alias_pc = $this->_escape($this->utf8StrRev($this->AliasNumPage, false, $this->tmprtl));
  7723. $alias_pcu = $this->_escape($this->utf8StrRev('{'.$this->AliasNumPage.'}', false, $this->tmprtl));
  7724. }
  7725. }
  7726. $pagegroupnum = 0;
  7727. $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
  7728. for ($n=1; $n <= $nb; ++$n) {
  7729. $temppage = $this->getPageBuffer($n);
  7730. if (!empty($this->pagegroups)) {
  7731. if(isset($this->newpagegroup[$n])) {
  7732. $pagegroupnum = 0;
  7733. }
  7734. ++$pagegroupnum;
  7735. foreach ($this->pagegroups as $k => $v) {
  7736. // replace total pages group numbers
  7737. $vs = $this->formatPageNumber($v);
  7738. $vu = $this->UTF8ToUTF16BE($vs, false);
  7739. $alias_ga = $this->_escape($k);
  7740. $alias_gau = $this->_escape('{'.$k.'}');
  7741. if ($this->isunicode) {
  7742. $alias_gb = $this->_escape($this->UTF8ToLatin1($k));
  7743. $alias_gbu = $this->_escape($this->UTF8ToLatin1('{'.$k.'}'));
  7744. $alias_gc = $this->_escape($this->utf8StrRev($k, false, $this->tmprtl));
  7745. $alias_gcu = $this->_escape($this->utf8StrRev('{'.$k.'}', false, $this->tmprtl));
  7746. }
  7747. $temppage = str_replace($alias_gau, $vu, $temppage);
  7748. if ($this->isunicode) {
  7749. $temppage = str_replace($alias_gbu, $vu, $temppage);
  7750. $temppage = str_replace($alias_gcu, $vu, $temppage);
  7751. $temppage = str_replace($alias_gb, $vs, $temppage);
  7752. $temppage = str_replace($alias_gc, $vs, $temppage);
  7753. }
  7754. $temppage = str_replace($alias_ga, $vs, $temppage);
  7755. // replace page group numbers
  7756. $pvs = $this->formatPageNumber($pagegroupnum);
  7757. $pvu = $this->UTF8ToUTF16BE($pvs, false);
  7758. $pk = str_replace('{nb', '{pnb', $k);
  7759. $alias_pga = $this->_escape($pk);
  7760. $alias_pgau = $this->_escape('{'.$pk.'}');
  7761. if ($this->isunicode) {
  7762. $alias_pgb = $this->_escape($this->UTF8ToLatin1($pk));
  7763. $alias_pgbu = $this->_escape($this->UTF8ToLatin1('{'.$pk.'}'));
  7764. $alias_pgc = $this->_escape($this->utf8StrRev($pk, false, $this->tmprtl));
  7765. $alias_pgcu = $this->_escape($this->utf8StrRev('{'.$pk.'}', false, $this->tmprtl));
  7766. }
  7767. $temppage = str_replace($alias_pgau, $pvu, $temppage);
  7768. if ($this->isunicode) {
  7769. $temppage = str_replace($alias_pgbu, $pvu, $temppage);
  7770. $temppage = str_replace($alias_pgcu, $pvu, $temppage);
  7771. $temppage = str_replace($alias_pgb, $pvs, $temppage);
  7772. $temppage = str_replace($alias_pgc, $pvs, $temppage);
  7773. }
  7774. $temppage = str_replace($alias_pga, $pvs, $temppage);
  7775. }
  7776. }
  7777. if (!empty($this->AliasNbPages)) {
  7778. // replace total pages number
  7779. $temppage = str_replace($alias_au, $nbu, $temppage);
  7780. if ($this->isunicode) {
  7781. $temppage = str_replace($alias_bu, $nbu, $temppage);
  7782. $temppage = str_replace($alias_cu, $nbu, $temppage);
  7783. $temppage = str_replace($alias_b, $nbs, $temppage);
  7784. $temppage = str_replace($alias_c, $nbs, $temppage);
  7785. }
  7786. $temppage = str_replace($alias_a, $nbs, $temppage);
  7787. }
  7788. if (!empty($this->AliasNumPage)) {
  7789. // replace page number
  7790. $pnbs = $this->formatPageNumber($n);
  7791. $pnbu = $this->UTF8ToUTF16BE($pnbs, false); // replacement for unicode font
  7792. $temppage = str_replace($alias_pau, $pnbu, $temppage);
  7793. if ($this->isunicode) {
  7794. $temppage = str_replace($alias_pbu, $pnbu, $temppage);
  7795. $temppage = str_replace($alias_pcu, $pnbu, $temppage);
  7796. $temppage = str_replace($alias_pb, $pnbs, $temppage);
  7797. $temppage = str_replace($alias_pc, $pnbs, $temppage);
  7798. }
  7799. $temppage = str_replace($alias_pa, $pnbs, $temppage);
  7800. }
  7801. $temppage = str_replace($this->epsmarker, '', $temppage);
  7802. //Page
  7803. $this->page_obj_id[$n] = $this->_newobj();
  7804. $out = '<<';
  7805. $out .= ' /Type /Page';
  7806. $out .= ' /Parent 1 0 R';
  7807. $out .= ' /LastModified '.$this->_datestring();
  7808. $out .= ' /Resources 2 0 R';
  7809. $boxes = array('MediaBox', 'CropBox', 'BleedBox', 'TrimBox', 'ArtBox');
  7810. foreach ($boxes as $box) {
  7811. $out .= ' /'.$box;
  7812. $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']);
  7813. }
  7814. if (isset($this->pagedim[$n]['BoxColorInfo']) AND !empty($this->pagedim[$n]['BoxColorInfo'])) {
  7815. $out .= ' /BoxColorInfo <<';
  7816. foreach ($boxes as $box) {
  7817. if (isset($this->pagedim[$n]['BoxColorInfo'][$box])) {
  7818. $out .= ' /'.$box.' <<';
  7819. if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['C'])) {
  7820. $color = $this->pagedim[$n]['BoxColorInfo'][$box]['C'];
  7821. $out .= ' /C [';
  7822. $out .= sprintf(' %.3F %.3F %.3F', $color[0]/255, $color[1]/255, $color[2]/255);
  7823. $out .= ' ]';
  7824. }
  7825. if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['W'])) {
  7826. $out .= ' /W '.($this->pagedim[$n]['BoxColorInfo'][$box]['W'] * $this->k);
  7827. }
  7828. if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['S'])) {
  7829. $out .= ' /S /'.$this->pagedim[$n]['BoxColorInfo'][$box]['S'];
  7830. }
  7831. if (isset($this->pagedim[$n]['BoxColorInfo'][$box]['D'])) {
  7832. $dashes = $this->pagedim[$n]['BoxColorInfo'][$box]['D'];
  7833. $out .= ' /D [';
  7834. foreach ($dashes as $dash) {
  7835. $out .= sprintf(' %.3F', ($dash * $this->k));
  7836. }
  7837. $out .= ' ]';
  7838. }
  7839. $out .= ' >>';
  7840. }
  7841. }
  7842. $out .= ' >>';
  7843. }
  7844. $out .= ' /Contents '.($this->n + 1).' 0 R';
  7845. $out .= ' /Rotate '.$this->pagedim[$n]['Rotate'];
  7846. $out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceRGB >>';
  7847. if (isset($this->pagedim[$n]['trans']) AND !empty($this->pagedim[$n]['trans'])) {
  7848. // page transitions
  7849. if (isset($this->pagedim[$n]['trans']['Dur'])) {
  7850. $out .= ' /Dur '.$this->pagedim[$n]['trans']['Dur'];
  7851. }
  7852. $out .= ' /Trans <<';
  7853. $out .= ' /Type /Trans';
  7854. if (isset($this->pagedim[$n]['trans']['S'])) {
  7855. $out .= ' /S /'.$this->pagedim[$n]['trans']['S'];
  7856. }
  7857. if (isset($this->pagedim[$n]['trans']['D'])) {
  7858. $out .= ' /D '.$this->pagedim[$n]['trans']['D'];
  7859. }
  7860. if (isset($this->pagedim[$n]['trans']['Dm'])) {
  7861. $out .= ' /Dm /'.$this->pagedim[$n]['trans']['Dm'];
  7862. }
  7863. if (isset($this->pagedim[$n]['trans']['M'])) {
  7864. $out .= ' /M /'.$this->pagedim[$n]['trans']['M'];
  7865. }
  7866. if (isset($this->pagedim[$n]['trans']['Di'])) {
  7867. $out .= ' /Di '.$this->pagedim[$n]['trans']['Di'];
  7868. }
  7869. if (isset($this->pagedim[$n]['trans']['SS'])) {
  7870. $out .= ' /SS '.$this->pagedim[$n]['trans']['SS'];
  7871. }
  7872. if (isset($this->pagedim[$n]['trans']['B'])) {
  7873. $out .= ' /B '.$this->pagedim[$n]['trans']['B'];
  7874. }
  7875. $out .= ' >>';
  7876. }
  7877. $out .= $this->_getannotsrefs($n);
  7878. $out .= ' /PZ '.$this->pagedim[$n]['PZ'];
  7879. $out .= ' >>';
  7880. $out .= "\n".'endobj';
  7881. $this->_out($out);
  7882. //Page content
  7883. $p = ($this->compress) ? gzcompress($temppage) : $temppage;
  7884. $this->_newobj();
  7885. $p = $this->_getrawstream($p);
  7886. $this->_out('<<'.$filter.'/Length '.strlen($p).'>> stream'."\n".$p."\n".'endstream'."\n".'endobj');
  7887. if ($this->diskcache) {
  7888. // remove temporary files
  7889. unlink($this->pages[$n]);
  7890. }
  7891. }
  7892. //Pages root
  7893. $out = $this->_getobj(1)."\n";
  7894. $out .= '<< /Type /Pages /Kids [';
  7895. foreach($this->page_obj_id as $page_obj) {
  7896. $out .= ' '.$page_obj.' 0 R';
  7897. }
  7898. $out .= ' ] /Count '.$nb.' >>';
  7899. $out .= "\n".'endobj';
  7900. $this->_out($out);
  7901. }
  7902. /**
  7903. * Output references to page annotations
  7904. * @param int $n page number
  7905. * @access protected
  7906. * @author Nicola Asuni
  7907. * @since 4.7.000 (2008-08-29)
  7908. * @deprecated
  7909. */
  7910. protected function _putannotsrefs($n) {
  7911. $this->_out($this->_getannotsrefs($n));
  7912. }
  7913. /**
  7914. * Get references to page annotations.
  7915. * @param int $n page number
  7916. * @return string
  7917. * @access protected
  7918. * @author Nicola Asuni
  7919. * @since 5.0.010 (2010-05-17)
  7920. */
  7921. protected function _getannotsrefs($n) {
  7922. if (!(isset($this->PageAnnots[$n]) OR ($this->sign AND isset($this->signature_data['cert_type'])))) {
  7923. return '';
  7924. }
  7925. $out = ' /Annots [';
  7926. if (isset($this->PageAnnots[$n])) {
  7927. foreach ($this->PageAnnots[$n] as $key => $val) {
  7928. if (!in_array($val['n'], $this->radio_groups)) {
  7929. $out .= ' '.$val['n'].' 0 R';
  7930. }
  7931. }
  7932. // add radiobutton groups
  7933. if (isset($this->radiobutton_groups[$n])) {
  7934. foreach ($this->radiobutton_groups[$n] as $key => $data) {
  7935. if (isset($data['n'])) {
  7936. $out .= ' '.$data['n'].' 0 R';
  7937. }
  7938. }
  7939. }
  7940. }
  7941. if ($this->sign AND ($n == $this->signature_appearance['page']) AND isset($this->signature_data['cert_type'])) {
  7942. // set reference for signature object
  7943. $out .= ' '.$this->sig_obj_id.' 0 R';
  7944. }
  7945. $out .= ' ]';
  7946. return $out;
  7947. }
  7948. /**
  7949. * Output annotations objects for all pages.
  7950. * !!! THIS METHOD IS NOT YET COMPLETED !!!
  7951. * See section 12.5 of PDF 32000_2008 reference.
  7952. * @access protected
  7953. * @author Nicola Asuni
  7954. * @since 4.0.018 (2008-08-06)
  7955. */
  7956. protected function _putannotsobjs() {
  7957. // reset object counter
  7958. for ($n=1; $n <= $this->numpages; ++$n) {
  7959. if (isset($this->PageAnnots[$n])) {
  7960. // set page annotations
  7961. foreach ($this->PageAnnots[$n] as $key => $pl) {
  7962. $annot_obj_id = $this->PageAnnots[$n][$key]['n'];
  7963. // create annotation object for grouping radiobuttons
  7964. if (isset($this->radiobutton_groups[$n][$pl['txt']]) AND is_array($this->radiobutton_groups[$n][$pl['txt']])) {
  7965. $radio_button_obj_id = $this->radiobutton_groups[$n][$pl['txt']]['n'];
  7966. $annots = '<<';
  7967. $annots .= ' /Type /Annot';
  7968. $annots .= ' /Subtype /Widget';
  7969. $annots .= ' /Rect [0 0 0 0]';
  7970. $annots .= ' /T '.$this->_datastring($pl['txt'], $radio_button_obj_id);
  7971. $annots .= ' /FT /Btn';
  7972. $annots .= ' /Ff 49152';
  7973. $annots .= ' /Kids [';
  7974. foreach ($this->radiobutton_groups[$n][$pl['txt']] as $key => $data) {
  7975. if ($key !== 'n') {
  7976. $annots .= ' '.$data['kid'].' 0 R';
  7977. if ($data['def'] !== 'Off') {
  7978. $defval = $data['def'];
  7979. }
  7980. }
  7981. }
  7982. $annots .= ' ]';
  7983. if (isset($defval)) {
  7984. $annots .= ' /V /'.$defval;
  7985. }
  7986. $annots .= ' >>';
  7987. $this->_out($this->_getobj($radio_button_obj_id)."\n".$annots."\n".'endobj');
  7988. $this->form_obj_id[] = $radio_button_obj_id;
  7989. // store object id to be used on Parent entry of Kids
  7990. $this->radiobutton_groups[$n][$pl['txt']] = $radio_button_obj_id;
  7991. }
  7992. $formfield = false;
  7993. $pl['opt'] = array_change_key_case($pl['opt'], CASE_LOWER);
  7994. $a = $pl['x'] * $this->k;
  7995. $b = $this->pagedim[$n]['h'] - (($pl['y'] + $pl['h']) * $this->k);
  7996. $c = $pl['w'] * $this->k;
  7997. $d = $pl['h'] * $this->k;
  7998. $rect = sprintf('%.2F %.2F %.2F %.2F', $a, $b, $a+$c, $b+$d);
  7999. // create new annotation object
  8000. $annots = '<</Type /Annot';
  8001. $annots .= ' /Subtype /'.$pl['opt']['subtype'];
  8002. $annots .= ' /Rect ['.$rect.']';
  8003. $ft = array('Btn', 'Tx', 'Ch', 'Sig');
  8004. if (isset($pl['opt']['ft']) AND in_array($pl['opt']['ft'], $ft)) {
  8005. $annots .= ' /FT /'.$pl['opt']['ft'];
  8006. $formfield = true;
  8007. }
  8008. $annots .= ' /Contents '.$this->_textstring($pl['txt'], $annot_obj_id);
  8009. $annots .= ' /P '.$this->page_obj_id[$n].' 0 R';
  8010. $annots .= ' /NM '.$this->_datastring(sprintf('%04u-%04u', $n, $key), $annot_obj_id);
  8011. $annots .= ' /M '.$this->_datestring($annot_obj_id);
  8012. if (isset($pl['opt']['f'])) {
  8013. $val = 0;
  8014. if (is_array($pl['opt']['f'])) {
  8015. foreach ($pl['opt']['f'] as $f) {
  8016. switch (strtolower($f)) {
  8017. case 'invisible': {
  8018. $val += 1 << 0;
  8019. break;
  8020. }
  8021. case 'hidden': {
  8022. $val += 1 << 1;
  8023. break;
  8024. }
  8025. case 'print': {
  8026. $val += 1 << 2;
  8027. break;
  8028. }
  8029. case 'nozoom': {
  8030. $val += 1 << 3;
  8031. break;
  8032. }
  8033. case 'norotate': {
  8034. $val += 1 << 4;
  8035. break;
  8036. }
  8037. case 'noview': {
  8038. $val += 1 << 5;
  8039. break;
  8040. }
  8041. case 'readonly': {
  8042. $val += 1 << 6;
  8043. break;
  8044. }
  8045. case 'locked': {
  8046. $val += 1 << 8;
  8047. break;
  8048. }
  8049. case 'togglenoview': {
  8050. $val += 1 << 9;
  8051. break;
  8052. }
  8053. case 'lockedcontents': {
  8054. $val += 1 << 10;
  8055. break;
  8056. }
  8057. default: {
  8058. break;
  8059. }
  8060. }
  8061. }
  8062. } else {
  8063. $val = intval($pl['opt']['f']);
  8064. }
  8065. $annots .= ' /F '.intval($val);
  8066. }
  8067. if (isset($pl['opt']['as']) AND is_string($pl['opt']['as'])) {
  8068. $annots .= ' /AS /'.$pl['opt']['as'];
  8069. }
  8070. if (isset($pl['opt']['ap'])) {
  8071. // appearance stream
  8072. $annots .= ' /AP <<';
  8073. if (is_array($pl['opt']['ap'])) {
  8074. foreach ($pl['opt']['ap'] as $apmode => $apdef) {
  8075. // $apmode can be: n = normal; r = rollover; d = down;
  8076. $annots .= ' /'.strtoupper($apmode);
  8077. if (is_array($apdef)) {
  8078. $annots .= ' <<';
  8079. foreach ($apdef as $apstate => $stream) {
  8080. // reference to XObject that define the appearance for this mode-state
  8081. $apsobjid = $this->_putAPXObject($c, $d, $stream);
  8082. $annots .= ' /'.$apstate.' '.$apsobjid.' 0 R';
  8083. }
  8084. $annots .= ' >>';
  8085. } else {
  8086. // reference to XObject that define the appearance for this mode
  8087. $apsobjid = $this->_putAPXObject($c, $d, $apdef);
  8088. $annots .= ' '.$apsobjid.' 0 R';
  8089. }
  8090. }
  8091. } else {
  8092. $annots .= $pl['opt']['ap'];
  8093. }
  8094. $annots .= ' >>';
  8095. }
  8096. if (isset($pl['opt']['bs']) AND (is_array($pl['opt']['bs']))) {
  8097. $annots .= ' /BS <<';
  8098. $annots .= ' /Type /Border';
  8099. if (isset($pl['opt']['bs']['w'])) {
  8100. $annots .= ' /W '.intval($pl['opt']['bs']['w']);
  8101. }
  8102. $bstyles = array('S', 'D', 'B', 'I', 'U');
  8103. if (isset($pl['opt']['bs']['s']) AND in_array($pl['opt']['bs']['s'], $bstyles)) {
  8104. $annots .= ' /S /'.$pl['opt']['bs']['s'];
  8105. }
  8106. if (isset($pl['opt']['bs']['d']) AND (is_array($pl['opt']['bs']['d']))) {
  8107. $annots .= ' /D [';
  8108. foreach ($pl['opt']['bs']['d'] as $cord) {
  8109. $annots .= ' '.intval($cord);
  8110. }
  8111. $annots .= ']';
  8112. }
  8113. $annots .= ' >>';
  8114. } else {
  8115. $annots .= ' /Border [';
  8116. if (isset($pl['opt']['border']) AND (count($pl['opt']['border']) >= 3)) {
  8117. $annots .= intval($pl['opt']['border'][0]).' ';
  8118. $annots .= intval($pl['opt']['border'][1]).' ';
  8119. $annots .= intval($pl['opt']['border'][2]);
  8120. if (isset($pl['opt']['border'][3]) AND is_array($pl['opt']['border'][3])) {
  8121. $annots .= ' [';
  8122. foreach ($pl['opt']['border'][3] as $dash) {
  8123. $annots .= intval($dash).' ';
  8124. }
  8125. $annots .= ']';
  8126. }
  8127. } else {
  8128. $annots .= '0 0 0';
  8129. }
  8130. $annots .= ']';
  8131. }
  8132. if (isset($pl['opt']['be']) AND (is_array($pl['opt']['be']))) {
  8133. $annots .= ' /BE <<';
  8134. $bstyles = array('S', 'C');
  8135. if (isset($pl['opt']['be']['s']) AND in_array($pl['opt']['be']['s'], $markups)) {
  8136. $annots .= ' /S /'.$pl['opt']['bs']['s'];
  8137. } else {
  8138. $annots .= ' /S /S';
  8139. }
  8140. if (isset($pl['opt']['be']['i']) AND ($pl['opt']['be']['i'] >= 0) AND ($pl['opt']['be']['i'] <= 2)) {
  8141. $annots .= ' /I '.sprintf(' %.4F', $pl['opt']['be']['i']);
  8142. }
  8143. $annots .= '>>';
  8144. }
  8145. if (isset($pl['opt']['c']) AND (is_array($pl['opt']['c'])) AND !empty($pl['opt']['c'])) {
  8146. $annots .= ' /C [';
  8147. foreach ($pl['opt']['c'] as $col) {
  8148. $col = intval($col);
  8149. $color = $col <= 0 ? 0 : ($col >= 255 ? 1 : $col / 255);
  8150. $annots .= sprintf(' %.4F', $color);
  8151. }
  8152. $annots .= ']';
  8153. }
  8154. //$annots .= ' /StructParent ';
  8155. //$annots .= ' /OC ';
  8156. $markups = array('text', 'freetext', 'line', 'square', 'circle', 'polygon', 'polyline', 'highlight', 'underline', 'squiggly', 'strikeout', 'stamp', 'caret', 'ink', 'fileattachment', 'sound');
  8157. if (in_array(strtolower($pl['opt']['subtype']), $markups)) {
  8158. // this is a markup type
  8159. if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
  8160. $annots .= ' /T '.$this->_textstring($pl['opt']['t'], $annot_obj_id);
  8161. }
  8162. //$annots .= ' /Popup ';
  8163. if (isset($pl['opt']['ca'])) {
  8164. $annots .= ' /CA '.sprintf('%.4F', floatval($pl['opt']['ca']));
  8165. }
  8166. if (isset($pl['opt']['rc'])) {
  8167. $annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
  8168. }
  8169. $annots .= ' /CreationDate '.$this->_datestring($annot_obj_id);
  8170. //$annots .= ' /IRT ';
  8171. if (isset($pl['opt']['subj'])) {
  8172. $annots .= ' /Subj '.$this->_textstring($pl['opt']['subj'], $annot_obj_id);
  8173. }
  8174. //$annots .= ' /RT ';
  8175. //$annots .= ' /IT ';
  8176. //$annots .= ' /ExData ';
  8177. }
  8178. $lineendings = array('Square', 'Circle', 'Diamond', 'OpenArrow', 'ClosedArrow', 'None', 'Butt', 'ROpenArrow', 'RClosedArrow', 'Slash');
  8179. // Annotation types
  8180. switch (strtolower($pl['opt']['subtype'])) {
  8181. case 'text': {
  8182. if (isset($pl['opt']['open'])) {
  8183. $annots .= ' /Open '. (strtolower($pl['opt']['open']) == 'true' ? 'true' : 'false');
  8184. }
  8185. $iconsapp = array('Comment', 'Help', 'Insert', 'Key', 'NewParagraph', 'Note', 'Paragraph');
  8186. if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
  8187. $annots .= ' /Name /'.$pl['opt']['name'];
  8188. } else {
  8189. $annots .= ' /Name /Note';
  8190. }
  8191. $statemodels = array('Marked', 'Review');
  8192. if (isset($pl['opt']['statemodel']) AND in_array($pl['opt']['statemodel'], $statemodels)) {
  8193. $annots .= ' /StateModel /'.$pl['opt']['statemodel'];
  8194. } else {
  8195. $pl['opt']['statemodel'] = 'Marked';
  8196. $annots .= ' /StateModel /'.$pl['opt']['statemodel'];
  8197. }
  8198. if ($pl['opt']['statemodel'] == 'Marked') {
  8199. $states = array('Accepted', 'Unmarked');
  8200. } else {
  8201. $states = array('Accepted', 'Rejected', 'Cancelled', 'Completed', 'None');
  8202. }
  8203. if (isset($pl['opt']['state']) AND in_array($pl['opt']['state'], $states)) {
  8204. $annots .= ' /State /'.$pl['opt']['state'];
  8205. } else {
  8206. if ($pl['opt']['statemodel'] == 'Marked') {
  8207. $annots .= ' /State /Unmarked';
  8208. } else {
  8209. $annots .= ' /State /None';
  8210. }
  8211. }
  8212. break;
  8213. }
  8214. case 'link': {
  8215. if(is_string($pl['txt'])) {
  8216. // external URI link
  8217. $annots .= ' /A <</S /URI /URI '.$this->_datastring($this->unhtmlentities($pl['txt']), $annot_obj_id).'>>';
  8218. } else {
  8219. // internal link
  8220. $l = $this->links[$pl['txt']];
  8221. $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)));
  8222. }
  8223. $hmodes = array('N', 'I', 'O', 'P');
  8224. if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmodes)) {
  8225. $annots .= ' /H /'.$pl['opt']['h'];
  8226. } else {
  8227. $annots .= ' /H /I';
  8228. }
  8229. //$annots .= ' /PA ';
  8230. //$annots .= ' /Quadpoints ';
  8231. break;
  8232. }
  8233. case 'freetext': {
  8234. if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
  8235. $annots .= ' /DA ('.$pl['opt']['da'].')';
  8236. }
  8237. if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
  8238. $annots .= ' /Q '.intval($pl['opt']['q']);
  8239. }
  8240. if (isset($pl['opt']['rc'])) {
  8241. $annots .= ' /RC '.$this->_textstring($pl['opt']['rc'], $annot_obj_id);
  8242. }
  8243. if (isset($pl['opt']['ds'])) {
  8244. $annots .= ' /DS '.$this->_textstring($pl['opt']['ds'], $annot_obj_id);
  8245. }
  8246. if (isset($pl['opt']['cl']) AND is_array($pl['opt']['cl'])) {
  8247. $annots .= ' /CL [';
  8248. foreach ($pl['opt']['cl'] as $cl) {
  8249. $annots .= sprintf('%.4F ', $cl * $this->k);
  8250. }
  8251. $annots .= ']';
  8252. }
  8253. $tfit = array('FreeText', 'FreeTextCallout', 'FreeTextTypeWriter');
  8254. if (isset($pl['opt']['it']) AND in_array($pl['opt']['it'], $tfit)) {
  8255. $annots .= ' /IT /'.$pl['opt']['it'];
  8256. }
  8257. if (isset($pl['opt']['rd']) AND is_array($pl['opt']['rd'])) {
  8258. $l = $pl['opt']['rd'][0] * $this->k;
  8259. $r = $pl['opt']['rd'][1] * $this->k;
  8260. $t = $pl['opt']['rd'][2] * $this->k;
  8261. $b = $pl['opt']['rd'][3] * $this->k;
  8262. $annots .= ' /RD ['.sprintf('%.2F %.2F %.2F %.2F', $l, $r, $t, $b).']';
  8263. }
  8264. if (isset($pl['opt']['le']) AND in_array($pl['opt']['le'], $lineendings)) {
  8265. $annots .= ' /LE /'.$pl['opt']['le'];
  8266. }
  8267. break;
  8268. }
  8269. case 'line': {
  8270. break;
  8271. }
  8272. case 'square': {
  8273. break;
  8274. }
  8275. case 'circle': {
  8276. break;
  8277. }
  8278. case 'polygon': {
  8279. break;
  8280. }
  8281. case 'polyline': {
  8282. break;
  8283. }
  8284. case 'highlight': {
  8285. break;
  8286. }
  8287. case 'underline': {
  8288. break;
  8289. }
  8290. case 'squiggly': {
  8291. break;
  8292. }
  8293. case 'strikeout': {
  8294. break;
  8295. }
  8296. case 'stamp': {
  8297. break;
  8298. }
  8299. case 'caret': {
  8300. break;
  8301. }
  8302. case 'ink': {
  8303. break;
  8304. }
  8305. case 'popup': {
  8306. break;
  8307. }
  8308. case 'fileattachment': {
  8309. if (!isset($pl['opt']['fs'])) {
  8310. break;
  8311. }
  8312. $filename = basename($pl['opt']['fs']);
  8313. if (isset($this->embeddedfiles[$filename]['n'])) {
  8314. $annots .= ' /FS <</Type /Filespec /F '.$this->_datastring($filename, $annot_obj_id).' /EF <</F '.$this->embeddedfiles[$filename]['n'].' 0 R>> >>';
  8315. $iconsapp = array('Graph', 'Paperclip', 'PushPin', 'Tag');
  8316. if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
  8317. $annots .= ' /Name /'.$pl['opt']['name'];
  8318. } else {
  8319. $annots .= ' /Name /PushPin';
  8320. }
  8321. }
  8322. break;
  8323. }
  8324. case 'sound': {
  8325. if (!isset($pl['opt']['fs'])) {
  8326. break;
  8327. }
  8328. $filename = basename($pl['opt']['fs']);
  8329. if (isset($this->embeddedfiles[$filename]['n'])) {
  8330. // ... TO BE COMPLETED ...
  8331. // /R /C /B /E /CO /CP
  8332. $annots .= ' /Sound <</Type /Filespec /F '.$this->_datastring($filename, $annot_obj_id).' /EF <</F '.$this->embeddedfiles[$filename]['n'].' 0 R>> >>';
  8333. $iconsapp = array('Speaker', 'Mic');
  8334. if (isset($pl['opt']['name']) AND in_array($pl['opt']['name'], $iconsapp)) {
  8335. $annots .= ' /Name /'.$pl['opt']['name'];
  8336. } else {
  8337. $annots .= ' /Name /Speaker';
  8338. }
  8339. }
  8340. break;
  8341. }
  8342. case 'movie': {
  8343. break;
  8344. }
  8345. case 'widget': {
  8346. $hmode = array('N', 'I', 'O', 'P', 'T');
  8347. if (isset($pl['opt']['h']) AND in_array($pl['opt']['h'], $hmode)) {
  8348. $annots .= ' /H /'.$pl['opt']['h'];
  8349. }
  8350. if (isset($pl['opt']['mk']) AND (is_array($pl['opt']['mk'])) AND !empty($pl['opt']['mk'])) {
  8351. $annots .= ' /MK <<';
  8352. if (isset($pl['opt']['mk']['r'])) {
  8353. $annots .= ' /R '.$pl['opt']['mk']['r'];
  8354. }
  8355. if (isset($pl['opt']['mk']['bc']) AND (is_array($pl['opt']['mk']['bc']))) {
  8356. $annots .= ' /BC [';
  8357. foreach($pl['opt']['mk']['bc'] AS $col) {
  8358. $col = intval($col);
  8359. $color = $col <= 0 ? 0 : ($col >= 255 ? 1 : $col / 255);
  8360. $annots .= sprintf(' %.2F', $color);
  8361. }
  8362. $annots .= ']';
  8363. }
  8364. if (isset($pl['opt']['mk']['bg']) AND (is_array($pl['opt']['mk']['bg']))) {
  8365. $annots .= ' /BG [';
  8366. foreach($pl['opt']['mk']['bg'] AS $col) {
  8367. $col = intval($col);
  8368. $color = $col <= 0 ? 0 : ($col >= 255 ? 1 : $col / 255);
  8369. $annots .= sprintf(' %.2F', $color);
  8370. }
  8371. $annots .= ']';
  8372. }
  8373. if (isset($pl['opt']['mk']['ca'])) {
  8374. $annots .= ' /CA '.$pl['opt']['mk']['ca'];
  8375. }
  8376. if (isset($pl['opt']['mk']['rc'])) {
  8377. $annots .= ' /RC '.$pl['opt']['mk']['rc'];
  8378. }
  8379. if (isset($pl['opt']['mk']['ac'])) {
  8380. $annots .= ' /AC '.$pl['opt']['mk']['ac'];
  8381. }
  8382. if (isset($pl['opt']['mk']['i'])) {
  8383. $info = $this->getImageBuffer($pl['opt']['mk']['i']);
  8384. if ($info !== false) {
  8385. $annots .= ' /I '.$info['n'].' 0 R';
  8386. }
  8387. }
  8388. if (isset($pl['opt']['mk']['ri'])) {
  8389. $info = $this->getImageBuffer($pl['opt']['mk']['ri']);
  8390. if ($info !== false) {
  8391. $annots .= ' /RI '.$info['n'].' 0 R';
  8392. }
  8393. }
  8394. if (isset($pl['opt']['mk']['ix'])) {
  8395. $info = $this->getImageBuffer($pl['opt']['mk']['ix']);
  8396. if ($info !== false) {
  8397. $annots .= ' /IX '.$info['n'].' 0 R';
  8398. }
  8399. }
  8400. if (isset($pl['opt']['mk']['if']) AND (is_array($pl['opt']['mk']['if'])) AND !empty($pl['opt']['mk']['if'])) {
  8401. $annots .= ' /IF <<';
  8402. $if_sw = array('A', 'B', 'S', 'N');
  8403. if (isset($pl['opt']['mk']['if']['sw']) AND in_array($pl['opt']['mk']['if']['sw'], $if_sw)) {
  8404. $annots .= ' /SW /'.$pl['opt']['mk']['if']['sw'];
  8405. }
  8406. $if_s = array('A', 'P');
  8407. if (isset($pl['opt']['mk']['if']['s']) AND in_array($pl['opt']['mk']['if']['s'], $if_s)) {
  8408. $annots .= ' /S /'.$pl['opt']['mk']['if']['s'];
  8409. }
  8410. if (isset($pl['opt']['mk']['if']['a']) AND (is_array($pl['opt']['mk']['if']['a'])) AND !empty($pl['opt']['mk']['if']['a'])) {
  8411. $annots .= sprintf(' /A [%.2F %.2F]', $pl['opt']['mk']['if']['a'][0], $pl['opt']['mk']['if']['a'][1]);
  8412. }
  8413. if (isset($pl['opt']['mk']['if']['fb']) AND ($pl['opt']['mk']['if']['fb'])) {
  8414. $annots .= ' /FB true';
  8415. }
  8416. $annots .= '>>';
  8417. }
  8418. if (isset($pl['opt']['mk']['tp']) AND ($pl['opt']['mk']['tp'] >= 0) AND ($pl['opt']['mk']['tp'] <= 6)) {
  8419. $annots .= ' /TP '.intval($pl['opt']['mk']['tp']);
  8420. } else {
  8421. $annots .= ' /TP 0';
  8422. }
  8423. $annots .= '>>';
  8424. } // end MK
  8425. // --- Entries for field dictionaries ---
  8426. if (isset($this->radiobutton_groups[$n][$pl['txt']])) {
  8427. // set parent
  8428. $annots .= ' /Parent '.$this->radiobutton_groups[$n][$pl['txt']].' 0 R';
  8429. }
  8430. if (isset($pl['opt']['t']) AND is_string($pl['opt']['t'])) {
  8431. $annots .= ' /T '.$this->_datastring($pl['opt']['t'], $annot_obj_id);
  8432. }
  8433. if (isset($pl['opt']['tu']) AND is_string($pl['opt']['tu'])) {
  8434. $annots .= ' /TU '.$this->_datastring($pl['opt']['tu'], $annot_obj_id);
  8435. }
  8436. if (isset($pl['opt']['tm']) AND is_string($pl['opt']['tm'])) {
  8437. $annots .= ' /TM '.$this->_datastring($pl['opt']['tm'], $annot_obj_id);
  8438. }
  8439. if (isset($pl['opt']['ff'])) {
  8440. if (is_array($pl['opt']['ff'])) {
  8441. // array of bit settings
  8442. $flag = 0;
  8443. foreach($pl['opt']['ff'] as $val) {
  8444. $flag += 1 << ($val - 1);
  8445. }
  8446. } else {
  8447. $flag = intval($pl['opt']['ff']);
  8448. }
  8449. $annots .= ' /Ff '.$flag;
  8450. }
  8451. if (isset($pl['opt']['maxlen'])) {
  8452. $annots .= ' /MaxLen '.intval($pl['opt']['maxlen']);
  8453. }
  8454. if (isset($pl['opt']['v'])) {
  8455. $annots .= ' /V';
  8456. if (is_array($pl['opt']['v'])) {
  8457. foreach ($pl['opt']['v'] AS $optval) {
  8458. if (is_float($optval)) {
  8459. $optval = sprintf('%.2F', $optval);
  8460. }
  8461. $annots .= ' '.$optval;
  8462. }
  8463. } else {
  8464. $annots .= ' '.$this->_textstring($pl['opt']['v'], $annot_obj_id);
  8465. }
  8466. }
  8467. if (isset($pl['opt']['dv'])) {
  8468. $annots .= ' /DV';
  8469. if (is_array($pl['opt']['dv'])) {
  8470. foreach ($pl['opt']['dv'] AS $optval) {
  8471. if (is_float($optval)) {
  8472. $optval = sprintf('%.2F', $optval);
  8473. }
  8474. $annots .= ' '.$optval;
  8475. }
  8476. } else {
  8477. $annots .= ' '.$this->_textstring($pl['opt']['dv'], $annot_obj_id);
  8478. }
  8479. }
  8480. if (isset($pl['opt']['rv'])) {
  8481. $annots .= ' /RV';
  8482. if (is_array($pl['opt']['rv'])) {
  8483. foreach ($pl['opt']['rv'] AS $optval) {
  8484. if (is_float($optval)) {
  8485. $optval = sprintf('%.2F', $optval);
  8486. }
  8487. $annots .= ' '.$optval;
  8488. }
  8489. } else {
  8490. $annots .= ' '.$this->_textstring($pl['opt']['rv'], $annot_obj_id);
  8491. }
  8492. }
  8493. if (isset($pl['opt']['a']) AND !empty($pl['opt']['a'])) {
  8494. $annots .= ' /A << '.$pl['opt']['a'].' >>';
  8495. }
  8496. if (isset($pl['opt']['aa']) AND !empty($pl['opt']['aa'])) {
  8497. $annots .= ' /AA << '.$pl['opt']['aa'].' >>';
  8498. }
  8499. if (isset($pl['opt']['da']) AND !empty($pl['opt']['da'])) {
  8500. $annots .= ' /DA ('.$pl['opt']['da'].')';
  8501. }
  8502. if (isset($pl['opt']['q']) AND ($pl['opt']['q'] >= 0) AND ($pl['opt']['q'] <= 2)) {
  8503. $annots .= ' /Q '.intval($pl['opt']['q']);
  8504. }
  8505. if (isset($pl['opt']['opt']) AND (is_array($pl['opt']['opt'])) AND !empty($pl['opt']['opt'])) {
  8506. $annots .= ' /Opt [';
  8507. foreach($pl['opt']['opt'] AS $copt) {
  8508. if (is_array($copt)) {
  8509. $annots .= ' ['.$this->_textstring($copt[0], $annot_obj_id).' '.$this->_textstring($copt[1], $annot_obj_id).']';
  8510. } else {
  8511. $annots .= ' '.$this->_textstring($copt, $annot_obj_id);
  8512. }
  8513. }
  8514. $annots .= ']';
  8515. }
  8516. if (isset($pl['opt']['ti'])) {
  8517. $annots .= ' /TI '.intval($pl['opt']['ti']);
  8518. }
  8519. if (isset($pl['opt']['i']) AND (is_array($pl['opt']['i'])) AND !empty($pl['opt']['i'])) {
  8520. $annots .= ' /I [';
  8521. foreach($pl['opt']['i'] AS $copt) {
  8522. $annots .= intval($copt).' ';
  8523. }
  8524. $annots .= ']';
  8525. }
  8526. break;
  8527. }
  8528. case 'screen': {
  8529. break;
  8530. }
  8531. case 'printermark': {
  8532. break;
  8533. }
  8534. case 'trapnet': {
  8535. break;
  8536. }
  8537. case 'watermark': {
  8538. break;
  8539. }
  8540. case '3d': {
  8541. break;
  8542. }
  8543. default: {
  8544. break;
  8545. }
  8546. }
  8547. $annots .= '>>';
  8548. // create new annotation object
  8549. $this->_out($this->_getobj($annot_obj_id)."\n".$annots."\n".'endobj');
  8550. if ($formfield AND !isset($this->radiobutton_groups[$n][$pl['txt']])) {
  8551. // store reference of form object
  8552. $this->form_obj_id[] = $annot_obj_id;
  8553. }
  8554. }
  8555. }
  8556. } // end for each page
  8557. }
  8558. /**
  8559. * Put appearance streams XObject used to define annotation's appearance states
  8560. * @param int $w annotation width
  8561. * @param int $h annotation height
  8562. * @param string $stream appearance stream
  8563. * @return int object ID
  8564. * @access protected
  8565. * @since 4.8.001 (2009-09-09)
  8566. */
  8567. protected function _putAPXObject($w=0, $h=0, $stream='') {
  8568. $stream = trim($stream);
  8569. $out = $this->_getobj()."\n";
  8570. $this->xobjects['AX'.$this->n] = array('n' => $this->n);
  8571. $out .= '<<';
  8572. $out .= ' /Type /XObject';
  8573. $out .= ' /Subtype /Form';
  8574. $out .= ' /FormType 1';
  8575. if ($this->compress) {
  8576. $stream = gzcompress($stream);
  8577. $out .= ' /Filter /FlateDecode';
  8578. }
  8579. $rect = sprintf('%.2F %.2F', $w, $h);
  8580. $out .= ' /BBox [0 0 '.$rect.']';
  8581. $out .= ' /Matrix [1 0 0 1 0 0]';
  8582. $out .= ' /Resources <<';
  8583. $out .= ' /ProcSet [/PDF /Text]';
  8584. $out .= ' /Font <<';
  8585. foreach ($this->annotation_fonts as $fontkey => $fontid) {
  8586. $out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
  8587. }
  8588. $out .= ' >>';
  8589. $out .= ' >>';
  8590. $stream = $this->_getrawstream($stream);
  8591. $out .= ' /Length '.strlen($stream);
  8592. $out .= ' >>';
  8593. $out .= ' stream'."\n".$stream."\n".'endstream';
  8594. $out .= "\n".'endobj';
  8595. $this->_out($out);
  8596. return $this->n;
  8597. }
  8598. /**
  8599. * Get ULONG from string (Big Endian 32-bit unsigned integer).
  8600. * @param string $str string from where to extract value
  8601. * @param int $offset point from where to read the data
  8602. * @return int 32 bit value
  8603. * @author Nicola Asuni
  8604. * @access protected
  8605. * @since 5.2.000 (2010-06-02)
  8606. */
  8607. protected function _getULONG(&$str, &$offset) {
  8608. $v = unpack('Ni', substr($str, $offset, 4));
  8609. $offset += 4;
  8610. return $v['i'];
  8611. }
  8612. /**
  8613. * Get USHORT from string (Big Endian 16-bit unsigned integer).
  8614. * @param string $str string from where to extract value
  8615. * @param int $offset point from where to read the data
  8616. * @return int 16 bit value
  8617. * @author Nicola Asuni
  8618. * @access protected
  8619. * @since 5.2.000 (2010-06-02)
  8620. */
  8621. protected function _getUSHORT(&$str, &$offset) {
  8622. $v = unpack('ni', substr($str, $offset, 2));
  8623. $offset += 2;
  8624. return $v['i'];
  8625. }
  8626. /**
  8627. * Get SHORT from string (Big Endian 16-bit signed integer).
  8628. * @param string $str string from where to extract value
  8629. * @param int $offset point from where to read the data
  8630. * @return int 16 bit value
  8631. * @author Nicola Asuni
  8632. * @access protected
  8633. * @since 5.2.000 (2010-06-02)
  8634. */
  8635. protected function _getSHORT(&$str, &$offset) {
  8636. $v = unpack('si', substr($str, $offset, 2));
  8637. $offset += 2;
  8638. return $v['i'];
  8639. }
  8640. /**
  8641. * Get BYTE from string (8-bit unsigned integer).
  8642. * @param string $str string from where to extract value
  8643. * @param int $offset point from where to read the data
  8644. * @return int 8 bit value
  8645. * @author Nicola Asuni
  8646. * @access protected
  8647. * @since 5.2.000 (2010-06-02)
  8648. */
  8649. protected function _getBYTE(&$str, &$offset) {
  8650. $v = unpack('Ci', substr($str, $offset, 1));
  8651. ++$offset;
  8652. return $v['i'];
  8653. }
  8654. /**
  8655. * Returns a subset of the TrueType font data without the unused glyphs.
  8656. * @param string $font TrueType font data
  8657. * @param array $subsetchars array of used characters (the glyphs to keep)
  8658. * @return string a subset of TrueType font data without the unused glyphs
  8659. * @author Nicola Asuni
  8660. * @access protected
  8661. * @since 5.2.000 (2010-06-02)
  8662. */
  8663. protected function _getTrueTypeFontSubset($font, $subsetchars) {
  8664. ksort($subsetchars);
  8665. $offset = 0; // offset position of the font data
  8666. if ($this->_getULONG($font, $offset) != 0x10000) {
  8667. // sfnt version must be 0x00010000 for TrueType version 1.0.
  8668. return $font;
  8669. }
  8670. // get number of tables
  8671. $numTables = $this->_getUSHORT($font, $offset);
  8672. // skip searchRange, entrySelector and rangeShift
  8673. $offset += 6;
  8674. // tables array
  8675. $table = array();
  8676. // for each table
  8677. for ($i = 0; $i < $numTables; ++$i) {
  8678. // get table info
  8679. $tag = substr($font, $offset, 4);
  8680. $offset += 4;
  8681. $table[$tag] = array();
  8682. $table[$tag]['checkSum'] = $this->_getULONG($font, $offset);
  8683. $table[$tag]['offset'] = $this->_getULONG($font, $offset);
  8684. $table[$tag]['length'] = $this->_getULONG($font, $offset);
  8685. }
  8686. // check magicNumber
  8687. $offset = $table['head']['offset'] + 12;
  8688. if ($this->_getULONG($font, $offset) != 0x5F0F3CF5) {
  8689. // magicNumber must be 0x5F0F3CF5
  8690. return $font;
  8691. }
  8692. // get offset mode (indexToLocFormat : 0 = short, 1 = long)
  8693. $offset = $table['head']['offset'] + 50;
  8694. $short_offset = ($this->_getSHORT($font, $offset) == 0);
  8695. // get the offsets to the locations of the glyphs in the font, relative to the beginning of the glyphData table
  8696. $indexToLoc = array();
  8697. $offset = $table['loca']['offset'];
  8698. if ($short_offset) {
  8699. // short version
  8700. $n = $table['loca']['length'] / 2; // numGlyphs + 1
  8701. for ($i = 0; $i < $n; ++$i) {
  8702. $indexToLoc[$i] = $this->_getUSHORT($font, $offset) * 2;
  8703. }
  8704. } else {
  8705. // long version
  8706. $n = $table['loca']['length'] / 4; // numGlyphs + 1
  8707. for ($i = 0; $i < $n; ++$i) {
  8708. $indexToLoc[$i] = $this->_getULONG($font, $offset);
  8709. }
  8710. }
  8711. // get glyphs indexes of chars from cmap table
  8712. $subsetglyphs = array(); // glyph IDs on key
  8713. $subsetglyphs[0] = true; // character codes that do not correspond to any glyph in the font should be mapped to glyph index 0
  8714. $offset = $table['cmap']['offset'] + 2;
  8715. $numEncodingTables = $this->_getUSHORT($font, $offset);
  8716. $encodingTables = array();
  8717. for ($i = 0; $i < $numEncodingTables; ++$i) {
  8718. $encodingTables[$i]['platformID'] = $this->_getUSHORT($font, $offset);
  8719. $encodingTables[$i]['encodingID'] = $this->_getUSHORT($font, $offset);
  8720. $encodingTables[$i]['offset'] = $this->_getULONG($font, $offset);
  8721. }
  8722. foreach ($encodingTables as $enctable) {
  8723. if (($enctable['platformID'] == 3) AND ($enctable['encodingID'] == 0)) {
  8724. $modesymbol = true;
  8725. } else {
  8726. $modesymbol = false;
  8727. }
  8728. $offset = $table['cmap']['offset'] + $enctable['offset'];
  8729. $format = $this->_getUSHORT($font, $offset);
  8730. switch ($format) {
  8731. case 0: { // Format 0: Byte encoding table
  8732. $offset += 4; // skip length and version/language
  8733. for ($k = 0; $k < 256; ++$k) {
  8734. if (isset($subsetchars[$k])) {
  8735. $g = $this->_getBYTE($font, $offset);
  8736. $subsetglyphs[$g] = $k;
  8737. } else {
  8738. ++$offset;
  8739. }
  8740. }
  8741. break;
  8742. }
  8743. case 2: { // Format 2: High-byte mapping through table
  8744. $offset += 4; // skip length and version
  8745. // to be implemented ...
  8746. break;
  8747. }
  8748. case 4: { // Format 4: Segment mapping to delta values
  8749. $length = $this->_getUSHORT($font, $offset);
  8750. $offset += 2; // skip version/language
  8751. $segCount = ($this->_getUSHORT($font, $offset) / 2);
  8752. $offset += 6; // skip searchRange, entrySelector, rangeShift
  8753. $endCount = array(); // array of end character codes for each segment
  8754. for ($k = 0; $k < $segCount; ++$k) {
  8755. $endCount[$k] = $this->_getUSHORT($font, $offset);
  8756. }
  8757. $offset += 2; // skip reservedPad
  8758. $startCount = array(); // array of start character codes for each segment
  8759. for ($k = 0; $k < $segCount; ++$k) {
  8760. $startCount[$k] = $this->_getUSHORT($font, $offset);
  8761. }
  8762. $idDelta = array(); // delta for all character codes in segment
  8763. for ($k = 0; $k < $segCount; ++$k) {
  8764. $idDelta[$k] = $this->_getUSHORT($font, $offset);
  8765. }
  8766. $idRangeOffset = array(); // Offsets into glyphIdArray or 0
  8767. for ($k = 0; $k < $segCount; ++$k) {
  8768. $idRangeOffset[$k] = $this->_getUSHORT($font, $offset);
  8769. }
  8770. $gidlen = ($length / 2) - 8 - (4 * $segCount);
  8771. $glyphIdArray = array(); // glyph index array
  8772. for ($k = 0; $k < $gidlen; ++$k) {
  8773. $glyphIdArray[$k] = $this->_getUSHORT($font, $offset);
  8774. }
  8775. for ($k = 0; $k < $segCount; ++$k) {
  8776. for ($c = $startCount[$k]; $c <= $endCount[$k]; ++$c) {
  8777. if (isset($subsetchars[$c])) {
  8778. if ($idRangeOffset[$k] == 0) {
  8779. $g = $c;
  8780. } else {
  8781. $gid = (($idRangeOffset[$k] / 2) + ($c - $startCount[$k]) - ($segCount - $k));
  8782. $g = $glyphIdArray[$gid];
  8783. }
  8784. $g += ($idDelta[$k] - 65536);
  8785. if ($g < 0) {
  8786. $g = 0;
  8787. }
  8788. $subsetglyphs[$g] = $c;
  8789. }
  8790. }
  8791. }
  8792. break;
  8793. }
  8794. case 6: { // Format 6: Trimmed table mapping
  8795. $offset += 4; // skip length and version/language
  8796. $firstCode = $this->_getUSHORT($font, $offset);
  8797. $entryCount = $this->_getUSHORT($font, $offset);
  8798. for ($k = 0; $k < $entryCount; ++$k) {
  8799. $c = ($k + $firstCode);
  8800. if (isset($subsetchars[$c])) {
  8801. $g = $this->_getUSHORT($font, $offset);
  8802. $subsetglyphs[$g] = $c;
  8803. } else {
  8804. $offset += 2;
  8805. }
  8806. }
  8807. break;
  8808. }
  8809. case 8: { // Format 8: Mixed 16-bit and 32-bit coverage
  8810. $offset += 10; // skip length and version
  8811. // to be implemented ...
  8812. break;
  8813. }
  8814. case 10: { // Format 10: Trimmed array
  8815. $offset += 10; // skip length and version/language
  8816. $startCharCode = $this->_getULONG($font, $offset);
  8817. $numChars = $this->_getULONG($font, $offset);
  8818. for ($k = 0; $k < $numChars; ++$k) {
  8819. $c = ($k + $startCharCode);
  8820. if (isset($subsetchars[$c])) {
  8821. $g = $this->_getUSHORT($font, $offset);
  8822. $subsetglyphs[$g] = $c;
  8823. } else {
  8824. $offset += 2;
  8825. }
  8826. }
  8827. break;
  8828. }
  8829. case 12: { // Format 12: Segmented coverage
  8830. $offset += 10; // skip length and version/language
  8831. $nGroups = $this->_getULONG($font, $offset);
  8832. for ($k = 0; $k < $nGroups; ++$k) {
  8833. $startCharCode = $this->_getULONG($font, $offset);
  8834. $endCharCode = $this->_getULONG($font, $offset);
  8835. $startGlyphCode = $this->_getULONG($font, $offset);
  8836. for ($c = $startCharCode; $c <= $endCharCode; ++$c) {
  8837. if (isset($subsetchars[$c])) {
  8838. $subsetglyphs[$startGlyphCode] = $c;
  8839. }
  8840. ++$startGlyphCode;
  8841. }
  8842. }
  8843. break;
  8844. }
  8845. }
  8846. }
  8847. // sort glyphs by key
  8848. ksort($subsetglyphs);
  8849. // add composite glyps to $subsetglyphs and remove missing glyphs
  8850. foreach ($subsetglyphs as $key => $val) {
  8851. if (isset($indexToLoc[$key])) {
  8852. $offset = $table['glyf']['offset'] + $indexToLoc[$key];
  8853. $numberOfContours = $this->_getSHORT($font, $offset);
  8854. if ($numberOfContours < 0) { // composite glyph
  8855. $offset += 8; // skip xMin, yMin, xMax, yMax
  8856. do {
  8857. $flags = $this->_getUSHORT($font, $offset);
  8858. $glyphIndex = $this->_getUSHORT($font, $offset);
  8859. if (!isset($subsetglyphs[$glyphIndex]) AND isset($indexToLoc[$glyphIndex])) {
  8860. // add missing glyphs
  8861. $subsetglyphs[$glyphIndex] = true;
  8862. }
  8863. // skip some bytes by case
  8864. if ($flags & 1) {
  8865. $offset += 4;
  8866. } else {
  8867. $offset += 2;
  8868. }
  8869. if ($flags & 8) {
  8870. $offset += 2;
  8871. } elseif ($flags & 64) {
  8872. $offset += 4;
  8873. } elseif ($flags & 128) {
  8874. $offset += 8;
  8875. }
  8876. } while ($flags & 32);
  8877. }
  8878. } else {
  8879. unset($subsetglyphs[$key]);
  8880. }
  8881. }
  8882. // build new glyf table with only used glyphs
  8883. $glyf = '';
  8884. $glyfSize = 0;
  8885. // create new empty indexToLoc table
  8886. $newIndexToLoc = array_fill(0, count($indexToLoc), 0);
  8887. $goffset = 0;
  8888. foreach ($subsetglyphs as $glyphID => $char) {
  8889. if (isset($indexToLoc[$glyphID]) AND isset($indexToLoc[($glyphID + 1)])) {
  8890. $start = $indexToLoc[$glyphID];
  8891. $length = ($indexToLoc[($glyphID + 1)] - $start);
  8892. $glyf .= substr($font, ($table['glyf']['offset'] + $start), $length);
  8893. $newIndexToLoc[$glyphID] = $goffset;
  8894. $goffset += $length;
  8895. }
  8896. }
  8897. // build new loca table
  8898. $loca = '';
  8899. if ($short_offset) {
  8900. foreach ($newIndexToLoc as $glyphID => $offset) {
  8901. $loca .= pack('n', ($offset / 2));
  8902. }
  8903. } else {
  8904. foreach ($newIndexToLoc as $glyphID => $offset) {
  8905. $loca .= pack('N', $offset);
  8906. }
  8907. }
  8908. // array of table names to preserve (loca and glyf tables will be added later)
  8909. //$table_names = array ('cmap', 'head', 'hhea', 'hmtx', 'maxp', 'name', 'OS/2', 'post', 'cvt ', 'fpgm', 'prep');
  8910. // the cmap table is not needed and shall not be present, since the mapping from character codes to glyph descriptions is provided separately
  8911. $table_names = array ('head', 'hhea', 'hmtx', 'maxp', 'cvt ', 'fpgm', 'prep'); // minimum required table names
  8912. // get the tables to preserve
  8913. $offset = 12;
  8914. foreach ($table as $tag => $val) {
  8915. if (in_array($tag, $table_names)) {
  8916. $table[$tag]['data'] = substr($font, $table[$tag]['offset'], $table[$tag]['length']);
  8917. if ($tag == 'head') {
  8918. // set the checkSumAdjustment to 0
  8919. $table[$tag]['data'] = substr($table[$tag]['data'], 0, 8)."\x0\x0\x0\x0".substr($table[$tag]['data'], 12);
  8920. }
  8921. $pad = 4 - ($table[$tag]['length'] % 4);
  8922. if ($pad != 4) {
  8923. // the length of a table must be a multiple of four bytes
  8924. $table[$tag]['length'] += $pad;
  8925. $table[$tag]['data'] .= str_repeat("\x0", $pad);
  8926. }
  8927. $table[$tag]['offset'] = $offset;
  8928. $offset += $table[$tag]['length'];
  8929. // check sum is not changed (so keep the following line commented)
  8930. //$table[$tag]['checkSum'] = $this->_getTTFtableChecksum($table[$tag]['data'], $table[$tag]['length']);
  8931. } else {
  8932. unset($table[$tag]);
  8933. }
  8934. }
  8935. // add loca
  8936. $table['loca']['data'] = $loca;
  8937. $table['loca']['length'] = strlen($loca);
  8938. $pad = 4 - ($table['loca']['length'] % 4);
  8939. if ($pad != 4) {
  8940. // the length of a table must be a multiple of four bytes
  8941. $table['loca']['length'] += $pad;
  8942. $table['loca']['data'] .= str_repeat("\x0", $pad);
  8943. }
  8944. $table['loca']['offset'] = $offset;
  8945. $table['loca']['checkSum'] = $this->_getTTFtableChecksum($table['loca']['data'], $table['loca']['length']);
  8946. $offset += $table['loca']['length'];
  8947. // add glyf
  8948. $table['glyf']['data'] = $glyf;
  8949. $table['glyf']['length'] = strlen($glyf);
  8950. $pad = 4 - ($table['glyf']['length'] % 4);
  8951. if ($pad != 4) {
  8952. // the length of a table must be a multiple of four bytes
  8953. $table['glyf']['length'] += $pad;
  8954. $table['glyf']['data'] .= str_repeat("\x0", $pad);
  8955. }
  8956. $table['glyf']['offset'] = $offset;
  8957. $table['glyf']['checkSum'] = $this->_getTTFtableChecksum($table['glyf']['data'], $table['glyf']['length']);
  8958. // rebuild font
  8959. $font = '';
  8960. $font .= pack('N', 0x10000); // sfnt version
  8961. $numTables = count($table);
  8962. $font .= pack('n', $numTables); // numTables
  8963. $entrySelector = floor(log($numTables, 2));
  8964. $searchRange = pow(2, $entrySelector) * 16;
  8965. $rangeShift = ($numTables * 16) - $searchRange;
  8966. $font .= pack('n', $searchRange); // searchRange
  8967. $font .= pack('n', $entrySelector); // entrySelector
  8968. $font .= pack('n', $rangeShift); // rangeShift
  8969. $offset = ($numTables * 16);
  8970. foreach ($table as $tag => $data) {
  8971. $font .= $tag; // tag
  8972. $font .= pack('N', $data['checkSum']); // checkSum
  8973. $font .= pack('N', ($data['offset'] + $offset)); // offset
  8974. $font .= pack('N', $data['length']); // length
  8975. }
  8976. foreach ($table as $data) {
  8977. $font .= $data['data'];
  8978. }
  8979. // set checkSumAdjustment on head table
  8980. $checkSumAdjustment = 0xB1B0AFBA - $this->_getTTFtableChecksum($font, strlen($font));
  8981. $font = substr($font, 0, $table['head']['offset'] + 8).pack('N', $checkSumAdjustment).substr($font, $table['head']['offset'] + 12);
  8982. return $font;
  8983. }
  8984. /**
  8985. * Returs the checksum of a TTF table.
  8986. * @param string $table table to check
  8987. * @param int $length lenght of table in bytes
  8988. * @return int checksum
  8989. * @author Nicola Asuni
  8990. * @access protected
  8991. * @since 5.2.000 (2010-06-02)
  8992. */
  8993. protected function _getTTFtableChecksum($table, $length) {
  8994. $sum = 0;
  8995. $tlen = ($length / 4);
  8996. $offset = 0;
  8997. for ($i = 0; $i < $tlen; ++$i) {
  8998. $v = unpack('Ni', substr($table, $offset, 4));
  8999. $sum += $v['i'];
  9000. $offset += 4;
  9001. }
  9002. $sum = unpack('Ni', pack('N', $sum));
  9003. return $sum['i'];
  9004. }
  9005. /**
  9006. * Outputs font widths
  9007. * @param array $font font data
  9008. * @param int $cidoffset offset for CID values
  9009. * @return PDF command string for font widths
  9010. * @author Nicola Asuni
  9011. * @access protected
  9012. * @since 4.4.000 (2008-12-07)
  9013. */
  9014. protected function _putfontwidths($font, $cidoffset=0) {
  9015. ksort($font['cw']);
  9016. $rangeid = 0;
  9017. $range = array();
  9018. $prevcid = -2;
  9019. $prevwidth = -1;
  9020. $interval = false;
  9021. // for each character
  9022. foreach ($font['cw'] as $cid => $width) {
  9023. $cid -= $cidoffset;
  9024. if ($font['subset'] AND ($cid > 255) AND (!isset($font['subsetchars'][$cid]))) {
  9025. // ignore the unused characters (font subsetting)
  9026. continue;
  9027. }
  9028. if ($width != $font['dw']) {
  9029. if ($cid == ($prevcid + 1)) {
  9030. // consecutive CID
  9031. if ($width == $prevwidth) {
  9032. if ($width == $range[$rangeid][0]) {
  9033. $range[$rangeid][] = $width;
  9034. } else {
  9035. array_pop($range[$rangeid]);
  9036. // new range
  9037. $rangeid = $prevcid;
  9038. $range[$rangeid] = array();
  9039. $range[$rangeid][] = $prevwidth;
  9040. $range[$rangeid][] = $width;
  9041. }
  9042. $interval = true;
  9043. $range[$rangeid]['interval'] = true;
  9044. } else {
  9045. if ($interval) {
  9046. // new range
  9047. $rangeid = $cid;
  9048. $range[$rangeid] = array();
  9049. $range[$rangeid][] = $width;
  9050. } else {
  9051. $range[$rangeid][] = $width;
  9052. }
  9053. $interval = false;
  9054. }
  9055. } else {
  9056. // new range
  9057. $rangeid = $cid;
  9058. $range[$rangeid] = array();
  9059. $range[$rangeid][] = $width;
  9060. $interval = false;
  9061. }
  9062. $prevcid = $cid;
  9063. $prevwidth = $width;
  9064. }
  9065. }
  9066. // optimize ranges
  9067. $prevk = -1;
  9068. $nextk = -1;
  9069. $prevint = false;
  9070. foreach ($range as $k => $ws) {
  9071. $cws = count($ws);
  9072. if (($k == $nextk) AND (!$prevint) AND ((!isset($ws['interval'])) OR ($cws < 4))) {
  9073. if (isset($range[$k]['interval'])) {
  9074. unset($range[$k]['interval']);
  9075. }
  9076. $range[$prevk] = array_merge($range[$prevk], $range[$k]);
  9077. unset($range[$k]);
  9078. } else {
  9079. $prevk = $k;
  9080. }
  9081. $nextk = $k + $cws;
  9082. if (isset($ws['interval'])) {
  9083. if ($cws > 3) {
  9084. $prevint = true;
  9085. } else {
  9086. $prevint = false;
  9087. }
  9088. unset($range[$k]['interval']);
  9089. --$nextk;
  9090. } else {
  9091. $prevint = false;
  9092. }
  9093. }
  9094. // output data
  9095. $w = '';
  9096. foreach ($range as $k => $ws) {
  9097. if (count(array_count_values($ws)) == 1) {
  9098. // interval mode is more compact
  9099. $w .= ' '.$k.' '.($k + count($ws) - 1).' '.$ws[0];
  9100. } else {
  9101. // range mode
  9102. $w .= ' '.$k.' [ '.implode(' ', $ws).' ]';
  9103. }
  9104. }
  9105. return '/W ['.$w.' ]';
  9106. }
  9107. /**
  9108. * Output fonts.
  9109. * @author Nicola Asuni
  9110. * @access protected
  9111. */
  9112. protected function _putfonts() {
  9113. $nf = $this->n;
  9114. foreach ($this->diffs as $diff) {
  9115. //Encodings
  9116. $this->_newobj();
  9117. $this->_out('<< /Type /Encoding /BaseEncoding /WinAnsiEncoding /Differences ['.$diff.'] >>'."\n".'endobj');
  9118. }
  9119. $mqr = $this->get_mqr();
  9120. $this->set_mqr(false);
  9121. foreach ($this->FontFiles as $file => $info) {
  9122. // search and get font file to embedd
  9123. $fontdir = $info['fontdir'];
  9124. $file = strtolower($file);
  9125. $fontfile = '';
  9126. // search files on various directories
  9127. if (($fontdir !== false) AND file_exists($fontdir.$file)) {
  9128. $fontfile = $fontdir.$file;
  9129. } elseif (file_exists($this->_getfontpath().$file)) {
  9130. $fontfile = $this->_getfontpath().$file;
  9131. } elseif (file_exists($file)) {
  9132. $fontfile = $file;
  9133. }
  9134. if (!$this->empty_string($fontfile)) {
  9135. $font = file_get_contents($fontfile);
  9136. $compressed = (substr($file, -2) == '.z');
  9137. if ((!$compressed) AND (isset($info['length2']))) {
  9138. $header = (ord($font{0}) == 128);
  9139. if ($header) {
  9140. //Strip first binary header
  9141. $font = substr($font, 6);
  9142. }
  9143. if ($header AND (ord($font{$info['length1']}) == 128)) {
  9144. //Strip second binary header
  9145. $font = substr($font, 0, $info['length1']).substr($font, ($info['length1'] + 6));
  9146. }
  9147. } elseif ($info['subset'] AND ((!$compressed) OR ($compressed AND function_exists('gzcompress')))) {
  9148. if ($compressed) {
  9149. // uncompress font
  9150. $font = gzuncompress($font);
  9151. }
  9152. // merge subset characters
  9153. $subsetchars = array(); // used chars
  9154. foreach ($info['fontkeys'] as $fontkey) {
  9155. $fontinfo = $this->getFontBuffer($fontkey);
  9156. $subsetchars += $fontinfo['subsetchars'];
  9157. }
  9158. $font = $this->_getTrueTypeFontSubset($font, $subsetchars);
  9159. if ($compressed) {
  9160. // recompress font
  9161. $font = gzcompress($font);
  9162. }
  9163. }
  9164. $this->_newobj();
  9165. $this->FontFiles[$file]['n'] = $this->n;
  9166. $stream = $this->_getrawstream($font);
  9167. $out = '<< /Length '.strlen($stream);
  9168. if ($compressed) {
  9169. $out .= ' /Filter /FlateDecode';
  9170. }
  9171. $out .= ' /Length1 '.$info['length1'];
  9172. if (isset($info['length2'])) {
  9173. $out .= ' /Length2 '.$info['length2'].' /Length3 0';
  9174. }
  9175. $out .= ' >>';
  9176. $out .= ' stream'."\n".$stream."\n".'endstream';
  9177. $out .= "\n".'endobj';
  9178. $this->_out($out);
  9179. }
  9180. }
  9181. $this->set_mqr($mqr);
  9182. foreach ($this->fontkeys as $k) {
  9183. //Font objects
  9184. $font = $this->getFontBuffer($k);
  9185. $type = $font['type'];
  9186. $name = $font['name'];
  9187. if ($type == 'core') {
  9188. // standard core font
  9189. $out = $this->_getobj($this->font_obj_ids[$k])."\n";
  9190. $out .= '<</Type /Font';
  9191. $out .= ' /Subtype /Type1';
  9192. $out .= ' /BaseFont /'.$name;
  9193. $out .= ' /Name /F'.$font['i'];
  9194. if ((strtolower($name) != 'symbol') AND (strtolower($name) != 'zapfdingbats')) {
  9195. $out .= ' /Encoding /WinAnsiEncoding';
  9196. }
  9197. if ($k == 'helvetica') {
  9198. // add default font for annotations
  9199. $this->annotation_fonts[$k] = $font['i'];
  9200. }
  9201. $out .= ' >>';
  9202. $out .= "\n".'endobj';
  9203. $this->_out($out);
  9204. } elseif (($type == 'Type1') OR ($type == 'TrueType')) {
  9205. // additional Type1 or TrueType font
  9206. $out = $this->_getobj($this->font_obj_ids[$k])."\n";
  9207. $out .= '<</Type /Font';
  9208. $out .= ' /Subtype /'.$type;
  9209. $out .= ' /BaseFont /'.$name;
  9210. $out .= ' /Name /F'.$font['i'];
  9211. $out .= ' /FirstChar 32 /LastChar 255';
  9212. $out .= ' /Widths '.($this->n + 1).' 0 R';
  9213. $out .= ' /FontDescriptor '.($this->n + 2).' 0 R';
  9214. if ($font['enc']) {
  9215. if (isset($font['diff'])) {
  9216. $out .= ' /Encoding '.($nf + $font['diff']).' 0 R';
  9217. } else {
  9218. $out .= ' /Encoding /WinAnsiEncoding';
  9219. }
  9220. }
  9221. $out .= ' >>';
  9222. $out .= "\n".'endobj';
  9223. $this->_out($out);
  9224. // Widths
  9225. $this->_newobj();
  9226. $cw = &$font['cw'];
  9227. $s = '[';
  9228. for ($i = 32; $i < 256; ++$i) {
  9229. $s .= $cw[$i].' ';
  9230. }
  9231. $s .= ']';
  9232. $s .= "\n".'endobj';
  9233. $this->_out($s);
  9234. //Descriptor
  9235. $this->_newobj();
  9236. $s = '<</Type /FontDescriptor /FontName /'.$name;
  9237. foreach ($font['desc'] as $fdk => $fdv) {
  9238. if(is_float($fdv)) {
  9239. $fdv = sprintf('%.3F', $fdv);
  9240. }
  9241. $s .= ' /'.$fdk.' '.$fdv.'';
  9242. }
  9243. if (!$this->empty_string($font['file'])) {
  9244. $s .= ' /FontFile'.($type == 'Type1' ? '' : '2').' '.$this->FontFiles[$font['file']]['n'].' 0 R';
  9245. }
  9246. $s .= '>>';
  9247. $s .= "\n".'endobj';
  9248. $this->_out($s);
  9249. } else {
  9250. // additional types
  9251. $mtd = '_put'.strtolower($type);
  9252. if (!method_exists($this, $mtd)) {
  9253. $this->Error('Unsupported font type: '.$type);
  9254. }
  9255. $this->$mtd($font);
  9256. }
  9257. }
  9258. }
  9259. /**
  9260. * Adds unicode fonts.<br>
  9261. * Based on PDF Reference 1.3 (section 5)
  9262. * @param array $font font data
  9263. * @access protected
  9264. * @author Nicola Asuni
  9265. * @since 1.52.0.TC005 (2005-01-05)
  9266. */
  9267. protected function _puttruetypeunicode($font) {
  9268. $fontname = '';
  9269. if ($font['subset']) {
  9270. // change name for font subsetting
  9271. $subtag = sprintf('%06u', $font['i']);
  9272. $subtag = strtr($subtag, '0123456789', 'ABCDEFGHIJ');
  9273. $fontname .= $subtag.'+';
  9274. }
  9275. $fontname .= $font['name'];
  9276. // Type0 Font
  9277. // A composite font composed of other fonts, organized hierarchically
  9278. $out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
  9279. $out .= '<< /Type /Font';
  9280. $out .= ' /Subtype /Type0';
  9281. $out .= ' /BaseFont /'.$fontname;
  9282. $out .= ' /Name /F'.$font['i'];
  9283. $out .= ' /Encoding /'.$font['enc'];
  9284. $out .= ' /ToUnicode '.($this->n + 1).' 0 R';
  9285. $out .= ' /DescendantFonts ['.($this->n + 2).' 0 R]';
  9286. $out .= ' >>';
  9287. $out .= "\n".'endobj';
  9288. $this->_out($out);
  9289. // ToUnicode map for Identity-H
  9290. $stream = "/CIDInit /ProcSet findresource begin\n";
  9291. $stream .= "12 dict begin\n";
  9292. $stream .= "begincmap\n";
  9293. $stream .= "/CIDSystemInfo << /Registry (Adobe) /Ordering (UCS) /Supplement 0 >> def\n";
  9294. $stream .= "/CMapName /Adobe-Identity-UCS def\n";
  9295. $stream .= "/CMapType 2 def\n";
  9296. $stream .= "/WMode 0 def\n";
  9297. $stream .= "1 begincodespacerange\n";
  9298. $stream .= "<0000> <FFFF>\n";
  9299. $stream .= "endcodespacerange\n";
  9300. $stream .= "100 beginbfrange\n";
  9301. $stream .= "<0000> <00ff> <0000>\n";
  9302. $stream .= "<0100> <01ff> <0100>\n";
  9303. $stream .= "<0200> <02ff> <0200>\n";
  9304. $stream .= "<0300> <03ff> <0300>\n";
  9305. $stream .= "<0400> <04ff> <0400>\n";
  9306. $stream .= "<0500> <05ff> <0500>\n";
  9307. $stream .= "<0600> <06ff> <0600>\n";
  9308. $stream .= "<0700> <07ff> <0700>\n";
  9309. $stream .= "<0800> <08ff> <0800>\n";
  9310. $stream .= "<0900> <09ff> <0900>\n";
  9311. $stream .= "<0a00> <0aff> <0a00>\n";
  9312. $stream .= "<0b00> <0bff> <0b00>\n";
  9313. $stream .= "<0c00> <0cff> <0c00>\n";
  9314. $stream .= "<0d00> <0dff> <0d00>\n";
  9315. $stream .= "<0e00> <0eff> <0e00>\n";
  9316. $stream .= "<0f00> <0fff> <0f00>\n";
  9317. $stream .= "<1000> <10ff> <1000>\n";
  9318. $stream .= "<1100> <11ff> <1100>\n";
  9319. $stream .= "<1200> <12ff> <1200>\n";
  9320. $stream .= "<1300> <13ff> <1300>\n";
  9321. $stream .= "<1400> <14ff> <1400>\n";
  9322. $stream .= "<1500> <15ff> <1500>\n";
  9323. $stream .= "<1600> <16ff> <1600>\n";
  9324. $stream .= "<1700> <17ff> <1700>\n";
  9325. $stream .= "<1800> <18ff> <1800>\n";
  9326. $stream .= "<1900> <19ff> <1900>\n";
  9327. $stream .= "<1a00> <1aff> <1a00>\n";
  9328. $stream .= "<1b00> <1bff> <1b00>\n";
  9329. $stream .= "<1c00> <1cff> <1c00>\n";
  9330. $stream .= "<1d00> <1dff> <1d00>\n";
  9331. $stream .= "<1e00> <1eff> <1e00>\n";
  9332. $stream .= "<1f00> <1fff> <1f00>\n";
  9333. $stream .= "<2000> <20ff> <2000>\n";
  9334. $stream .= "<2100> <21ff> <2100>\n";
  9335. $stream .= "<2200> <22ff> <2200>\n";
  9336. $stream .= "<2300> <23ff> <2300>\n";
  9337. $stream .= "<2400> <24ff> <2400>\n";
  9338. $stream .= "<2500> <25ff> <2500>\n";
  9339. $stream .= "<2600> <26ff> <2600>\n";
  9340. $stream .= "<2700> <27ff> <2700>\n";
  9341. $stream .= "<2800> <28ff> <2800>\n";
  9342. $stream .= "<2900> <29ff> <2900>\n";
  9343. $stream .= "<2a00> <2aff> <2a00>\n";
  9344. $stream .= "<2b00> <2bff> <2b00>\n";
  9345. $stream .= "<2c00> <2cff> <2c00>\n";
  9346. $stream .= "<2d00> <2dff> <2d00>\n";
  9347. $stream .= "<2e00> <2eff> <2e00>\n";
  9348. $stream .= "<2f00> <2fff> <2f00>\n";
  9349. $stream .= "<3000> <30ff> <3000>\n";
  9350. $stream .= "<3100> <31ff> <3100>\n";
  9351. $stream .= "<3200> <32ff> <3200>\n";
  9352. $stream .= "<3300> <33ff> <3300>\n";
  9353. $stream .= "<3400> <34ff> <3400>\n";
  9354. $stream .= "<3500> <35ff> <3500>\n";
  9355. $stream .= "<3600> <36ff> <3600>\n";
  9356. $stream .= "<3700> <37ff> <3700>\n";
  9357. $stream .= "<3800> <38ff> <3800>\n";
  9358. $stream .= "<3900> <39ff> <3900>\n";
  9359. $stream .= "<3a00> <3aff> <3a00>\n";
  9360. $stream .= "<3b00> <3bff> <3b00>\n";
  9361. $stream .= "<3c00> <3cff> <3c00>\n";
  9362. $stream .= "<3d00> <3dff> <3d00>\n";
  9363. $stream .= "<3e00> <3eff> <3e00>\n";
  9364. $stream .= "<3f00> <3fff> <3f00>\n";
  9365. $stream .= "<4000> <40ff> <4000>\n";
  9366. $stream .= "<4100> <41ff> <4100>\n";
  9367. $stream .= "<4200> <42ff> <4200>\n";
  9368. $stream .= "<4300> <43ff> <4300>\n";
  9369. $stream .= "<4400> <44ff> <4400>\n";
  9370. $stream .= "<4500> <45ff> <4500>\n";
  9371. $stream .= "<4600> <46ff> <4600>\n";
  9372. $stream .= "<4700> <47ff> <4700>\n";
  9373. $stream .= "<4800> <48ff> <4800>\n";
  9374. $stream .= "<4900> <49ff> <4900>\n";
  9375. $stream .= "<4a00> <4aff> <4a00>\n";
  9376. $stream .= "<4b00> <4bff> <4b00>\n";
  9377. $stream .= "<4c00> <4cff> <4c00>\n";
  9378. $stream .= "<4d00> <4dff> <4d00>\n";
  9379. $stream .= "<4e00> <4eff> <4e00>\n";
  9380. $stream .= "<4f00> <4fff> <4f00>\n";
  9381. $stream .= "<5000> <50ff> <5000>\n";
  9382. $stream .= "<5100> <51ff> <5100>\n";
  9383. $stream .= "<5200> <52ff> <5200>\n";
  9384. $stream .= "<5300> <53ff> <5300>\n";
  9385. $stream .= "<5400> <54ff> <5400>\n";
  9386. $stream .= "<5500> <55ff> <5500>\n";
  9387. $stream .= "<5600> <56ff> <5600>\n";
  9388. $stream .= "<5700> <57ff> <5700>\n";
  9389. $stream .= "<5800> <58ff> <5800>\n";
  9390. $stream .= "<5900> <59ff> <5900>\n";
  9391. $stream .= "<5a00> <5aff> <5a00>\n";
  9392. $stream .= "<5b00> <5bff> <5b00>\n";
  9393. $stream .= "<5c00> <5cff> <5c00>\n";
  9394. $stream .= "<5d00> <5dff> <5d00>\n";
  9395. $stream .= "<5e00> <5eff> <5e00>\n";
  9396. $stream .= "<5f00> <5fff> <5f00>\n";
  9397. $stream .= "<6000> <60ff> <6000>\n";
  9398. $stream .= "<6100> <61ff> <6100>\n";
  9399. $stream .= "<6200> <62ff> <6200>\n";
  9400. $stream .= "<6300> <63ff> <6300>\n";
  9401. $stream .= "endbfrange\n";
  9402. $stream .= "100 beginbfrange\n";
  9403. $stream .= "<6400> <64ff> <6400>\n";
  9404. $stream .= "<6500> <65ff> <6500>\n";
  9405. $stream .= "<6600> <66ff> <6600>\n";
  9406. $stream .= "<6700> <67ff> <6700>\n";
  9407. $stream .= "<6800> <68ff> <6800>\n";
  9408. $stream .= "<6900> <69ff> <6900>\n";
  9409. $stream .= "<6a00> <6aff> <6a00>\n";
  9410. $stream .= "<6b00> <6bff> <6b00>\n";
  9411. $stream .= "<6c00> <6cff> <6c00>\n";
  9412. $stream .= "<6d00> <6dff> <6d00>\n";
  9413. $stream .= "<6e00> <6eff> <6e00>\n";
  9414. $stream .= "<6f00> <6fff> <6f00>\n";
  9415. $stream .= "<7000> <70ff> <7000>\n";
  9416. $stream .= "<7100> <71ff> <7100>\n";
  9417. $stream .= "<7200> <72ff> <7200>\n";
  9418. $stream .= "<7300> <73ff> <7300>\n";
  9419. $stream .= "<7400> <74ff> <7400>\n";
  9420. $stream .= "<7500> <75ff> <7500>\n";
  9421. $stream .= "<7600> <76ff> <7600>\n";
  9422. $stream .= "<7700> <77ff> <7700>\n";
  9423. $stream .= "<7800> <78ff> <7800>\n";
  9424. $stream .= "<7900> <79ff> <7900>\n";
  9425. $stream .= "<7a00> <7aff> <7a00>\n";
  9426. $stream .= "<7b00> <7bff> <7b00>\n";
  9427. $stream .= "<7c00> <7cff> <7c00>\n";
  9428. $stream .= "<7d00> <7dff> <7d00>\n";
  9429. $stream .= "<7e00> <7eff> <7e00>\n";
  9430. $stream .= "<7f00> <7fff> <7f00>\n";
  9431. $stream .= "<8000> <80ff> <8000>\n";
  9432. $stream .= "<8100> <81ff> <8100>\n";
  9433. $stream .= "<8200> <82ff> <8200>\n";
  9434. $stream .= "<8300> <83ff> <8300>\n";
  9435. $stream .= "<8400> <84ff> <8400>\n";
  9436. $stream .= "<8500> <85ff> <8500>\n";
  9437. $stream .= "<8600> <86ff> <8600>\n";
  9438. $stream .= "<8700> <87ff> <8700>\n";
  9439. $stream .= "<8800> <88ff> <8800>\n";
  9440. $stream .= "<8900> <89ff> <8900>\n";
  9441. $stream .= "<8a00> <8aff> <8a00>\n";
  9442. $stream .= "<8b00> <8bff> <8b00>\n";
  9443. $stream .= "<8c00> <8cff> <8c00>\n";
  9444. $stream .= "<8d00> <8dff> <8d00>\n";
  9445. $stream .= "<8e00> <8eff> <8e00>\n";
  9446. $stream .= "<8f00> <8fff> <8f00>\n";
  9447. $stream .= "<9000> <90ff> <9000>\n";
  9448. $stream .= "<9100> <91ff> <9100>\n";
  9449. $stream .= "<9200> <92ff> <9200>\n";
  9450. $stream .= "<9300> <93ff> <9300>\n";
  9451. $stream .= "<9400> <94ff> <9400>\n";
  9452. $stream .= "<9500> <95ff> <9500>\n";
  9453. $stream .= "<9600> <96ff> <9600>\n";
  9454. $stream .= "<9700> <97ff> <9700>\n";
  9455. $stream .= "<9800> <98ff> <9800>\n";
  9456. $stream .= "<9900> <99ff> <9900>\n";
  9457. $stream .= "<9a00> <9aff> <9a00>\n";
  9458. $stream .= "<9b00> <9bff> <9b00>\n";
  9459. $stream .= "<9c00> <9cff> <9c00>\n";
  9460. $stream .= "<9d00> <9dff> <9d00>\n";
  9461. $stream .= "<9e00> <9eff> <9e00>\n";
  9462. $stream .= "<9f00> <9fff> <9f00>\n";
  9463. $stream .= "<a000> <a0ff> <a000>\n";
  9464. $stream .= "<a100> <a1ff> <a100>\n";
  9465. $stream .= "<a200> <a2ff> <a200>\n";
  9466. $stream .= "<a300> <a3ff> <a300>\n";
  9467. $stream .= "<a400> <a4ff> <a400>\n";
  9468. $stream .= "<a500> <a5ff> <a500>\n";
  9469. $stream .= "<a600> <a6ff> <a600>\n";
  9470. $stream .= "<a700> <a7ff> <a700>\n";
  9471. $stream .= "<a800> <a8ff> <a800>\n";
  9472. $stream .= "<a900> <a9ff> <a900>\n";
  9473. $stream .= "<aa00> <aaff> <aa00>\n";
  9474. $stream .= "<ab00> <abff> <ab00>\n";
  9475. $stream .= "<ac00> <acff> <ac00>\n";
  9476. $stream .= "<ad00> <adff> <ad00>\n";
  9477. $stream .= "<ae00> <aeff> <ae00>\n";
  9478. $stream .= "<af00> <afff> <af00>\n";
  9479. $stream .= "<b000> <b0ff> <b000>\n";
  9480. $stream .= "<b100> <b1ff> <b100>\n";
  9481. $stream .= "<b200> <b2ff> <b200>\n";
  9482. $stream .= "<b300> <b3ff> <b300>\n";
  9483. $stream .= "<b400> <b4ff> <b400>\n";
  9484. $stream .= "<b500> <b5ff> <b500>\n";
  9485. $stream .= "<b600> <b6ff> <b600>\n";
  9486. $stream .= "<b700> <b7ff> <b700>\n";
  9487. $stream .= "<b800> <b8ff> <b800>\n";
  9488. $stream .= "<b900> <b9ff> <b900>\n";
  9489. $stream .= "<ba00> <baff> <ba00>\n";
  9490. $stream .= "<bb00> <bbff> <bb00>\n";
  9491. $stream .= "<bc00> <bcff> <bc00>\n";
  9492. $stream .= "<bd00> <bdff> <bd00>\n";
  9493. $stream .= "<be00> <beff> <be00>\n";
  9494. $stream .= "<bf00> <bfff> <bf00>\n";
  9495. $stream .= "<c000> <c0ff> <c000>\n";
  9496. $stream .= "<c100> <c1ff> <c100>\n";
  9497. $stream .= "<c200> <c2ff> <c200>\n";
  9498. $stream .= "<c300> <c3ff> <c300>\n";
  9499. $stream .= "<c400> <c4ff> <c400>\n";
  9500. $stream .= "<c500> <c5ff> <c500>\n";
  9501. $stream .= "<c600> <c6ff> <c600>\n";
  9502. $stream .= "<c700> <c7ff> <c700>\n";
  9503. $stream .= "endbfrange\n";
  9504. $stream .= "56 beginbfrange\n";
  9505. $stream .= "<c800> <c8ff> <c800>\n";
  9506. $stream .= "<c900> <c9ff> <c900>\n";
  9507. $stream .= "<ca00> <caff> <ca00>\n";
  9508. $stream .= "<cb00> <cbff> <cb00>\n";
  9509. $stream .= "<cc00> <ccff> <cc00>\n";
  9510. $stream .= "<cd00> <cdff> <cd00>\n";
  9511. $stream .= "<ce00> <ceff> <ce00>\n";
  9512. $stream .= "<cf00> <cfff> <cf00>\n";
  9513. $stream .= "<d000> <d0ff> <d000>\n";
  9514. $stream .= "<d100> <d1ff> <d100>\n";
  9515. $stream .= "<d200> <d2ff> <d200>\n";
  9516. $stream .= "<d300> <d3ff> <d300>\n";
  9517. $stream .= "<d400> <d4ff> <d400>\n";
  9518. $stream .= "<d500> <d5ff> <d500>\n";
  9519. $stream .= "<d600> <d6ff> <d600>\n";
  9520. $stream .= "<d700> <d7ff> <d700>\n";
  9521. $stream .= "<d800> <d8ff> <d800>\n";
  9522. $stream .= "<d900> <d9ff> <d900>\n";
  9523. $stream .= "<da00> <daff> <da00>\n";
  9524. $stream .= "<db00> <dbff> <db00>\n";
  9525. $stream .= "<dc00> <dcff> <dc00>\n";
  9526. $stream .= "<dd00> <ddff> <dd00>\n";
  9527. $stream .= "<de00> <deff> <de00>\n";
  9528. $stream .= "<df00> <dfff> <df00>\n";
  9529. $stream .= "<e000> <e0ff> <e000>\n";
  9530. $stream .= "<e100> <e1ff> <e100>\n";
  9531. $stream .= "<e200> <e2ff> <e200>\n";
  9532. $stream .= "<e300> <e3ff> <e300>\n";
  9533. $stream .= "<e400> <e4ff> <e400>\n";
  9534. $stream .= "<e500> <e5ff> <e500>\n";
  9535. $stream .= "<e600> <e6ff> <e600>\n";
  9536. $stream .= "<e700> <e7ff> <e700>\n";
  9537. $stream .= "<e800> <e8ff> <e800>\n";
  9538. $stream .= "<e900> <e9ff> <e900>\n";
  9539. $stream .= "<ea00> <eaff> <ea00>\n";
  9540. $stream .= "<eb00> <ebff> <eb00>\n";
  9541. $stream .= "<ec00> <ecff> <ec00>\n";
  9542. $stream .= "<ed00> <edff> <ed00>\n";
  9543. $stream .= "<ee00> <eeff> <ee00>\n";
  9544. $stream .= "<ef00> <efff> <ef00>\n";
  9545. $stream .= "<f000> <f0ff> <f000>\n";
  9546. $stream .= "<f100> <f1ff> <f100>\n";
  9547. $stream .= "<f200> <f2ff> <f200>\n";
  9548. $stream .= "<f300> <f3ff> <f300>\n";
  9549. $stream .= "<f400> <f4ff> <f400>\n";
  9550. $stream .= "<f500> <f5ff> <f500>\n";
  9551. $stream .= "<f600> <f6ff> <f600>\n";
  9552. $stream .= "<f700> <f7ff> <f700>\n";
  9553. $stream .= "<f800> <f8ff> <f800>\n";
  9554. $stream .= "<f900> <f9ff> <f900>\n";
  9555. $stream .= "<fa00> <faff> <fa00>\n";
  9556. $stream .= "<fb00> <fbff> <fb00>\n";
  9557. $stream .= "<fc00> <fcff> <fc00>\n";
  9558. $stream .= "<fd00> <fdff> <fd00>\n";
  9559. $stream .= "<fe00> <feff> <fe00>\n";
  9560. $stream .= "<ff00> <ffff> <ff00>\n";
  9561. $stream .= "endbfrange\n";
  9562. $stream .= "endcmap\n";
  9563. $stream .= "CMapName currentdict /CMap defineresource pop\n";
  9564. $stream .= "end\n";
  9565. $stream .= "end";
  9566. // ToUnicode Object
  9567. $this->_newobj();
  9568. $stream = ($this->compress) ? gzcompress($stream) : $stream;
  9569. $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
  9570. $stream = $this->_getrawstream($stream);
  9571. $this->_out('<<'.$filter.'/Length '.strlen($stream).'>> stream'."\n".$stream."\n".'endstream'."\n".'endobj');
  9572. // CIDFontType2
  9573. // A CIDFont whose glyph descriptions are based on TrueType font technology
  9574. $oid = $this->_newobj();
  9575. $out = '<< /Type /Font';
  9576. $out .= ' /Subtype /CIDFontType2';
  9577. $out .= ' /BaseFont /'.$fontname;
  9578. // A dictionary containing entries that define the character collection of the CIDFont.
  9579. $cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
  9580. $cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
  9581. $cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
  9582. $out .= ' /CIDSystemInfo << '.$cidinfo.' >>';
  9583. $out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
  9584. $out .= ' /DW '.$font['dw']; // default width
  9585. $out .= "\n".$this->_putfontwidths($font, 0);
  9586. if (isset($font['ctg']) AND (!$this->empty_string($font['ctg']))) {
  9587. $out .= "\n".'/CIDToGIDMap '.($this->n + 2).' 0 R';
  9588. }
  9589. $out .= ' >>';
  9590. $out .= "\n".'endobj';
  9591. $this->_out($out);
  9592. // Font descriptor
  9593. // A font descriptor describing the CIDFont default metrics other than its glyph widths
  9594. $this->_newobj();
  9595. $out = '<< /Type /FontDescriptor';
  9596. $out .= ' /FontName /'.$fontname;
  9597. foreach ($font['desc'] as $key => $value) {
  9598. if(is_float($value)) {
  9599. $value = sprintf('%.3F', $value);
  9600. }
  9601. $out .= ' /'.$key.' '.$value;
  9602. }
  9603. $fontdir = false;
  9604. if (!$this->empty_string($font['file'])) {
  9605. // A stream containing a TrueType font
  9606. $out .= ' /FontFile2 '.$this->FontFiles[$font['file']]['n'].' 0 R';
  9607. $fontdir = $this->FontFiles[$font['file']]['fontdir'];
  9608. }
  9609. $out .= ' >>';
  9610. $out .= "\n".'endobj';
  9611. $this->_out($out);
  9612. if (isset($font['ctg']) AND (!$this->empty_string($font['ctg']))) {
  9613. $this->_newobj();
  9614. // Embed CIDToGIDMap
  9615. // A specification of the mapping from CIDs to glyph indices
  9616. // search and get CTG font file to embedd
  9617. $ctgfile = strtolower($font['ctg']);
  9618. // search and get ctg font file to embedd
  9619. $fontfile = '';
  9620. // search files on various directories
  9621. if (($fontdir !== false) AND file_exists($fontdir.$ctgfile)) {
  9622. $fontfile = $fontdir.$ctgfile;
  9623. } elseif (file_exists($this->_getfontpath().$ctgfile)) {
  9624. $fontfile = $this->_getfontpath().$ctgfile;
  9625. } elseif (file_exists($ctgfile)) {
  9626. $fontfile = $ctgfile;
  9627. }
  9628. if ($this->empty_string($fontfile)) {
  9629. $this->Error('Font file not found: '.$ctgfile);
  9630. }
  9631. $stream = $this->_getrawstream(file_get_contents($fontfile));
  9632. $out = '<< /Length '.strlen($stream).'';
  9633. if (substr($fontfile, -2) == '.z') { // check file extension
  9634. // Decompresses data encoded using the public-domain
  9635. // zlib/deflate compression method, reproducing the
  9636. // original text or binary data
  9637. $out .= ' /Filter /FlateDecode';
  9638. }
  9639. $out .= ' >>';
  9640. $out .= ' stream'."\n".$stream."\n".'endstream';
  9641. $out .= "\n".'endobj';
  9642. $this->_out($out);
  9643. }
  9644. }
  9645. /**
  9646. * Output CID-0 fonts.
  9647. * A Type 0 CIDFont contains glyph descriptions based on the Adobe Type 1 font format
  9648. * @param array $font font data
  9649. * @access protected
  9650. * @author Andrew Whitehead, Nicola Asuni, Yukihiro Nakadaira
  9651. * @since 3.2.000 (2008-06-23)
  9652. */
  9653. protected function _putcidfont0($font) {
  9654. $cidoffset = 0;
  9655. if (!isset($font['cw'][1])) {
  9656. $cidoffset = 31;
  9657. }
  9658. if (isset($font['cidinfo']['uni2cid'])) {
  9659. // convert unicode to cid.
  9660. $uni2cid = $font['cidinfo']['uni2cid'];
  9661. $cw = array();
  9662. foreach ($font['cw'] as $uni => $width) {
  9663. if (isset($uni2cid[$uni])) {
  9664. $cw[($uni2cid[$uni] + $cidoffset)] = $width;
  9665. } elseif ($uni < 256) {
  9666. $cw[$uni] = $width;
  9667. } // else unknown character
  9668. }
  9669. $font = array_merge($font, array('cw' => $cw));
  9670. }
  9671. $name = $font['name'];
  9672. $enc = $font['enc'];
  9673. if ($enc) {
  9674. $longname = $name.'-'.$enc;
  9675. } else {
  9676. $longname = $name;
  9677. }
  9678. $out = $this->_getobj($this->font_obj_ids[$font['fontkey']])."\n";
  9679. $out .= '<</Type /Font';
  9680. $out .= ' /Subtype /Type0';
  9681. $out .= ' /BaseFont /'.$longname;
  9682. $out .= ' /Name /F'.$font['i'];
  9683. if ($enc) {
  9684. $out .= ' /Encoding /'.$enc;
  9685. }
  9686. $out .= ' /DescendantFonts ['.($this->n + 1).' 0 R]';
  9687. $out .= ' >>';
  9688. $out .= "\n".'endobj';
  9689. $this->_out($out);
  9690. $oid = $this->_newobj();
  9691. $out = '<</Type /Font';
  9692. $out .= ' /Subtype /CIDFontType0';
  9693. $out .= ' /BaseFont /'.$name;
  9694. $cidinfo = '/Registry '.$this->_datastring($font['cidinfo']['Registry'], $oid);
  9695. $cidinfo .= ' /Ordering '.$this->_datastring($font['cidinfo']['Ordering'], $oid);
  9696. $cidinfo .= ' /Supplement '.$font['cidinfo']['Supplement'];
  9697. $out .= ' /CIDSystemInfo <<'.$cidinfo.'>>';
  9698. $out .= ' /FontDescriptor '.($this->n + 1).' 0 R';
  9699. $out .= ' /DW '.$font['dw'];
  9700. $out .= "\n".$this->_putfontwidths($font, $cidoffset);
  9701. $out .= ' >>';
  9702. $out .= "\n".'endobj';
  9703. $this->_out($out);
  9704. $this->_newobj();
  9705. $s = '<</Type /FontDescriptor /FontName /'.$name;
  9706. foreach ($font['desc'] as $k => $v) {
  9707. if ($k != 'Style') {
  9708. if(is_float($v)) {
  9709. $v = sprintf('%.3F', $v);
  9710. }
  9711. $s .= ' /'.$k.' '.$v.'';
  9712. }
  9713. }
  9714. $s .= '>>';
  9715. $s .= "\n".'endobj';
  9716. $this->_out($s);
  9717. }
  9718. /**
  9719. * Output images.
  9720. * @access protected
  9721. */
  9722. protected function _putimages() {
  9723. $filter = ($this->compress) ? '/Filter /FlateDecode ' : '';
  9724. foreach ($this->imagekeys as $file) {
  9725. $info = $this->getImageBuffer($file);
  9726. $oid = $this->_newobj();
  9727. $this->xobjects['I'.$info['i']] = array('n' => $oid);
  9728. $this->setImageSubBuffer($file, 'n', $this->n);
  9729. $out = '<</Type /XObject';
  9730. $out .= ' /Subtype /Image';
  9731. $out .= ' /Width '.$info['w'];
  9732. $out .= ' /Height '.$info['h'];
  9733. if (array_key_exists('masked', $info)) {
  9734. $out .= ' /SMask '.($this->n - 1).' 0 R';
  9735. }
  9736. if ($info['cs'] == 'Indexed') {
  9737. $out .= ' /ColorSpace [/Indexed /DeviceRGB '.((strlen($info['pal']) / 3) - 1).' '.($this->n + 1).' 0 R]';
  9738. } else {
  9739. $out .= ' /ColorSpace /'.$info['cs'];
  9740. if ($info['cs'] == 'DeviceCMYK') {
  9741. $out .= ' /Decode [1 0 1 0 1 0 1 0]';
  9742. }
  9743. }
  9744. $out .= ' /BitsPerComponent '.$info['bpc'];
  9745. if (isset($info['f'])) {
  9746. $out .= ' /Filter /'.$info['f'];
  9747. }
  9748. if (isset($info['parms'])) {
  9749. $out .= ' '.$info['parms'];
  9750. }
  9751. if (isset($info['trns']) AND is_array($info['trns'])) {
  9752. $trns='';
  9753. $count_info = count($info['trns']);
  9754. for ($i=0; $i < $count_info; ++$i) {
  9755. $trns .= $info['trns'][$i].' '.$info['trns'][$i].' ';
  9756. }
  9757. $out .= ' /Mask ['.$trns.']';
  9758. }
  9759. $stream = $this->_getrawstream($info['data']);
  9760. $out .= ' /Length '.strlen($stream).' >>';
  9761. $out .= ' stream'."\n".$stream."\n".'endstream';
  9762. $out .= "\n".'endobj';
  9763. $this->_out($out);
  9764. //Palette
  9765. if ($info['cs'] == 'Indexed') {
  9766. $this->_newobj();
  9767. $pal = ($this->compress) ? gzcompress($info['pal']) : $info['pal'];
  9768. $pal = $this->_getrawstream($pal);
  9769. $this->_out('<<'.$filter.'/Length '.strlen($pal).'>> stream'."\n".$pal."\n".'endstream'."\n".'endobj');
  9770. }
  9771. }
  9772. }
  9773. /**
  9774. * Output Form XObjects Templates.
  9775. * @author Nicola Asuni
  9776. * @since 5.8.017 (2010-08-24)
  9777. * @access protected
  9778. * @see startTemplate(), endTemplate(), printTemplate()
  9779. */
  9780. protected function _putxobjects() {
  9781. foreach ($this->xobjects as $key => $data) {
  9782. if (isset($data['outdata'])) {
  9783. $stream = trim($data['outdata']);
  9784. $out = $this->_getobj($data['n'])."\n";
  9785. $out .= '<<';
  9786. $out .= ' /Type /XObject';
  9787. $out .= ' /Subtype /Form';
  9788. $out .= ' /FormType 1';
  9789. if ($this->compress) {
  9790. $stream = gzcompress($stream);
  9791. $out .= ' /Filter /FlateDecode';
  9792. }
  9793. $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));
  9794. $out .= ' /Matrix [1 0 0 1 0 0]';
  9795. $out .= ' /Resources <<';
  9796. $out .= ' /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
  9797. // fonts
  9798. if (!empty($data['fonts'])) {
  9799. $out .= ' /Font <<';
  9800. foreach ($data['fonts'] as $fontkey => $fontid) {
  9801. $out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
  9802. }
  9803. $out .= ' >>';
  9804. }
  9805. // images or nested xobjects
  9806. if (!empty($data['images']) OR !empty($data['xobjects'])) {
  9807. $out .= ' /XObject <<';
  9808. foreach ($data['images'] as $imgid) {
  9809. $out .= ' /I'.$imgid.' '.$this->xobjects['I'.$imgid]['n'].' 0 R';
  9810. }
  9811. foreach ($data['xobjects'] as $sub_id => $sub_objid) {
  9812. $out .= ' /'.$sub_id.' '.$sub_objid['n'].' 0 R';
  9813. }
  9814. $out .= ' >>';
  9815. }
  9816. $out .= ' >>';
  9817. $stream = $this->_getrawstream($stream);
  9818. $out .= ' /Length '.strlen($stream);
  9819. $out .= ' >>';
  9820. $out .= ' stream'."\n".$stream."\n".'endstream';
  9821. $out .= "\n".'endobj';
  9822. $this->_out($out);
  9823. }
  9824. }
  9825. }
  9826. /**
  9827. * Output Spot Colors Resources.
  9828. * @access protected
  9829. * @since 4.0.024 (2008-09-12)
  9830. */
  9831. protected function _putspotcolors() {
  9832. foreach ($this->spot_colors as $name => $color) {
  9833. $this->_newobj();
  9834. $this->spot_colors[$name]['n'] = $this->n;
  9835. $out = '[/Separation /'.str_replace(' ', '#20', $name);
  9836. $out .= ' /DeviceCMYK <<';
  9837. $out .= ' /Range [0 1 0 1 0 1 0 1] /C0 [0 0 0 0]';
  9838. $out .= ' '.sprintf('/C1 [%.4F %.4F %.4F %.4F] ', $color['c']/100, $color['m']/100, $color['y']/100, $color['k']/100);
  9839. $out .= ' /FunctionType 2 /Domain [0 1] /N 1>>]';
  9840. $out .= "\n".'endobj';
  9841. $this->_out($out);
  9842. }
  9843. }
  9844. /**
  9845. * Return XObjects Dictionary.
  9846. * @return string XObjects dictionary
  9847. * @access protected
  9848. * @since 5.8.014 (2010-08-23)
  9849. */
  9850. protected function _getxobjectdict() {
  9851. $out = '';
  9852. foreach ($this->xobjects as $id => $objid) {
  9853. $out .= ' /'.$id.' '.$objid['n'].' 0 R';
  9854. }
  9855. return $out;
  9856. }
  9857. /**
  9858. * Output Resources Dictionary.
  9859. * @access protected
  9860. */
  9861. protected function _putresourcedict() {
  9862. $out = $this->_getobj(2)."\n";
  9863. $out .= '<< /ProcSet [/PDF /Text /ImageB /ImageC /ImageI]';
  9864. $out .= ' /Font <<';
  9865. foreach ($this->fontkeys as $fontkey) {
  9866. $font = $this->getFontBuffer($fontkey);
  9867. $out .= ' /F'.$font['i'].' '.$font['n'].' 0 R';
  9868. }
  9869. $out .= ' >>';
  9870. $out .= ' /XObject <<';
  9871. $out .= $this->_getxobjectdict();
  9872. $out .= ' >>';
  9873. // visibility
  9874. $out .= ' /Properties <</OC1 '.$this->n_ocg_print.' 0 R /OC2 '.$this->n_ocg_view.' 0 R>>';
  9875. // transparency
  9876. $out .= ' /ExtGState <<';
  9877. foreach ($this->extgstates as $k => $extgstate) {
  9878. if (isset($extgstate['name'])) {
  9879. $out .= ' /'.$extgstate['name'];
  9880. } else {
  9881. $out .= ' /GS'.$k;
  9882. }
  9883. $out .= ' '.$extgstate['n'].' 0 R';
  9884. }
  9885. $out .= ' >>';
  9886. // gradient patterns
  9887. if (isset($this->gradients) AND (count($this->gradients) > 0)) {
  9888. $out .= ' /Pattern <<';
  9889. foreach ($this->gradients as $id => $grad) {
  9890. $out .= ' /p'.$id.' '.$grad['pattern'].' 0 R';
  9891. }
  9892. $out .= ' >>';
  9893. }
  9894. // gradient shadings
  9895. if (isset($this->gradients) AND (count($this->gradients) > 0)) {
  9896. $out .= ' /Shading <<';
  9897. foreach ($this->gradients as $id => $grad) {
  9898. $out .= ' /Sh'.$id.' '.$grad['id'].' 0 R';
  9899. }
  9900. $out .= ' >>';
  9901. }
  9902. // spot colors
  9903. if (isset($this->spot_colors) AND (count($this->spot_colors) > 0)) {
  9904. $out .= ' /ColorSpace <<';
  9905. foreach ($this->spot_colors as $color) {
  9906. $out .= ' /CS'.$color['i'].' '.$color['n'].' 0 R';
  9907. }
  9908. $out .= ' >>';
  9909. }
  9910. $out .= ' >>';
  9911. $out .= "\n".'endobj';
  9912. $this->_out($out);
  9913. }
  9914. /**
  9915. * Output Resources.
  9916. * @access protected
  9917. */
  9918. protected function _putresources() {
  9919. $this->_putextgstates();
  9920. $this->_putocg();
  9921. $this->_putfonts();
  9922. $this->_putimages();
  9923. $this->_putxobjects();
  9924. $this->_putspotcolors();
  9925. $this->_putshaders();
  9926. $this->_putresourcedict();
  9927. $this->_putbookmarks();
  9928. $this->_putEmbeddedFiles();
  9929. $this->_putannotsobjs();
  9930. $this->_putjavascript();
  9931. $this->_putencryption();
  9932. }
  9933. /**
  9934. * Adds some Metadata information (Document Information Dictionary)
  9935. * (see Chapter 14.3.3 Document Information Dictionary of PDF32000_2008.pdf Reference)
  9936. * @return int object id
  9937. * @access protected
  9938. */
  9939. protected function _putinfo() {
  9940. $oid = $this->_newobj();
  9941. $out = '<<';
  9942. if (!$this->empty_string($this->title)) {
  9943. // The document's title.
  9944. $out .= ' /Title '.$this->_textstring($this->title, $oid);
  9945. }
  9946. if (!$this->empty_string($this->author)) {
  9947. // The name of the person who created the document.
  9948. $out .= ' /Author '.$this->_textstring($this->author, $oid);
  9949. }
  9950. if (!$this->empty_string($this->subject)) {
  9951. // The subject of the document.
  9952. $out .= ' /Subject '.$this->_textstring($this->subject, $oid);
  9953. }
  9954. if (!$this->empty_string($this->keywords)) {
  9955. // Keywords associated with the document.
  9956. $out .= ' /Keywords '.$this->_textstring($this->keywords.' TCP'.'DF', $oid);
  9957. }
  9958. if (!$this->empty_string($this->creator)) {
  9959. // 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.
  9960. $out .= ' /Creator '.$this->_textstring($this->creator, $oid);
  9961. }
  9962. if (defined('PDF_PRODUCER')) {
  9963. // If the document was converted to PDF from another format, the name of the conforming product that converted it to PDF.
  9964. $out .= ' /Producer '.$this->_textstring(PDF_PRODUCER.' (TCP'.'DF)', $oid);
  9965. } else {
  9966. // default producer
  9967. $out .= ' /Producer '.$this->_textstring('TCP'.'DF', $oid);
  9968. }
  9969. // The date and time the document was created, in human-readable form
  9970. $out .= ' /CreationDate '.$this->_datestring();
  9971. // The date and time the document was most recently modified, in human-readable form
  9972. $out .= ' /ModDate '.$this->_datestring();
  9973. // A name object indicating whether the document has been modified to include trapping information
  9974. $out .= ' /Trapped /False';
  9975. $out .= ' >>';
  9976. $out .= "\n".'endobj';
  9977. $this->_out($out);
  9978. return $oid;
  9979. }
  9980. /**
  9981. * Output Catalog.
  9982. * @return int object id
  9983. * @access protected
  9984. */
  9985. protected function _putcatalog() {
  9986. $oid = $this->_newobj();
  9987. $out = '<< /Type /Catalog';
  9988. $out .= ' /Pages 1 0 R';
  9989. if ($this->ZoomMode == 'fullpage') {
  9990. $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /Fit]';
  9991. } elseif ($this->ZoomMode == 'fullwidth') {
  9992. $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /FitH null]';
  9993. } elseif ($this->ZoomMode == 'real') {
  9994. $out .= ' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null 1]';
  9995. } elseif (!is_string($this->ZoomMode)) {
  9996. $out .= sprintf(' /OpenAction ['.$this->page_obj_id[1].' 0 R /XYZ null null %.2F]',($this->ZoomMode / 100));
  9997. }
  9998. if (isset($this->LayoutMode) AND (!$this->empty_string($this->LayoutMode))) {
  9999. $out .= ' /PageLayout /'.$this->LayoutMode;
  10000. }
  10001. if (isset($this->PageMode) AND (!$this->empty_string($this->PageMode))) {
  10002. $out .= ' /PageMode /'.$this->PageMode;
  10003. }
  10004. if (isset($this->l['a_meta_language'])) {
  10005. $out .= ' /Lang '.$this->_textstring($this->l['a_meta_language'], $oid);
  10006. }
  10007. $out .= ' /Names <<';
  10008. if ((!empty($this->javascript)) OR (!empty($this->js_objects))) {
  10009. $out .= ' /JavaScript '.($this->n_js).' 0 R';
  10010. }
  10011. $out .= ' >>';
  10012. if (count($this->outlines) > 0) {
  10013. $out .= ' /Outlines '.$this->OutlineRoot.' 0 R';
  10014. $out .= ' /PageMode /UseOutlines';
  10015. }
  10016. $out .= ' '.$this->_putviewerpreferences();
  10017. $p = $this->n_ocg_print.' 0 R';
  10018. $v = $this->n_ocg_view.' 0 R';
  10019. $as = '<< /Event /Print /OCGs ['.$p.' '.$v.'] /Category [/Print] >> << /Event /View /OCGs ['.$p.' '.$v.'] /Category [/View] >>';
  10020. $out .= ' /OCProperties << /OCGs ['.$p.' '.$v.'] /D << /ON ['.$p.'] /OFF ['.$v.'] /AS ['.$as.'] >> >>';
  10021. // AcroForm
  10022. if (!empty($this->form_obj_id) OR ($this->sign AND isset($this->signature_data['cert_type']))) {
  10023. $out .= ' /AcroForm <<';
  10024. $objrefs = '';
  10025. if ($this->sign AND isset($this->signature_data['cert_type'])) {
  10026. $objrefs .= $this->sig_obj_id.' 0 R';
  10027. }
  10028. if (!empty($this->form_obj_id)) {
  10029. foreach($this->form_obj_id as $objid) {
  10030. $objrefs .= ' '.$objid.' 0 R';
  10031. }
  10032. }
  10033. $out .= ' /Fields ['.$objrefs.']';
  10034. if (!empty($this->form_obj_id) AND !$this->sign) {
  10035. // It's better to turn off this value and set the appearance stream for each annotation (/AP) to avoid conflicts with signature fields.
  10036. $out .= ' /NeedAppearances true';
  10037. }
  10038. if ($this->sign AND isset($this->signature_data['cert_type'])) {
  10039. if ($this->signature_data['cert_type'] > 0) {
  10040. $out .= ' /SigFlags 3';
  10041. } else {
  10042. $out .= ' /SigFlags 1';
  10043. }
  10044. }
  10045. //$out .= ' /CO ';
  10046. if (isset($this->annotation_fonts) AND !empty($this->annotation_fonts)) {
  10047. $out .= ' /DR <<';
  10048. $out .= ' /Font <<';
  10049. foreach ($this->annotation_fonts as $fontkey => $fontid) {
  10050. $out .= ' /F'.$fontid.' '.$this->font_obj_ids[$fontkey].' 0 R';
  10051. }
  10052. $out .= ' >> >>';
  10053. }
  10054. $font = $this->getFontBuffer('helvetica');
  10055. $out .= ' /DA (/F'.$font['i'].' 0 Tf 0 g)';
  10056. $out .= ' /Q '.(($this->rtl)?'2':'0');
  10057. //$out .= ' /XFA ';
  10058. $out .= ' >>';
  10059. // signatures
  10060. if ($this->sign AND isset($this->signature_data['cert_type'])) {
  10061. if ($this->signature_data['cert_type'] > 0) {
  10062. $out .= ' /Perms << /DocMDP '.($this->sig_obj_id + 1).' 0 R >>';
  10063. } else {
  10064. $out .= ' /Perms << /UR3 '.($this->sig_obj_id + 1).' 0 R >>';
  10065. }
  10066. }
  10067. }
  10068. $out .= ' >>';
  10069. $out .= "\n".'endobj';
  10070. $this->_out($out);
  10071. return $oid;
  10072. }
  10073. /**
  10074. * Output viewer preferences.
  10075. * @return string for viewer preferences
  10076. * @author Nicola asuni
  10077. * @since 3.1.000 (2008-06-09)
  10078. * @access protected
  10079. */
  10080. protected function _putviewerpreferences() {
  10081. $out = '/ViewerPreferences <<';
  10082. if ($this->rtl) {
  10083. $out .= ' /Direction /R2L';
  10084. } else {
  10085. $out .= ' /Direction /L2R';
  10086. }
  10087. if (isset($this->viewer_preferences['HideToolbar']) AND ($this->viewer_preferences['HideToolbar'])) {
  10088. $out .= ' /HideToolbar true';
  10089. }
  10090. if (isset($this->viewer_preferences['HideMenubar']) AND ($this->viewer_preferences['HideMenubar'])) {
  10091. $out .= ' /HideMenubar true';
  10092. }
  10093. if (isset($this->viewer_preferences['HideWindowUI']) AND ($this->viewer_preferences['HideWindowUI'])) {
  10094. $out .= ' /HideWindowUI true';
  10095. }
  10096. if (isset($this->viewer_preferences['FitWindow']) AND ($this->viewer_preferences['FitWindow'])) {
  10097. $out .= ' /FitWindow true';
  10098. }
  10099. if (isset($this->viewer_preferences['CenterWindow']) AND ($this->viewer_preferences['CenterWindow'])) {
  10100. $out .= ' /CenterWindow true';
  10101. }
  10102. if (isset($this->viewer_preferences['DisplayDocTitle']) AND ($this->viewer_preferences['DisplayDocTitle'])) {
  10103. $out .= ' /DisplayDocTitle true';
  10104. }
  10105. if (isset($this->viewer_preferences['NonFullScreenPageMode'])) {
  10106. $out .= ' /NonFullScreenPageMode /'.$this->viewer_preferences['NonFullScreenPageMode'];
  10107. }
  10108. if (isset($this->viewer_preferences['ViewArea'])) {
  10109. $out .= ' /ViewArea /'.$this->viewer_preferences['ViewArea'];
  10110. }
  10111. if (isset($this->viewer_preferences['ViewClip'])) {
  10112. $out .= ' /ViewClip /'.$this->viewer_preferences['ViewClip'];
  10113. }
  10114. if (isset($this->viewer_preferences['PrintArea'])) {
  10115. $out .= ' /PrintArea /'.$this->viewer_preferences['PrintArea'];
  10116. }
  10117. if (isset($this->viewer_preferences['PrintClip'])) {
  10118. $out .= ' /PrintClip /'.$this->viewer_preferences['PrintClip'];
  10119. }
  10120. if (isset($this->viewer_preferences['PrintScaling'])) {
  10121. $out .= ' /PrintScaling /'.$this->viewer_preferences['PrintScaling'];
  10122. }
  10123. if (isset($this->viewer_preferences['Duplex']) AND (!$this->empty_string($this->viewer_preferences['Duplex']))) {
  10124. $out .= ' /Duplex /'.$this->viewer_preferences['Duplex'];
  10125. }
  10126. if (isset($this->viewer_preferences['PickTrayByPDFSize'])) {
  10127. if ($this->viewer_preferences['PickTrayByPDFSize']) {
  10128. $out .= ' /PickTrayByPDFSize true';
  10129. } else {
  10130. $out .= ' /PickTrayByPDFSize false';
  10131. }
  10132. }
  10133. if (isset($this->viewer_preferences['PrintPageRange'])) {
  10134. $PrintPageRangeNum = '';
  10135. foreach ($this->viewer_preferences['PrintPageRange'] as $k => $v) {
  10136. $PrintPageRangeNum .= ' '.($v - 1).'';
  10137. }
  10138. $out .= ' /PrintPageRange ['.substr($PrintPageRangeNum,1).']';
  10139. }
  10140. if (isset($this->viewer_preferences['NumCopies'])) {
  10141. $out .= ' /NumCopies '.intval($this->viewer_preferences['NumCopies']);
  10142. }
  10143. $out .= ' >>';
  10144. return $out;
  10145. }
  10146. /**
  10147. * Output PDF header.
  10148. * @access protected
  10149. */
  10150. protected function _putheader() {
  10151. $this->_out('%PDF-'.$this->PDFVersion);
  10152. }
  10153. /**
  10154. * Output end of document (EOF).
  10155. * @access protected
  10156. */
  10157. protected function _enddoc() {
  10158. $this->state = 1;
  10159. $this->_putheader();
  10160. $this->_putpages();
  10161. $this->_putresources();
  10162. // Signature
  10163. if ($this->sign AND isset($this->signature_data['cert_type'])) {
  10164. // widget annotation for signature
  10165. $out = $this->_getobj($this->sig_obj_id)."\n";
  10166. $out .= '<< /Type /Annot';
  10167. $out .= ' /Subtype /Widget';
  10168. $out .= ' /Rect ['.$this->signature_appearance['rect'].']';
  10169. $out .= ' /P '.$this->page_obj_id[($this->signature_appearance['page'])].' 0 R'; // link to signature appearance page
  10170. $out .= ' /F 4';
  10171. $out .= ' /FT /Sig';
  10172. $out .= ' /T '.$this->_textstring('Signature', $this->sig_obj_id);
  10173. $out .= ' /Ff 0';
  10174. $out .= ' /V '.($this->sig_obj_id + 1).' 0 R';
  10175. $out .= ' >>';
  10176. $out .= "\n".'endobj';
  10177. $this->_out($out);
  10178. // signature
  10179. $this->_putsignature();
  10180. }
  10181. // Info
  10182. $objid_info = $this->_putinfo();
  10183. // Catalog
  10184. $objid_catalog = $this->_putcatalog();
  10185. // Cross-ref
  10186. $o = $this->bufferlen;
  10187. // XREF section
  10188. $this->_out('xref');
  10189. $this->_out('0 '.($this->n + 1));
  10190. $this->_out('0000000000 65535 f ');
  10191. for ($i=1; $i <= $this->n; ++$i) {
  10192. $this->_out(sprintf('%010d 00000 n ', $this->offsets[$i]));
  10193. }
  10194. // TRAILER
  10195. $out = 'trailer <<';
  10196. $out .= ' /Size '.($this->n + 1);
  10197. $out .= ' /Root '.$objid_catalog.' 0 R';
  10198. $out .= ' /Info '.$objid_info.' 0 R';
  10199. if ($this->encrypted) {
  10200. $out .= ' /Encrypt '.$this->encryptdata['objid'].' 0 R';
  10201. }
  10202. $out .= ' /ID [ <'.$this->file_id.'> <'.$this->file_id.'> ]';
  10203. $out .= ' >>';
  10204. $this->_out($out);
  10205. $this->_out('startxref');
  10206. $this->_out($o);
  10207. $this->_out('%%EOF');
  10208. $this->state = 3; // end-of-doc
  10209. if ($this->diskcache) {
  10210. // remove temporary files used for images
  10211. foreach ($this->imagekeys as $key) {
  10212. // remove temporary files
  10213. unlink($this->images[$key]);
  10214. }
  10215. foreach ($this->fontkeys as $key) {
  10216. // remove temporary files
  10217. unlink($this->fonts[$key]);
  10218. }
  10219. }
  10220. }
  10221. /**
  10222. * Initialize a new page.
  10223. * @param string $orientation page orientation. Possible values are (case insensitive):<ul><li>P or PORTRAIT (default)</li><li>L or LANDSCAPE</li></ul>
  10224. * @param mixed $format The format used for pages. It can be either: one of the string values specified at getPageSizeFromFormat() or an array of parameters specified at setPageFormat().
  10225. * @access protected
  10226. * @see getPageSizeFromFormat(), setPageFormat()
  10227. */
  10228. protected function _beginpage($orientation='', $format='') {
  10229. ++$this->page;
  10230. $this->setPageBuffer($this->page, '');
  10231. // initialize array for graphics tranformation positions inside a page buffer
  10232. $this->transfmrk[$this->page] = array();
  10233. $this->state = 2;
  10234. if ($this->empty_string($orientation)) {
  10235. if (isset($this->CurOrientation)) {
  10236. $orientation = $this->CurOrientation;
  10237. } elseif ($this->fwPt > $this->fhPt) {
  10238. // landscape
  10239. $orientation = 'L';
  10240. } else {
  10241. // portrait
  10242. $orientation = 'P';
  10243. }
  10244. }
  10245. if ($this->empty_string($format)) {
  10246. $this->pagedim[$this->page] = $this->pagedim[($this->page - 1)];
  10247. $this->setPageOrientation($orientation);
  10248. } else {
  10249. $this->setPageFormat($format, $orientation);
  10250. }
  10251. if ($this->rtl) {
  10252. $this->x = $this->w - $this->rMargin;
  10253. } else {
  10254. $this->x = $this->lMargin;
  10255. }
  10256. $this->y = $this->tMargin;
  10257. if (isset($this->newpagegroup[$this->page])) {
  10258. // start a new group
  10259. $n = sizeof($this->pagegroups) + 1;
  10260. $alias = '{nb'.$n.'}';
  10261. $this->pagegroups[$alias] = 1;
  10262. $this->currpagegroup = $alias;
  10263. } elseif ($this->currpagegroup) {
  10264. ++$this->pagegroups[$this->currpagegroup];
  10265. }
  10266. }
  10267. /**
  10268. * Mark end of page.
  10269. * @access protected
  10270. */
  10271. protected function _endpage() {
  10272. $this->setVisibility('all');
  10273. $this->state = 1;
  10274. }
  10275. /**
  10276. * Begin a new object and return the object number.
  10277. * @return int object number
  10278. * @access protected
  10279. */
  10280. protected function _newobj() {
  10281. $this->_out($this->_getobj());
  10282. return $this->n;
  10283. }
  10284. /**
  10285. * Return the starting object string for the selected object ID.
  10286. * @param int $objid Object ID (leave empty to get a new ID).
  10287. * @return string the starting object string
  10288. * @access protected
  10289. * @since 5.8.009 (2010-08-20)
  10290. */
  10291. protected function _getobj($objid='') {
  10292. if ($objid === '') {
  10293. ++$this->n;
  10294. $objid = $this->n;
  10295. }
  10296. $this->offsets[$objid] = $this->bufferlen;
  10297. return $objid.' 0 obj';
  10298. }
  10299. /**
  10300. * Underline text.
  10301. * @param int $x X coordinate
  10302. * @param int $y Y coordinate
  10303. * @param string $txt text to underline
  10304. * @access protected
  10305. */
  10306. protected function _dounderline($x, $y, $txt) {
  10307. $w = $this->GetStringWidth($txt);
  10308. return $this->_dounderlinew($x, $y, $w);
  10309. }
  10310. /**
  10311. * Underline for rectangular text area.
  10312. * @param int $x X coordinate
  10313. * @param int $y Y coordinate
  10314. * @param int $w width to underline
  10315. * @access protected
  10316. * @since 4.8.008 (2009-09-29)
  10317. */
  10318. protected function _dounderlinew($x, $y, $w) {
  10319. $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
  10320. return sprintf('%.2F %.2F %.2F %.2F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew), $w * $this->k, $linew);
  10321. }
  10322. /**
  10323. * Line through text.
  10324. * @param int $x X coordinate
  10325. * @param int $y Y coordinate
  10326. * @param string $txt text to linethrough
  10327. * @access protected
  10328. */
  10329. protected function _dolinethrough($x, $y, $txt) {
  10330. $w = $this->GetStringWidth($txt);
  10331. return $this->_dolinethroughw($x, $y, $w);
  10332. }
  10333. /**
  10334. * Line through for rectangular text area.
  10335. * @param int $x X coordinate
  10336. * @param int $y Y coordinate
  10337. * @param string $txt text to linethrough
  10338. * @access protected
  10339. * @since 4.9.008 (2009-09-29)
  10340. */
  10341. protected function _dolinethroughw($x, $y, $w) {
  10342. $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
  10343. return sprintf('%.2F %.2F %.2F %.2F re f', $x * $this->k, ((($this->h - $y) * $this->k) + $linew + ($this->FontSizePt / 3)), $w * $this->k, $linew);
  10344. }
  10345. /**
  10346. * Overline text.
  10347. * @param int $x X coordinate
  10348. * @param int $y Y coordinate
  10349. * @param string $txt text to overline
  10350. * @access protected
  10351. * @since 4.9.015 (2010-04-19)
  10352. */
  10353. protected function _dooverline($x, $y, $txt) {
  10354. $w = $this->GetStringWidth($txt);
  10355. return $this->_dooverlinew($x, $y, $w);
  10356. }
  10357. /**
  10358. * Overline for rectangular text area.
  10359. * @param int $x X coordinate
  10360. * @param int $y Y coordinate
  10361. * @param int $w width to overline
  10362. * @access protected
  10363. * @since 4.9.015 (2010-04-19)
  10364. */
  10365. protected function _dooverlinew($x, $y, $w) {
  10366. $linew = - $this->CurrentFont['ut'] / 1000 * $this->FontSizePt;
  10367. return sprintf('%.2F %.2F %.2F %.2F re f', $x * $this->k, (($this->h - $y + $this->FontAscent) * $this->k) - $linew, $w * $this->k, $linew);
  10368. }
  10369. /**
  10370. * Read a 4-byte (32 bit) integer from file.
  10371. * @param string $f file name.
  10372. * @return 4-byte integer
  10373. * @access protected
  10374. */
  10375. protected function _freadint($f) {
  10376. $a = unpack('Ni', fread($f, 4));
  10377. return $a['i'];
  10378. }
  10379. /**
  10380. * Add "\" before "\", "(" and ")"
  10381. * @param string $s string to escape.
  10382. * @return string escaped string.
  10383. * @access protected
  10384. */
  10385. protected function _escape($s) {
  10386. // the chr(13) substitution fixes the Bugs item #1421290.
  10387. return strtr($s, array(')' => '\\)', '(' => '\\(', '\\' => '\\\\', chr(13) => '\r'));
  10388. }
  10389. /**
  10390. * Format a data string for meta information
  10391. * @param string $s data string to escape.
  10392. * @param int $n object ID
  10393. * @return string escaped string.
  10394. * @access protected
  10395. */
  10396. protected function _datastring($s, $n=0) {
  10397. if ($n == 0) {
  10398. $n = $this->n;
  10399. }
  10400. $s = $this->_encrypt_data($n, $s);
  10401. return '('. $this->_escape($s).')';
  10402. }
  10403. /**
  10404. * Returns a formatted date for meta information
  10405. * @param int $n object ID
  10406. * @return string escaped date string.
  10407. * @access protected
  10408. * @since 4.6.028 (2009-08-25)
  10409. */
  10410. protected function _datestring($n=0) {
  10411. $current_time = substr_replace(date('YmdHisO'), '\'', (0 - 2), 0).'\'';
  10412. return $this->_datastring('D:'.$current_time, $n);
  10413. }
  10414. /**
  10415. * Format a text string for meta information
  10416. * @param string $s string to escape.
  10417. * @param int $n object ID
  10418. * @return string escaped string.
  10419. * @access protected
  10420. */
  10421. protected function _textstring($s, $n=0) {
  10422. if ($this->isunicode) {
  10423. //Convert string to UTF-16BE
  10424. $s = $this->UTF8ToUTF16BE($s, true);
  10425. }
  10426. return $this->_datastring($s, $n);
  10427. }
  10428. /**
  10429. * THIS METHOD IS DEPRECATED
  10430. * Format a text string
  10431. * @param string $s string to escape.
  10432. * @return string escaped string.
  10433. * @access protected
  10434. * @deprecated
  10435. */
  10436. protected function _escapetext($s) {
  10437. if ($this->isunicode) {
  10438. if (($this->CurrentFont['type'] == 'core') OR ($this->CurrentFont['type'] == 'TrueType') OR ($this->CurrentFont['type'] == 'Type1')) {
  10439. $s = $this->UTF8ToLatin1($s);
  10440. } else {
  10441. //Convert string to UTF-16BE and reverse RTL language
  10442. $s = $this->utf8StrRev($s, false, $this->tmprtl);
  10443. }
  10444. }
  10445. return $this->_escape($s);
  10446. }
  10447. /**
  10448. * get raw output stream.
  10449. * @param string $s string to output.
  10450. * @param int $n object reference for encryption mode
  10451. * @access protected
  10452. * @author Nicola Asuni
  10453. * @since 5.5.000 (2010-06-22)
  10454. */
  10455. protected function _getrawstream($s, $n=0) {
  10456. if ($n <= 0) {
  10457. // default to current object
  10458. $n = $this->n;
  10459. }
  10460. return $this->_encrypt_data($n, $s);
  10461. }
  10462. /**
  10463. * Format output stream (DEPRECATED).
  10464. * @param string $s string to output.
  10465. * @param int $n object reference for encryption mode
  10466. * @access protected
  10467. * @deprecated
  10468. */
  10469. protected function _getstream($s, $n=0) {
  10470. return 'stream'."\n".$this->_getrawstream($s, $n)."\n".'endstream';
  10471. }
  10472. /**
  10473. * Output a stream (DEPRECATED).
  10474. * @param string $s string to output.
  10475. * @param int $n object reference for encryption mode
  10476. * @access protected
  10477. * @deprecated
  10478. */
  10479. protected function _putstream($s, $n=0) {
  10480. $this->_out($this->_getstream($s, $n));
  10481. }
  10482. /**
  10483. * Output a string to the document.
  10484. * @param string $s string to output.
  10485. * @access protected
  10486. */
  10487. protected function _out($s) {
  10488. if ($this->state == 2) {
  10489. if ($this->inxobj) {
  10490. // we are inside an XObject template
  10491. $this->xobjects[$this->xobjid]['outdata'] .= $s."\n";
  10492. } elseif ((!$this->InFooter) AND isset($this->footerlen[$this->page]) AND ($this->footerlen[$this->page] > 0)) {
  10493. // puts data before page footer
  10494. $pagebuff = $this->getPageBuffer($this->page);
  10495. $page = substr($pagebuff, 0, -$this->footerlen[$this->page]);
  10496. $footer = substr($pagebuff, -$this->footerlen[$this->page]);
  10497. $this->setPageBuffer($this->page, $page.$s."\n".$footer);
  10498. // update footer position
  10499. $this->footerpos[$this->page] += strlen($s."\n");
  10500. } else {
  10501. $this->setPageBuffer($this->page, $s."\n", true);
  10502. }
  10503. } else {
  10504. $this->setBuffer($s."\n");
  10505. }
  10506. }
  10507. /**
  10508. * Converts UTF-8 strings to codepoints array.<br>
  10509. * Invalid byte sequences will be replaced with 0xFFFD (replacement character)<br>
  10510. * Based on: http://www.faqs.org/rfcs/rfc3629.html
  10511. * <pre>
  10512. * Char. number range | UTF-8 octet sequence
  10513. * (hexadecimal) | (binary)
  10514. * --------------------+-----------------------------------------------
  10515. * 0000 0000-0000 007F | 0xxxxxxx
  10516. * 0000 0080-0000 07FF | 110xxxxx 10xxxxxx
  10517. * 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
  10518. * 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
  10519. * ---------------------------------------------------------------------
  10520. *
  10521. * ABFN notation:
  10522. * ---------------------------------------------------------------------
  10523. * UTF8-octets = *( UTF8-char )
  10524. * UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
  10525. * UTF8-1 = %x00-7F
  10526. * UTF8-2 = %xC2-DF UTF8-tail
  10527. *
  10528. * UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
  10529. * %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
  10530. * UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
  10531. * %xF4 %x80-8F 2( UTF8-tail )
  10532. * UTF8-tail = %x80-BF
  10533. * ---------------------------------------------------------------------
  10534. * </pre>
  10535. * @param string $str string to process.
  10536. * @return array containing codepoints (UTF-8 characters values)
  10537. * @access protected
  10538. * @author Nicola Asuni
  10539. * @since 1.53.0.TC005 (2005-01-05)
  10540. */
  10541. protected function UTF8StringToArray($str) {
  10542. // build a unique string key
  10543. $strkey = md5($str);
  10544. if (isset($this->cache_UTF8StringToArray[$strkey])) {
  10545. // return cached value
  10546. $chrarray = $this->cache_UTF8StringToArray[$strkey]['s'];
  10547. if (!isset($this->cache_UTF8StringToArray[$strkey]['f'][$this->CurrentFont['fontkey']])) {
  10548. if ($this->isunicode) {
  10549. foreach ($chrarray as $chr) {
  10550. // store this char for font subsetting
  10551. $this->CurrentFont['subsetchars'][$chr] = true;
  10552. }
  10553. // update font subsetchars
  10554. $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
  10555. }
  10556. $this->cache_UTF8StringToArray[$strkey]['f'][$this->CurrentFont['fontkey']] = true;
  10557. }
  10558. return $chrarray;
  10559. }
  10560. // check cache size
  10561. if ($this->cache_size_UTF8StringToArray >= $this->cache_maxsize_UTF8StringToArray) {
  10562. // remove first element
  10563. array_shift($this->cache_UTF8StringToArray);
  10564. }
  10565. // new cache array for selected string
  10566. $this->cache_UTF8StringToArray[$strkey] = array('s' => array(), 'f' => array());
  10567. ++$this->cache_size_UTF8StringToArray;
  10568. if (!$this->isunicode) {
  10569. // split string into array of equivalent codes
  10570. $strarr = array();
  10571. $strlen = strlen($str);
  10572. for ($i=0; $i < $strlen; ++$i) {
  10573. $strarr[] = ord($str{$i});
  10574. }
  10575. // insert new value on cache
  10576. $this->cache_UTF8StringToArray[$strkey]['s'] = $strarr;
  10577. $this->cache_UTF8StringToArray[$strkey]['f'][$this->CurrentFont['fontkey']] = true;
  10578. return $strarr;
  10579. }
  10580. $unichar = -1; // last unicode char
  10581. $unicode = array(); // array containing unicode values
  10582. $bytes = array(); // array containing single character byte sequences
  10583. $numbytes = 1; // number of octetc needed to represent the UTF-8 character
  10584. $str .= ''; // force $str to be a string
  10585. $length = strlen($str);
  10586. for ($i = 0; $i < $length; ++$i) {
  10587. $char = ord($str{$i}); // get one string character at time
  10588. if (count($bytes) == 0) { // get starting octect
  10589. if ($char <= 0x7F) {
  10590. $unichar = $char; // use the character "as is" because is ASCII
  10591. $numbytes = 1;
  10592. } elseif (($char >> 0x05) == 0x06) { // 2 bytes character (0x06 = 110 BIN)
  10593. $bytes[] = ($char - 0xC0) << 0x06;
  10594. $numbytes = 2;
  10595. } elseif (($char >> 0x04) == 0x0E) { // 3 bytes character (0x0E = 1110 BIN)
  10596. $bytes[] = ($char - 0xE0) << 0x0C;
  10597. $numbytes = 3;
  10598. } elseif (($char >> 0x03) == 0x1E) { // 4 bytes character (0x1E = 11110 BIN)
  10599. $bytes[] = ($char - 0xF0) << 0x12;
  10600. $numbytes = 4;
  10601. } else {
  10602. // use replacement character for other invalid sequences
  10603. $unichar = 0xFFFD;
  10604. $bytes = array();
  10605. $numbytes = 1;
  10606. }
  10607. } elseif (($char >> 0x06) == 0x02) { // bytes 2, 3 and 4 must start with 0x02 = 10 BIN
  10608. $bytes[] = $char - 0x80;
  10609. if (count($bytes) == $numbytes) {
  10610. // compose UTF-8 bytes to a single unicode value
  10611. $char = $bytes[0];
  10612. for ($j = 1; $j < $numbytes; ++$j) {
  10613. $char += ($bytes[$j] << (($numbytes - $j - 1) * 0x06));
  10614. }
  10615. if ((($char >= 0xD800) AND ($char <= 0xDFFF)) OR ($char >= 0x10FFFF)) {
  10616. /* The definition of UTF-8 prohibits encoding character numbers between
  10617. U+D800 and U+DFFF, which are reserved for use with the UTF-16
  10618. encoding form (as surrogate pairs) and do not directly represent
  10619. characters. */
  10620. $unichar = 0xFFFD; // use replacement character
  10621. } else {
  10622. $unichar = $char; // add char to array
  10623. }
  10624. // reset data for next char
  10625. $bytes = array();
  10626. $numbytes = 1;
  10627. }
  10628. } else {
  10629. // use replacement character for other invalid sequences
  10630. $unichar = 0xFFFD;
  10631. $bytes = array();
  10632. $numbytes = 1;
  10633. }
  10634. if ($unichar >= 0) {
  10635. // insert unicode value into array
  10636. $unicode[] = $unichar;
  10637. // store this char for font subsetting
  10638. $this->CurrentFont['subsetchars'][$unichar] = true;
  10639. $unichar = -1;
  10640. }
  10641. }
  10642. // update font subsetchars
  10643. $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
  10644. // insert new value on cache
  10645. $this->cache_UTF8StringToArray[$strkey]['s'] = $unicode;
  10646. $this->cache_UTF8StringToArray[$strkey]['f'][$this->CurrentFont['fontkey']] = true;
  10647. return $unicode;
  10648. }
  10649. /**
  10650. * Converts UTF-8 strings to UTF16-BE.<br>
  10651. * @param string $str string to process.
  10652. * @param boolean $setbom if true set the Byte Order Mark (BOM = 0xFEFF)
  10653. * @return string
  10654. * @access protected
  10655. * @author Nicola Asuni
  10656. * @since 1.53.0.TC005 (2005-01-05)
  10657. * @uses UTF8StringToArray(), arrUTF8ToUTF16BE()
  10658. */
  10659. protected function UTF8ToUTF16BE($str, $setbom=true) {
  10660. if (!$this->isunicode) {
  10661. return $str; // string is not in unicode
  10662. }
  10663. $unicode = $this->UTF8StringToArray($str); // array containing UTF-8 unicode values
  10664. return $this->arrUTF8ToUTF16BE($unicode, $setbom);
  10665. }
  10666. /**
  10667. * Converts UTF-8 strings to Latin1 when using the standard 14 core fonts.<br>
  10668. * @param string $str string to process.
  10669. * @return string
  10670. * @author Andrew Whitehead, Nicola Asuni
  10671. * @access protected
  10672. * @since 3.2.000 (2008-06-23)
  10673. */
  10674. protected function UTF8ToLatin1($str) {
  10675. if (!$this->isunicode) {
  10676. return $str; // string is not in unicode
  10677. }
  10678. $outstr = ''; // string to be returned
  10679. $unicode = $this->UTF8StringToArray($str); // array containing UTF-8 unicode values
  10680. foreach ($unicode as $char) {
  10681. if ($char < 256) {
  10682. $outstr .= chr($char);
  10683. } elseif (array_key_exists($char, $this->unicode->uni_utf8tolatin)) {
  10684. // map from UTF-8
  10685. $outstr .= chr($this->unicode->uni_utf8tolatin[$char]);
  10686. } elseif ($char == 0xFFFD) {
  10687. // skip
  10688. } else {
  10689. $outstr .= '?';
  10690. }
  10691. }
  10692. return $outstr;
  10693. }
  10694. /**
  10695. * Converts UTF-8 characters array to array of Latin1 characters<br>
  10696. * @param array $unicode array containing UTF-8 unicode values
  10697. * @return array
  10698. * @author Nicola Asuni
  10699. * @access protected
  10700. * @since 4.8.023 (2010-01-15)
  10701. */
  10702. protected function UTF8ArrToLatin1($unicode) {
  10703. if ((!$this->isunicode) OR $this->isUnicodeFont()) {
  10704. return $unicode;
  10705. }
  10706. $outarr = array(); // array to be returned
  10707. foreach ($unicode as $char) {
  10708. if ($char < 256) {
  10709. $outarr[] = $char;
  10710. } elseif (array_key_exists($char, $this->unicode->uni_utf8tolatin)) {
  10711. // map from UTF-8
  10712. $outarr[] = $this->unicode->uni_utf8tolatin[$char];
  10713. } elseif ($char == 0xFFFD) {
  10714. // skip
  10715. } else {
  10716. $outarr[] = 63; // '?' character
  10717. }
  10718. }
  10719. return $outarr;
  10720. }
  10721. /**
  10722. * Converts array of UTF-8 characters to UTF16-BE string.<br>
  10723. * Based on: http://www.faqs.org/rfcs/rfc2781.html
  10724. * <pre>
  10725. * Encoding UTF-16:
  10726. *
  10727. * Encoding of a single character from an ISO 10646 character value to
  10728. * UTF-16 proceeds as follows. Let U be the character number, no greater
  10729. * than 0x10FFFF.
  10730. *
  10731. * 1) If U < 0x10000, encode U as a 16-bit unsigned integer and
  10732. * terminate.
  10733. *
  10734. * 2) Let U' = U - 0x10000. Because U is less than or equal to 0x10FFFF,
  10735. * U' must be less than or equal to 0xFFFFF. That is, U' can be
  10736. * represented in 20 bits.
  10737. *
  10738. * 3) Initialize two 16-bit unsigned integers, W1 and W2, to 0xD800 and
  10739. * 0xDC00, respectively. These integers each have 10 bits free to
  10740. * encode the character value, for a total of 20 bits.
  10741. *
  10742. * 4) Assign the 10 high-order bits of the 20-bit U' to the 10 low-order
  10743. * bits of W1 and the 10 low-order bits of U' to the 10 low-order
  10744. * bits of W2. Terminate.
  10745. *
  10746. * Graphically, steps 2 through 4 look like:
  10747. * U' = yyyyyyyyyyxxxxxxxxxx
  10748. * W1 = 110110yyyyyyyyyy
  10749. * W2 = 110111xxxxxxxxxx
  10750. * </pre>
  10751. * @param array $unicode array containing UTF-8 unicode values
  10752. * @param boolean $setbom if true set the Byte Order Mark (BOM = 0xFEFF)
  10753. * @return string
  10754. * @access protected
  10755. * @author Nicola Asuni
  10756. * @since 2.1.000 (2008-01-08)
  10757. * @see UTF8ToUTF16BE()
  10758. */
  10759. protected function arrUTF8ToUTF16BE($unicode, $setbom=true) {
  10760. $outstr = ''; // string to be returned
  10761. if ($setbom) {
  10762. $outstr .= "\xFE\xFF"; // Byte Order Mark (BOM)
  10763. }
  10764. foreach ($unicode as $char) {
  10765. if ($char == 0x200b) {
  10766. // skip Unicode Character 'ZERO WIDTH SPACE' (DEC:8203, U+200B)
  10767. } elseif ($char == 0xFFFD) {
  10768. $outstr .= "\xFF\xFD"; // replacement character
  10769. } elseif ($char < 0x10000) {
  10770. $outstr .= chr($char >> 0x08);
  10771. $outstr .= chr($char & 0xFF);
  10772. } else {
  10773. $char -= 0x10000;
  10774. $w1 = 0xD800 | ($char >> 0x10);
  10775. $w2 = 0xDC00 | ($char & 0x3FF);
  10776. $outstr .= chr($w1 >> 0x08);
  10777. $outstr .= chr($w1 & 0xFF);
  10778. $outstr .= chr($w2 >> 0x08);
  10779. $outstr .= chr($w2 & 0xFF);
  10780. }
  10781. }
  10782. return $outstr;
  10783. }
  10784. // ====================================================
  10785. /**
  10786. * Set header font.
  10787. * @param array $font font
  10788. * @access public
  10789. * @since 1.1
  10790. */
  10791. public function setHeaderFont($font) {
  10792. $this->header_font = $font;
  10793. }
  10794. /**
  10795. * Get header font.
  10796. * @return array()
  10797. * @access public
  10798. * @since 4.0.012 (2008-07-24)
  10799. */
  10800. public function getHeaderFont() {
  10801. return $this->header_font;
  10802. }
  10803. /**
  10804. * Set footer font.
  10805. * @param array $font font
  10806. * @access public
  10807. * @since 1.1
  10808. */
  10809. public function setFooterFont($font) {
  10810. $this->footer_font = $font;
  10811. }
  10812. /**
  10813. * Get Footer font.
  10814. * @return array()
  10815. * @access public
  10816. * @since 4.0.012 (2008-07-24)
  10817. */
  10818. public function getFooterFont() {
  10819. return $this->footer_font;
  10820. }
  10821. /**
  10822. * Set language array.
  10823. * @param array $language
  10824. * @access public
  10825. * @since 1.1
  10826. */
  10827. public function setLanguageArray($language) {
  10828. $this->l = $language;
  10829. if (isset($this->l['a_meta_dir'])) {
  10830. $this->rtl = $this->l['a_meta_dir']=='rtl' ? true : false;
  10831. } else {
  10832. $this->rtl = false;
  10833. }
  10834. }
  10835. /**
  10836. * Returns the PDF data.
  10837. * @access public
  10838. */
  10839. public function getPDFData() {
  10840. if ($this->state < 3) {
  10841. $this->Close();
  10842. }
  10843. return $this->buffer;
  10844. }
  10845. /**
  10846. * Output anchor link.
  10847. * @param string $url link URL or internal link (i.e.: &lt;a href="#23,4.5"&gt;link to page 23 at 4.5 Y position&lt;/a&gt;)
  10848. * @param string $name link name
  10849. * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
  10850. * @param boolean $firstline if true prints only the first line and return the remaining string.
  10851. * @param array $color array of RGB text color
  10852. * @param string $style font style (U, D, B, I)
  10853. * @param boolean $firstblock if true the string is the starting of a line.
  10854. * @return the number of cells used or the remaining text if $firstline = true;
  10855. * @access public
  10856. */
  10857. public function addHtmlLink($url, $name, $fill=false, $firstline=false, $color='', $style=-1, $firstblock=false) {
  10858. if (!$this->empty_string($url) AND ($url{0} == '#')) {
  10859. // convert url to internal link
  10860. $lnkdata = explode(',', $url);
  10861. if (isset($lnkdata[0])) {
  10862. $page = intval(substr($lnkdata[0], 1));
  10863. if (empty($page) OR ($page <= 0)) {
  10864. $page = $this->page;
  10865. }
  10866. if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
  10867. $lnky = floatval($lnkdata[1]);
  10868. } else {
  10869. $lnky = 0;
  10870. }
  10871. $url = $this->AddLink();
  10872. $this->SetLink($url, $lnky, $page);
  10873. }
  10874. }
  10875. // store current settings
  10876. $prevcolor = $this->fgcolor;
  10877. $prevstyle = $this->FontStyle;
  10878. if (empty($color)) {
  10879. $this->SetTextColorArray($this->htmlLinkColorArray);
  10880. } else {
  10881. $this->SetTextColorArray($color);
  10882. }
  10883. if ($style == -1) {
  10884. $this->SetFont('', $this->FontStyle.$this->htmlLinkFontStyle);
  10885. } else {
  10886. $this->SetFont('', $this->FontStyle.$style);
  10887. }
  10888. $ret = $this->Write($this->lasth, $name, $url, $fill, '', false, 0, $firstline, $firstblock, 0);
  10889. // restore settings
  10890. $this->SetFont('', $prevstyle);
  10891. $this->SetTextColorArray($prevcolor);
  10892. return $ret;
  10893. }
  10894. /**
  10895. * Returns an associative array (keys: R,G,B) from an html color name or a six-digit or three-digit hexadecimal color representation (i.e. #3FE5AA or #7FF).
  10896. * @param string $color html color
  10897. * @return array RGB color or false in case of error.
  10898. * @access public
  10899. */
  10900. public function convertHTMLColorToDec($color='#FFFFFF') {
  10901. $returncolor = false;
  10902. $color = preg_replace('/[\s]*/', '', $color); // remove extra spaces
  10903. $color = strtolower($color);
  10904. if (($dotpos = strpos($color, '.')) !== false) {
  10905. // remove class parent (i.e.: color.red)
  10906. $color = substr($color, ($dotpos + 1));
  10907. }
  10908. if (strlen($color) == 0) {
  10909. return false;
  10910. }
  10911. // RGB ARRAY
  10912. if (substr($color, 0, 3) == 'rgb') {
  10913. $codes = substr($color, 4);
  10914. $codes = str_replace(')', '', $codes);
  10915. $returncolor = explode(',', $codes);
  10916. return $returncolor;
  10917. }
  10918. // CMYK ARRAY
  10919. if (substr($color, 0, 4) == 'cmyk') {
  10920. $codes = substr($color, 5);
  10921. $codes = str_replace(')', '', $codes);
  10922. $returncolor = explode(',', $codes);
  10923. return $returncolor;
  10924. }
  10925. // COLOR NAME
  10926. if (substr($color, 0, 1) != '#') {
  10927. // decode color name
  10928. if (isset($this->webcolor[$color])) {
  10929. $color_code = $this->webcolor[$color];
  10930. } else {
  10931. return false;
  10932. }
  10933. } else {
  10934. $color_code = substr($color, 1);
  10935. }
  10936. // RGB VALUE
  10937. switch (strlen($color_code)) {
  10938. case 3: {
  10939. // three-digit hexadecimal representation
  10940. $r = substr($color_code, 0, 1);
  10941. $g = substr($color_code, 1, 1);
  10942. $b = substr($color_code, 2, 1);
  10943. $returncolor['R'] = hexdec($r.$r);
  10944. $returncolor['G'] = hexdec($g.$g);
  10945. $returncolor['B'] = hexdec($b.$b);
  10946. break;
  10947. }
  10948. case 6: {
  10949. // six-digit hexadecimal representation
  10950. $returncolor['R'] = hexdec(substr($color_code, 0, 2));
  10951. $returncolor['G'] = hexdec(substr($color_code, 2, 2));
  10952. $returncolor['B'] = hexdec(substr($color_code, 4, 2));
  10953. break;
  10954. }
  10955. }
  10956. return $returncolor;
  10957. }
  10958. /**
  10959. * Converts pixels to User's Units.
  10960. * @param int $px pixels
  10961. * @return float value in user's unit
  10962. * @access public
  10963. * @see setImageScale(), getImageScale()
  10964. */
  10965. public function pixelsToUnits($px) {
  10966. return ($px / ($this->imgscale * $this->k));
  10967. }
  10968. /**
  10969. * Reverse function for htmlentities.
  10970. * Convert entities in UTF-8.
  10971. * @param string $text_to_convert Text to convert.
  10972. * @return string converted text string
  10973. * @access public
  10974. */
  10975. public function unhtmlentities($text_to_convert) {
  10976. return html_entity_decode($text_to_convert, ENT_QUOTES, $this->encoding);
  10977. }
  10978. // ENCRYPTION METHODS ----------------------------------
  10979. /**
  10980. * Returns a string containing random data to be used as a seed for encryption methods.
  10981. * @param string $seed starting seed value
  10982. * @return string containing random data
  10983. * @author Nicola Asuni
  10984. * @since 5.9.006 (2010-10-19)
  10985. * @access protected
  10986. */
  10987. protected function getRandomSeed($seed='') {
  10988. $seed .= microtime();
  10989. if (function_exists('openssl_random_pseudo_bytes')) {
  10990. $seed .= openssl_random_pseudo_bytes(512);
  10991. }
  10992. $seed .= uniqid('', true);
  10993. $seed .= rand();
  10994. $seed .= getmypid();
  10995. $seed .= __FILE__;
  10996. $seed .= $this->bufferlen;
  10997. if (isset($_SERVER['REMOTE_ADDR'])) {
  10998. $seed .= $_SERVER['REMOTE_ADDR'];
  10999. }
  11000. if (isset($_SERVER['HTTP_USER_AGENT'])) {
  11001. $seed .= $_SERVER['HTTP_USER_AGENT'];
  11002. }
  11003. if (isset($_SERVER['HTTP_ACCEPT'])) {
  11004. $seed .= $_SERVER['HTTP_ACCEPT'];
  11005. }
  11006. if (isset($_SERVER['HTTP_ACCEPT_ENCODING'])) {
  11007. $seed .= $_SERVER['HTTP_ACCEPT_ENCODING'];
  11008. }
  11009. if (isset($_SERVER['HTTP_ACCEPT_LANGUAGE'])) {
  11010. $seed .= $_SERVER['HTTP_ACCEPT_LANGUAGE'];
  11011. }
  11012. if (isset($_SERVER['HTTP_ACCEPT_CHARSET'])) {
  11013. $seed .= $_SERVER['HTTP_ACCEPT_CHARSET'];
  11014. }
  11015. $seed .= rand();
  11016. $seed .= uniqid('', true);
  11017. $seed .= microtime();
  11018. return $seed;
  11019. }
  11020. /**
  11021. * Compute encryption key depending on object number where the encrypted data is stored.
  11022. * This is used for all strings and streams without crypt filter specifier.
  11023. * @param int $n object number
  11024. * @return int object key
  11025. * @access protected
  11026. * @author Nicola Asuni
  11027. * @since 2.0.000 (2008-01-02)
  11028. */
  11029. protected function _objectkey($n) {
  11030. $objkey = $this->encryptdata['key'].pack('VXxx', $n);
  11031. if ($this->encryptdata['mode'] == 2) { // AES-128
  11032. // AES padding
  11033. $objkey .= "\x73\x41\x6C\x54"; // sAlT
  11034. }
  11035. $objkey = substr($this->_md5_16($objkey), 0, (($this->encryptdata['Length'] / 8) + 5));
  11036. $objkey = substr($objkey, 0, 16);
  11037. return $objkey;
  11038. }
  11039. /**
  11040. * Encrypt the input string.
  11041. * @param int $n object number
  11042. * @param string $s data string to encrypt
  11043. * @return encrypted string
  11044. * @access protected
  11045. * @author Nicola Asuni
  11046. * @since 5.0.005 (2010-05-11)
  11047. */
  11048. protected function _encrypt_data($n, $s) {
  11049. if (!$this->encrypted) {
  11050. return $s;
  11051. }
  11052. switch ($this->encryptdata['mode']) {
  11053. case 0: // RC4-40
  11054. case 1: { // RC4-128
  11055. $s = $this->_RC4($this->_objectkey($n), $s);
  11056. break;
  11057. }
  11058. case 2: { // AES-128
  11059. $s = $this->_AES($this->_objectkey($n), $s);
  11060. break;
  11061. }
  11062. case 3: { // AES-256
  11063. $s = $this->_AES($this->encryptdata['key'], $s);
  11064. break;
  11065. }
  11066. }
  11067. return $s;
  11068. }
  11069. /**
  11070. * Put encryption on PDF document.
  11071. * @access protected
  11072. * @author Nicola Asuni
  11073. * @since 2.0.000 (2008-01-02)
  11074. */
  11075. protected function _putencryption() {
  11076. if (!$this->encrypted) {
  11077. return;
  11078. }
  11079. $this->encryptdata['objid'] = $this->_newobj();
  11080. $out = '<<';
  11081. if (!isset($this->encryptdata['Filter']) OR empty($this->encryptdata['Filter'])) {
  11082. $this->encryptdata['Filter'] = 'Standard';
  11083. }
  11084. $out .= ' /Filter /'.$this->encryptdata['Filter'];
  11085. if (isset($this->encryptdata['SubFilter']) AND !empty($this->encryptdata['SubFilter'])) {
  11086. $out .= ' /SubFilter /'.$this->encryptdata['SubFilter'];
  11087. }
  11088. if (!isset($this->encryptdata['V']) OR empty($this->encryptdata['V'])) {
  11089. $this->encryptdata['V'] = 1;
  11090. }
  11091. // V is a code specifying the algorithm to be used in encrypting and decrypting the document
  11092. $out .= ' /V '.$this->encryptdata['V'];
  11093. if (isset($this->encryptdata['Length']) AND !empty($this->encryptdata['Length'])) {
  11094. // The length of the encryption key, in bits. The value shall be a multiple of 8, in the range 40 to 256
  11095. $out .= ' /Length '.$this->encryptdata['Length'];
  11096. } else {
  11097. $out .= ' /Length 40';
  11098. }
  11099. if ($this->encryptdata['V'] >= 4) {
  11100. if (!isset($this->encryptdata['StmF']) OR empty($this->encryptdata['StmF'])) {
  11101. $this->encryptdata['StmF'] = 'Identity';
  11102. }
  11103. if (!isset($this->encryptdata['StrF']) OR empty($this->encryptdata['StrF'])) {
  11104. // The name of the crypt filter that shall be used when decrypting all strings in the document.
  11105. $this->encryptdata['StrF'] = 'Identity';
  11106. }
  11107. // A dictionary whose keys shall be crypt filter names and whose values shall be the corresponding crypt filter dictionaries.
  11108. if (isset($this->encryptdata['CF']) AND !empty($this->encryptdata['CF'])) {
  11109. $out .= ' /CF <<';
  11110. $out .= ' /'.$this->encryptdata['StmF'].' <<';
  11111. $out .= ' /Type /CryptFilter';
  11112. if (isset($this->encryptdata['CF']['CFM']) AND !empty($this->encryptdata['CF']['CFM'])) {
  11113. // The method used
  11114. $out .= ' /CFM /'.$this->encryptdata['CF']['CFM'];
  11115. if ($this->encryptdata['pubkey']) {
  11116. $out .= ' /Recipients [';
  11117. foreach ($this->encryptdata['Recipients'] as $rec) {
  11118. $out .= ' <'.$rec.'>';
  11119. }
  11120. $out .= ' ]';
  11121. if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) {
  11122. $out .= ' /EncryptMetadata false';
  11123. } else {
  11124. $out .= ' /EncryptMetadata true';
  11125. }
  11126. }
  11127. } else {
  11128. $out .= ' /CFM /None';
  11129. }
  11130. if (isset($this->encryptdata['CF']['AuthEvent']) AND !empty($this->encryptdata['CF']['AuthEvent'])) {
  11131. // The event to be used to trigger the authorization that is required to access encryption keys used by this filter.
  11132. $out .= ' /AuthEvent /'.$this->encryptdata['CF']['AuthEvent'];
  11133. } else {
  11134. $out .= ' /AuthEvent /DocOpen';
  11135. }
  11136. if (isset($this->encryptdata['CF']['Length']) AND !empty($this->encryptdata['CF']['Length'])) {
  11137. // The bit length of the encryption key.
  11138. $out .= ' /Length '.$this->encryptdata['CF']['Length'];
  11139. }
  11140. $out .= ' >> >>';
  11141. }
  11142. // The name of the crypt filter that shall be used by default when decrypting streams.
  11143. $out .= ' /StmF /'.$this->encryptdata['StmF'];
  11144. // The name of the crypt filter that shall be used when decrypting all strings in the document.
  11145. $out .= ' /StrF /'.$this->encryptdata['StrF'];
  11146. if (isset($this->encryptdata['EFF']) AND !empty($this->encryptdata['EFF'])) {
  11147. // The name of the crypt filter that shall be used when encrypting embedded file streams that do not have their own crypt filter specifier.
  11148. $out .= ' /EFF /'.$this->encryptdata[''];
  11149. }
  11150. }
  11151. // Additional encryption dictionary entries for the standard security handler
  11152. if ($this->encryptdata['pubkey']) {
  11153. if (($this->encryptdata['V'] < 4) AND isset($this->encryptdata['Recipients']) AND !empty($this->encryptdata['Recipients'])) {
  11154. $out .= ' /Recipients [';
  11155. foreach ($this->encryptdata['Recipients'] as $rec) {
  11156. $out .= ' <'.$rec.'>';
  11157. }
  11158. $out .= ' ]';
  11159. }
  11160. } else {
  11161. $out .= ' /R';
  11162. if ($this->encryptdata['V'] == 5) { // AES-256
  11163. $out .= ' 5';
  11164. $out .= ' /OE ('.$this->_escape($this->encryptdata['OE']).')';
  11165. $out .= ' /UE ('.$this->_escape($this->encryptdata['UE']).')';
  11166. $out .= ' /Perms ('.$this->_escape($this->encryptdata['perms']).')';
  11167. } elseif ($this->encryptdata['V'] == 4) { // AES-128
  11168. $out .= ' 4';
  11169. } elseif ($this->encryptdata['V'] < 2) { // RC-40
  11170. $out .= ' 2';
  11171. } else { // RC-128
  11172. $out .= ' 3';
  11173. }
  11174. $out .= ' /O ('.$this->_escape($this->encryptdata['O']).')';
  11175. $out .= ' /U ('.$this->_escape($this->encryptdata['U']).')';
  11176. $out .= ' /P '.$this->encryptdata['P'];
  11177. if (isset($this->encryptdata['EncryptMetadata']) AND (!$this->encryptdata['EncryptMetadata'])) {
  11178. $out .= ' /EncryptMetadata false';
  11179. } else {
  11180. $out .= ' /EncryptMetadata true';
  11181. }
  11182. }
  11183. $out .= ' >>';
  11184. $out .= "\n".'endobj';
  11185. $this->_out($out);
  11186. }
  11187. /**
  11188. * Returns the input text encrypted using RC4 algorithm and the specified key.
  11189. * RC4 is the standard encryption algorithm used in PDF format
  11190. * @param string $key encryption key
  11191. * @param String $text input text to be encrypted
  11192. * @return String encrypted text
  11193. * @access protected
  11194. * @since 2.0.000 (2008-01-02)
  11195. * @author Klemen Vodopivec, Nicola Asuni
  11196. */
  11197. protected function _RC4($key, $text) {
  11198. if (function_exists('mcrypt_decrypt') AND ($out = @mcrypt_decrypt(MCRYPT_ARCFOUR, $key, $text, MCRYPT_MODE_STREAM, ''))) {
  11199. // try to use mcrypt function if exist
  11200. return $out;
  11201. }
  11202. if ($this->last_enc_key != $key) {
  11203. $k = str_repeat($key, ((256 / strlen($key)) + 1));
  11204. $rc4 = range(0, 255);
  11205. $j = 0;
  11206. for ($i = 0; $i < 256; ++$i) {
  11207. $t = $rc4[$i];
  11208. $j = ($j + $t + ord($k{$i})) % 256;
  11209. $rc4[$i] = $rc4[$j];
  11210. $rc4[$j] = $t;
  11211. }
  11212. $this->last_enc_key = $key;
  11213. $this->last_enc_key_c = $rc4;
  11214. } else {
  11215. $rc4 = $this->last_enc_key_c;
  11216. }
  11217. $len = strlen($text);
  11218. $a = 0;
  11219. $b = 0;
  11220. $out = '';
  11221. for ($i = 0; $i < $len; ++$i) {
  11222. $a = ($a + 1) % 256;
  11223. $t = $rc4[$a];
  11224. $b = ($b + $t) % 256;
  11225. $rc4[$a] = $rc4[$b];
  11226. $rc4[$b] = $t;
  11227. $k = $rc4[($rc4[$a] + $rc4[$b]) % 256];
  11228. $out .= chr(ord($text{$i}) ^ $k);
  11229. }
  11230. return $out;
  11231. }
  11232. /**
  11233. * Returns the input text exrypted using AES algorithm and the specified key.
  11234. * This method requires mcrypt.
  11235. * @param string $key encryption key
  11236. * @param String $text input text to be encrypted
  11237. * @return String encrypted text
  11238. * @access protected
  11239. * @author Nicola Asuni
  11240. * @since 5.0.005 (2010-05-11)
  11241. */
  11242. protected function _AES($key, $text) {
  11243. // padding (RFC 2898, PKCS #5: Password-Based Cryptography Specification Version 2.0)
  11244. $padding = 16 - (strlen($text) % 16);
  11245. $text .= str_repeat(chr($padding), $padding);
  11246. $iv = mcrypt_create_iv(mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC), MCRYPT_RAND);
  11247. $text = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $key, $text, MCRYPT_MODE_CBC, $iv);
  11248. $text = $iv.$text;
  11249. return $text;
  11250. }
  11251. /**
  11252. * Encrypts a string using MD5 and returns it's value as a binary string.
  11253. * @param string $str input string
  11254. * @return String MD5 encrypted binary string
  11255. * @access protected
  11256. * @since 2.0.000 (2008-01-02)
  11257. * @author Klemen Vodopivec
  11258. */
  11259. protected function _md5_16($str) {
  11260. return pack('H*', md5($str));
  11261. }
  11262. /**
  11263. * Compute U value (used for encryption)
  11264. * @return string U value
  11265. * @access protected
  11266. * @since 2.0.000 (2008-01-02)
  11267. * @author Nicola Asuni
  11268. */
  11269. protected function _Uvalue() {
  11270. if ($this->encryptdata['mode'] == 0) { // RC4-40
  11271. return $this->_RC4($this->encryptdata['key'], $this->enc_padding);
  11272. } elseif ($this->encryptdata['mode'] < 3) { // RC4-128, AES-128
  11273. $tmp = $this->_md5_16($this->enc_padding.$this->encryptdata['fileid']);
  11274. $enc = $this->_RC4($this->encryptdata['key'], $tmp);
  11275. $len = strlen($tmp);
  11276. for ($i = 1; $i <= 19; ++$i) {
  11277. $ek = '';
  11278. for ($j = 0; $j < $len; ++$j) {
  11279. $ek .= chr(ord($this->encryptdata['key']{$j}) ^ $i);
  11280. }
  11281. $enc = $this->_RC4($ek, $enc);
  11282. }
  11283. $enc .= str_repeat("\x00", 16);
  11284. return substr($enc, 0, 32);
  11285. } elseif ($this->encryptdata['mode'] == 3) { // AES-256
  11286. $seed = $this->_md5_16($this->getRandomSeed());
  11287. // User Validation Salt
  11288. $this->encryptdata['UVS'] = substr($seed, 0, 8);
  11289. // User Key Salt
  11290. $this->encryptdata['UKS'] = substr($seed, 8, 16);
  11291. return hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UVS'], true).$this->encryptdata['UVS'].$this->encryptdata['UKS'];
  11292. }
  11293. }
  11294. /**
  11295. * Compute UE value (used for encryption)
  11296. * @return string UE value
  11297. * @access protected
  11298. * @since 5.9.006 (2010-10-19)
  11299. * @author Nicola Asuni
  11300. */
  11301. protected function _UEvalue() {
  11302. $hashkey = hash('sha256', $this->encryptdata['user_password'].$this->encryptdata['UKS'], true);
  11303. $iv = str_repeat("\x00", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC));
  11304. return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $hashkey, $this->encryptdata['key'], MCRYPT_MODE_CBC, $iv);
  11305. }
  11306. /**
  11307. * Compute O value (used for encryption)
  11308. * @return string O value
  11309. * @access protected
  11310. * @since 2.0.000 (2008-01-02)
  11311. * @author Nicola Asuni
  11312. */
  11313. protected function _Ovalue() {
  11314. if ($this->encryptdata['mode'] < 3) { // RC4-40, RC4-128, AES-128
  11315. $tmp = $this->_md5_16($this->encryptdata['owner_password']);
  11316. if ($this->encryptdata['mode'] > 0) {
  11317. for ($i = 0; $i < 50; ++$i) {
  11318. $tmp = $this->_md5_16($tmp);
  11319. }
  11320. }
  11321. $owner_key = substr($tmp, 0, ($this->encryptdata['Length'] / 8));
  11322. $enc = $this->_RC4($owner_key, $this->encryptdata['user_password']);
  11323. if ($this->encryptdata['mode'] > 0) {
  11324. $len = strlen($owner_key);
  11325. for ($i = 1; $i <= 19; ++$i) {
  11326. $ek = '';
  11327. for ($j = 0; $j < $len; ++$j) {
  11328. $ek .= chr(ord($owner_key{$j}) ^ $i);
  11329. }
  11330. $enc = $this->_RC4($ek, $enc);
  11331. }
  11332. }
  11333. return $enc;
  11334. } elseif ($this->encryptdata['mode'] == 3) { // AES-256
  11335. $seed = $this->_md5_16($this->getRandomSeed());
  11336. // Owner Validation Salt
  11337. $this->encryptdata['OVS'] = substr($seed, 0, 8);
  11338. // Owner Key Salt
  11339. $this->encryptdata['OKS'] = substr($seed, 8, 16);
  11340. return hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OVS'].$this->encryptdata['U'], true).$this->encryptdata['OVS'].$this->encryptdata['OKS'];
  11341. }
  11342. }
  11343. /**
  11344. * Compute OE value (used for encryption)
  11345. * @return string OE value
  11346. * @access protected
  11347. * @since 5.9.006 (2010-10-19)
  11348. * @author Nicola Asuni
  11349. */
  11350. protected function _OEvalue() {
  11351. $hashkey = hash('sha256', $this->encryptdata['owner_password'].$this->encryptdata['OKS'].$this->encryptdata['U'], true);
  11352. $iv = str_repeat("\x00", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC));
  11353. return mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $hashkey, $this->encryptdata['key'], MCRYPT_MODE_CBC, $iv);
  11354. }
  11355. /**
  11356. * Convert password for AES-256 encryption mode
  11357. * @return string password
  11358. * @access protected
  11359. * @since 5.9.006 (2010-10-19)
  11360. * @author Nicola Asuni
  11361. */
  11362. protected function _fixAES256Password($password) {
  11363. $psw = ''; // password to be returned
  11364. $psw_array = $this->utf8Bidi($this->UTF8StringToArray($password), $password, $this->rtl);
  11365. foreach ($psw_array as $c) {
  11366. $psw .= $this->unichr($c);
  11367. }
  11368. return substr($psw, 0, 127);
  11369. }
  11370. /**
  11371. * Compute encryption key
  11372. * @access protected
  11373. * @since 2.0.000 (2008-01-02)
  11374. * @author Nicola Asuni
  11375. */
  11376. protected function _generateencryptionkey() {
  11377. $keybytelen = ($this->encryptdata['Length'] / 8);
  11378. if (!$this->encryptdata['pubkey']) { // standard mode
  11379. if ($this->encryptdata['mode'] == 3) { // AES-256
  11380. // generate 256 bit random key
  11381. $this->encryptdata['key'] = substr(hash('sha256', $this->getRandomSeed(), true), 0, $keybytelen);
  11382. // truncate passwords
  11383. $this->encryptdata['user_password'] = $this->_fixAES256Password($this->encryptdata['user_password']);
  11384. $this->encryptdata['owner_password'] = $this->_fixAES256Password($this->encryptdata['owner_password']);
  11385. // Compute U value
  11386. $this->encryptdata['U'] = $this->_Uvalue();
  11387. // Compute UE value
  11388. $this->encryptdata['UE'] = $this->_UEvalue();
  11389. // Compute O value
  11390. $this->encryptdata['O'] = $this->_Ovalue();
  11391. // Compute OE value
  11392. $this->encryptdata['OE'] = $this->_OEvalue();
  11393. // Compute P value
  11394. $this->encryptdata['P'] = $this->encryptdata['protection'];
  11395. // Computing the encryption dictionary's Perms (permissions) value
  11396. $perms = $this->getEncPermissionsString($this->encryptdata['protection']); // bytes 0-3
  11397. $perms .= chr(255).chr(255).chr(255).chr(255); // bytes 4-7
  11398. if (isset($this->encryptdata['CF']['EncryptMetadata']) AND (!$this->encryptdata['CF']['EncryptMetadata'])) { // byte 8
  11399. $perms .= 'F';
  11400. } else {
  11401. $perms .= 'T';
  11402. }
  11403. $perms .= 'adb'; // bytes 9-11
  11404. $perms .= 'nick'; // bytes 12-15
  11405. $iv = str_repeat("\x00", mcrypt_get_iv_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_ECB));
  11406. $this->encryptdata['perms'] = mcrypt_encrypt(MCRYPT_RIJNDAEL_128, $this->encryptdata['key'], $perms, MCRYPT_MODE_ECB, $iv);
  11407. } else { // RC4-40, RC4-128, AES-128
  11408. // Pad passwords
  11409. $this->encryptdata['user_password'] = substr($this->encryptdata['user_password'].$this->enc_padding, 0, 32);
  11410. $this->encryptdata['owner_password'] = substr($this->encryptdata['owner_password'].$this->enc_padding, 0, 32);
  11411. // Compute O value
  11412. $this->encryptdata['O'] = $this->_Ovalue();
  11413. // get default permissions (reverse byte order)
  11414. $permissions = $this->getEncPermissionsString($this->encryptdata['protection']);
  11415. // Compute encryption key
  11416. $tmp = $this->_md5_16($this->encryptdata['user_password'].$this->encryptdata['O'].$permissions.$this->encryptdata['fileid']);
  11417. if ($this->encryptdata['mode'] > 0) {
  11418. for ($i = 0; $i < 50; ++$i) {
  11419. $tmp = $this->_md5_16(substr($tmp, 0, $keybytelen));
  11420. }
  11421. }
  11422. $this->encryptdata['key'] = substr($tmp, 0, $keybytelen);
  11423. // Compute U value
  11424. $this->encryptdata['U'] = $this->_Uvalue();
  11425. // Compute P value
  11426. $this->encryptdata['P'] = $this->encryptdata['protection'];
  11427. }
  11428. } else { // Public-Key mode
  11429. // random 20-byte seed
  11430. $seed = sha1($this->getRandomSeed(), true);
  11431. $recipient_bytes = '';
  11432. foreach ($this->encryptdata['pubkeys'] as $pubkey) {
  11433. // for each public certificate
  11434. if (isset($pubkey['p'])) {
  11435. $pkprotection = $this->getUserPermissionCode($pubkey['p'], $this->encryptdata['mode']);
  11436. } else {
  11437. $pkprotection = $this->encryptdata['protection'];
  11438. }
  11439. // get default permissions (reverse byte order)
  11440. $pkpermissions = $this->getEncPermissionsString($pkprotection);
  11441. // envelope data
  11442. $envelope = $seed.$pkpermissions;
  11443. // write the envelope data to a temporary file
  11444. $tempkeyfile = tempnam(K_PATH_CACHE, 'tmpkey_');
  11445. $f = fopen($tempkeyfile, 'wb');
  11446. if (!$f) {
  11447. $this->Error('Unable to create temporary key file: '.$tempkeyfile);
  11448. }
  11449. $envelope_lenght = strlen($envelope);
  11450. fwrite($f, $envelope, $envelope_lenght);
  11451. fclose($f);
  11452. $tempencfile = tempnam(K_PATH_CACHE, 'tmpenc_');
  11453. if (!openssl_pkcs7_encrypt($tempkeyfile, $tempencfile, $pubkey['c'], array(), PKCS7_DETACHED | PKCS7_BINARY)) {
  11454. $this->Error('Unable to encrypt the file: '.$tempkeyfile);
  11455. }
  11456. unlink($tempkeyfile);
  11457. // read encryption signature
  11458. $signature = file_get_contents($tempencfile, false, null, $envelope_lenght);
  11459. unlink($tempencfile);
  11460. // extract signature
  11461. $signature = substr($signature, strpos($signature, 'Content-Disposition'));
  11462. $tmparr = explode("\n\n", $signature);
  11463. $signature = trim($tmparr[1]);
  11464. unset($tmparr);
  11465. // decode signature
  11466. $signature = base64_decode($signature);
  11467. // convert signature to hex
  11468. $hexsignature = current(unpack('H*', $signature));
  11469. // store signature on recipients array
  11470. $this->encryptdata['Recipients'][] = $hexsignature;
  11471. // The bytes of each item in the Recipients array of PKCS#7 objects in the order in which they appear in the array
  11472. $recipient_bytes .= $signature;
  11473. }
  11474. // calculate encryption key
  11475. if ($this->encryptdata['mode'] == 3) { // AES-256
  11476. $this->encryptdata['key'] = substr(hash('sha256', $seed.$recipient_bytes, true), 0, $keybytelen);
  11477. } else { // RC4-40, RC4-128, AES-128
  11478. $this->encryptdata['key'] = substr(sha1($seed.$recipient_bytes, true), 0, $keybytelen);
  11479. }
  11480. }
  11481. }
  11482. /**
  11483. * Return the premission code used on encryption (P value).
  11484. * @param Array $permissions the set of permissions (specify the ones you want to block).
  11485. * @param int $mode encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit.
  11486. * @access protected
  11487. * @since 5.0.005 (2010-05-12)
  11488. * @author Nicola Asuni
  11489. */
  11490. protected function getUserPermissionCode($permissions, $mode=0) {
  11491. $options = array(
  11492. 'owner' => 2, // bit 2 -- inverted logic: cleared by default
  11493. 'print' => 4, // bit 3
  11494. 'modify' => 8, // bit 4
  11495. 'copy' => 16, // bit 5
  11496. 'annot-forms' => 32, // bit 6
  11497. 'fill-forms' => 256, // bit 9
  11498. 'extract' => 512, // bit 10
  11499. 'assemble' => 1024,// bit 11
  11500. 'print-high' => 2048 // bit 12
  11501. );
  11502. $protection = 2147422012; // 32 bit: (01111111 11111111 00001111 00111100)
  11503. foreach ($permissions as $permission) {
  11504. if (!isset($options[$permission])) {
  11505. $this->Error('Incorrect permission: '.$permission);
  11506. }
  11507. if (($mode > 0) OR ($options[$permission] <= 32)) {
  11508. // set only valid permissions
  11509. if ($options[$permission] == 2) {
  11510. // the logic for bit 2 is inverted (cleared by default)
  11511. $protection += $options[$permission];
  11512. } else {
  11513. $protection -= $options[$permission];
  11514. }
  11515. }
  11516. }
  11517. return $protection;
  11518. }
  11519. /**
  11520. * Set document protection
  11521. * Remark: the protection against modification is for people who have the full Acrobat product.
  11522. * If you don't set any password, the document will open as usual. If you set a user password, the PDF viewer will ask for it before displaying the document. The master password, if different from the user one, can be used to get full access.
  11523. * Note: protecting a document requires to encrypt it, which increases the processing time a lot. This can cause a PHP time-out in some cases, especially if the document contains images or fonts.
  11524. * @param Array $permissions the set of permissions (specify the ones you want to block):<ul><li>print : Print the document;</li><li>modify : Modify the contents of the document by operations other than those controlled by 'fill-forms', 'extract' and 'assemble';</li><li>copy : Copy or otherwise extract text and graphics from the document;</li><li>annot-forms : Add or modify text annotations, fill in interactive form fields, and, if 'modify' is also set, create or modify interactive form fields (including signature fields);</li><li>fill-forms : Fill in existing interactive form fields (including signature fields), even if 'annot-forms' is not specified;</li><li>extract : Extract text and graphics (in support of accessibility to users with disabilities or for other purposes);</li><li>assemble : Assemble the document (insert, rotate, or delete pages and create bookmarks or thumbnail images), even if 'modify' is not set;</li><li>print-high : Print the document to a representation from which a faithful digital copy of the PDF content could be generated. When this is not set, printing is limited to a low-level representation of the appearance, possibly of degraded quality.</li><li>owner : (inverted logic - only for public-key) when set permits change of encryption and enables all other permissions.</li></ul>
  11525. * @param String $user_pass user password. Empty by default.
  11526. * @param String $owner_pass owner password. If not specified, a random value is used.
  11527. * @param int $mode encryption strength: 0 = RC4 40 bit; 1 = RC4 128 bit; 2 = AES 128 bit; 3 = AES 256 bit.
  11528. * @param String $pubkeys array of recipients containing public-key certificates ('c') and permissions ('p'). For example: array(array('c' => 'file://../tcpdf.crt', 'p' => array('print')))
  11529. * @access public
  11530. * @since 2.0.000 (2008-01-02)
  11531. * @author Nicola Asuni
  11532. */
  11533. 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) {
  11534. $this->encryptdata['protection'] = $this->getUserPermissionCode($permissions, $mode);
  11535. if (($pubkeys !== null) AND (is_array($pubkeys))) {
  11536. // public-key mode
  11537. $this->encryptdata['pubkeys'] = $pubkeys;
  11538. if ($mode == 0) {
  11539. // public-Key Security requires at least 128 bit
  11540. $mode = 1;
  11541. }
  11542. if (!function_exists('openssl_pkcs7_encrypt')) {
  11543. $this->Error('Public-Key Security requires openssl library.');
  11544. }
  11545. // Set Public-Key filter (availabe are: Entrust.PPKEF, Adobe.PPKLite, Adobe.PubSec)
  11546. $this->encryptdata['pubkey'] = true;
  11547. $this->encryptdata['Filter'] = 'Adobe.PubSec';
  11548. $this->encryptdata['StmF'] = 'DefaultCryptFilter';
  11549. $this->encryptdata['StrF'] = 'DefaultCryptFilter';
  11550. } else {
  11551. // standard mode (password mode)
  11552. $this->encryptdata['pubkey'] = false;
  11553. $this->encryptdata['Filter'] = 'Standard';
  11554. $this->encryptdata['StmF'] = 'StdCF';
  11555. $this->encryptdata['StrF'] = 'StdCF';
  11556. }
  11557. if ($mode > 1) { // AES
  11558. if (!extension_loaded('mcrypt')) {
  11559. $this->Error('AES encryption requires mcrypt library (http://www.php.net/manual/en/mcrypt.requirements.php).');
  11560. }
  11561. if (mcrypt_get_cipher_name(MCRYPT_RIJNDAEL_128) === false) {
  11562. $this->Error('AES encryption requires MCRYPT_RIJNDAEL_128 cypher.');
  11563. }
  11564. if (($mode == 3) AND !function_exists('hash')) {
  11565. // the Hash extension requires no external libraries and is enabled by default as of PHP 5.1.2.
  11566. $this->Error('AES 256 encryption requires HASH Message Digest Framework (http://www.php.net/manual/en/book.hash.php).');
  11567. }
  11568. }
  11569. if ($owner_pass === null) {
  11570. $owner_pass = md5($this->getRandomSeed());
  11571. }
  11572. $this->encryptdata['user_password'] = $user_pass;
  11573. $this->encryptdata['owner_password'] = $owner_pass;
  11574. $this->encryptdata['mode'] = $mode;
  11575. switch ($mode) {
  11576. case 0: { // RC4 40 bit
  11577. $this->encryptdata['V'] = 1;
  11578. $this->encryptdata['Length'] = 40;
  11579. $this->encryptdata['CF']['CFM'] = 'V2';
  11580. break;
  11581. }
  11582. case 1: { // RC4 128 bit
  11583. $this->encryptdata['V'] = 2;
  11584. $this->encryptdata['Length'] = 128;
  11585. $this->encryptdata['CF']['CFM'] = 'V2';
  11586. if ($this->encryptdata['pubkey']) {
  11587. $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s4';
  11588. $this->encryptdata['Recipients'] = array();
  11589. }
  11590. break;
  11591. }
  11592. case 2: { // AES 128 bit
  11593. $this->encryptdata['V'] = 4;
  11594. $this->encryptdata['Length'] = 128;
  11595. $this->encryptdata['CF']['CFM'] = 'AESV2';
  11596. $this->encryptdata['CF']['Length'] = 128;
  11597. if ($this->encryptdata['pubkey']) {
  11598. $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
  11599. $this->encryptdata['Recipients'] = array();
  11600. }
  11601. break;
  11602. }
  11603. case 3: { // AES 256 bit
  11604. $this->encryptdata['V'] = 5;
  11605. $this->encryptdata['Length'] = 256;
  11606. $this->encryptdata['CF']['CFM'] = 'AESV3';
  11607. $this->encryptdata['CF']['Length'] = 256;
  11608. if ($this->encryptdata['pubkey']) {
  11609. $this->encryptdata['SubFilter'] = 'adbe.pkcs7.s5';
  11610. $this->encryptdata['Recipients'] = array();
  11611. }
  11612. break;
  11613. }
  11614. }
  11615. $this->encrypted = true;
  11616. $this->encryptdata['fileid'] = $this->convertHexStringToString($this->file_id);
  11617. $this->_generateencryptionkey();
  11618. }
  11619. /**
  11620. * Convert hexadecimal string to string
  11621. * @param string $bs byte-string to convert
  11622. * @return String
  11623. * @access protected
  11624. * @since 5.0.005 (2010-05-12)
  11625. * @author Nicola Asuni
  11626. */
  11627. protected function convertHexStringToString($bs) {
  11628. $string = ''; // string to be returned
  11629. $bslenght = strlen($bs);
  11630. if (($bslenght % 2) != 0) {
  11631. // padding
  11632. $bs .= '0';
  11633. ++$bslenght;
  11634. }
  11635. for ($i = 0; $i < $bslenght; $i += 2) {
  11636. $string .= chr(hexdec($bs{$i}.$bs{($i + 1)}));
  11637. }
  11638. return $string;
  11639. }
  11640. /**
  11641. * Convert string to hexadecimal string (byte string)
  11642. * @param string $s string to convert
  11643. * @return byte string
  11644. * @access protected
  11645. * @since 5.0.010 (2010-05-17)
  11646. * @author Nicola Asuni
  11647. */
  11648. protected function convertStringToHexString($s) {
  11649. $bs = '';
  11650. $chars = preg_split('//', $s, -1, PREG_SPLIT_NO_EMPTY);
  11651. foreach ($chars as $c) {
  11652. $bs .= sprintf('%02s', dechex(ord($c)));
  11653. }
  11654. return $bs;
  11655. }
  11656. /**
  11657. * Convert encryption P value to a string of bytes, low-order byte first.
  11658. * @param string $protection 32bit encryption permission value (P value)
  11659. * @return String
  11660. * @access protected
  11661. * @since 5.0.005 (2010-05-12)
  11662. * @author Nicola Asuni
  11663. */
  11664. protected function getEncPermissionsString($protection) {
  11665. $binprot = sprintf('%032b', $protection);
  11666. $str = chr(bindec(substr($binprot, 24, 8)));
  11667. $str .= chr(bindec(substr($binprot, 16, 8)));
  11668. $str .= chr(bindec(substr($binprot, 8, 8)));
  11669. $str .= chr(bindec(substr($binprot, 0, 8)));
  11670. return $str;
  11671. }
  11672. // END OF ENCRYPTION FUNCTIONS -------------------------
  11673. // START TRANSFORMATIONS SECTION -----------------------
  11674. /**
  11675. * Starts a 2D tranformation saving current graphic state.
  11676. * This function must be called before scaling, mirroring, translation, rotation and skewing.
  11677. * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
  11678. * @access public
  11679. * @since 2.1.000 (2008-01-07)
  11680. * @see StartTransform(), StopTransform()
  11681. */
  11682. public function StartTransform() {
  11683. $this->_out('q');
  11684. if ($this->inxobj) {
  11685. // we are inside an XObject template
  11686. $this->xobjects[$this->xobjid]['transfmrk'][] = strlen($this->xobjects[$this->xobjid]['outdata']);
  11687. } else {
  11688. $this->transfmrk[$this->page][] = $this->pagelen[$this->page];
  11689. }
  11690. ++$this->transfmatrix_key;
  11691. $this->transfmatrix[$this->transfmatrix_key] = array();
  11692. }
  11693. /**
  11694. * Stops a 2D tranformation restoring previous graphic state.
  11695. * This function must be called after scaling, mirroring, translation, rotation and skewing.
  11696. * Use StartTransform() before, and StopTransform() after the transformations to restore the normal behavior.
  11697. * @access public
  11698. * @since 2.1.000 (2008-01-07)
  11699. * @see StartTransform(), StopTransform()
  11700. */
  11701. public function StopTransform() {
  11702. $this->_out('Q');
  11703. if (isset($this->transfmatrix[$this->transfmatrix_key])) {
  11704. array_pop($this->transfmatrix[$this->transfmatrix_key]);
  11705. --$this->transfmatrix_key;
  11706. }
  11707. if ($this->inxobj) {
  11708. // we are inside an XObject template
  11709. array_pop($this->xobjects[$this->xobjid]['transfmrk']);
  11710. } else {
  11711. array_pop($this->transfmrk[$this->page]);
  11712. }
  11713. }
  11714. /**
  11715. * Horizontal Scaling.
  11716. * @param float $s_x scaling factor for width as percent. 0 is not allowed.
  11717. * @param int $x abscissa of the scaling center. Default is current x position
  11718. * @param int $y ordinate of the scaling center. Default is current y position
  11719. * @access public
  11720. * @since 2.1.000 (2008-01-07)
  11721. * @see StartTransform(), StopTransform()
  11722. */
  11723. public function ScaleX($s_x, $x='', $y='') {
  11724. $this->Scale($s_x, 100, $x, $y);
  11725. }
  11726. /**
  11727. * Vertical Scaling.
  11728. * @param float $s_y scaling factor for height as percent. 0 is not allowed.
  11729. * @param int $x abscissa of the scaling center. Default is current x position
  11730. * @param int $y ordinate of the scaling center. Default is current y position
  11731. * @access public
  11732. * @since 2.1.000 (2008-01-07)
  11733. * @see StartTransform(), StopTransform()
  11734. */
  11735. public function ScaleY($s_y, $x='', $y='') {
  11736. $this->Scale(100, $s_y, $x, $y);
  11737. }
  11738. /**
  11739. * Vertical and horizontal proportional Scaling.
  11740. * @param float $s scaling factor for width and height as percent. 0 is not allowed.
  11741. * @param int $x abscissa of the scaling center. Default is current x position
  11742. * @param int $y ordinate of the scaling center. Default is current y position
  11743. * @access public
  11744. * @since 2.1.000 (2008-01-07)
  11745. * @see StartTransform(), StopTransform()
  11746. */
  11747. public function ScaleXY($s, $x='', $y='') {
  11748. $this->Scale($s, $s, $x, $y);
  11749. }
  11750. /**
  11751. * Vertical and horizontal non-proportional Scaling.
  11752. * @param float $s_x scaling factor for width as percent. 0 is not allowed.
  11753. * @param float $s_y scaling factor for height as percent. 0 is not allowed.
  11754. * @param int $x abscissa of the scaling center. Default is current x position
  11755. * @param int $y ordinate of the scaling center. Default is current y position
  11756. * @access public
  11757. * @since 2.1.000 (2008-01-07)
  11758. * @see StartTransform(), StopTransform()
  11759. */
  11760. public function Scale($s_x, $s_y, $x='', $y='') {
  11761. if ($x === '') {
  11762. $x = $this->x;
  11763. }
  11764. if ($y === '') {
  11765. $y = $this->y;
  11766. }
  11767. if (($s_x == 0) OR ($s_y == 0)) {
  11768. $this->Error('Please do not use values equal to zero for scaling');
  11769. }
  11770. $y = ($this->h - $y) * $this->k;
  11771. $x *= $this->k;
  11772. //calculate elements of transformation matrix
  11773. $s_x /= 100;
  11774. $s_y /= 100;
  11775. $tm = array();
  11776. $tm[0] = $s_x;
  11777. $tm[1] = 0;
  11778. $tm[2] = 0;
  11779. $tm[3] = $s_y;
  11780. $tm[4] = $x * (1 - $s_x);
  11781. $tm[5] = $y * (1 - $s_y);
  11782. //scale the coordinate system
  11783. $this->Transform($tm);
  11784. }
  11785. /**
  11786. * Horizontal Mirroring.
  11787. * @param int $x abscissa of the point. Default is current x position
  11788. * @access public
  11789. * @since 2.1.000 (2008-01-07)
  11790. * @see StartTransform(), StopTransform()
  11791. */
  11792. public function MirrorH($x='') {
  11793. $this->Scale(-100, 100, $x);
  11794. }
  11795. /**
  11796. * Verical Mirroring.
  11797. * @param int $y ordinate of the point. Default is current y position
  11798. * @access public
  11799. * @since 2.1.000 (2008-01-07)
  11800. * @see StartTransform(), StopTransform()
  11801. */
  11802. public function MirrorV($y='') {
  11803. $this->Scale(100, -100, '', $y);
  11804. }
  11805. /**
  11806. * Point reflection mirroring.
  11807. * @param int $x abscissa of the point. Default is current x position
  11808. * @param int $y ordinate of the point. Default is current y position
  11809. * @access public
  11810. * @since 2.1.000 (2008-01-07)
  11811. * @see StartTransform(), StopTransform()
  11812. */
  11813. public function MirrorP($x='',$y='') {
  11814. $this->Scale(-100, -100, $x, $y);
  11815. }
  11816. /**
  11817. * Reflection against a straight line through point (x, y) with the gradient angle (angle).
  11818. * @param float $angle gradient angle of the straight line. Default is 0 (horizontal line).
  11819. * @param int $x abscissa of the point. Default is current x position
  11820. * @param int $y ordinate of the point. Default is current y position
  11821. * @access public
  11822. * @since 2.1.000 (2008-01-07)
  11823. * @see StartTransform(), StopTransform()
  11824. */
  11825. public function MirrorL($angle=0, $x='',$y='') {
  11826. $this->Scale(-100, 100, $x, $y);
  11827. $this->Rotate(-2*($angle-90), $x, $y);
  11828. }
  11829. /**
  11830. * Translate graphic object horizontally.
  11831. * @param int $t_x movement to the right (or left for RTL)
  11832. * @access public
  11833. * @since 2.1.000 (2008-01-07)
  11834. * @see StartTransform(), StopTransform()
  11835. */
  11836. public function TranslateX($t_x) {
  11837. $this->Translate($t_x, 0);
  11838. }
  11839. /**
  11840. * Translate graphic object vertically.
  11841. * @param int $t_y movement to the bottom
  11842. * @access public
  11843. * @since 2.1.000 (2008-01-07)
  11844. * @see StartTransform(), StopTransform()
  11845. */
  11846. public function TranslateY($t_y) {
  11847. $this->Translate(0, $t_y);
  11848. }
  11849. /**
  11850. * Translate graphic object horizontally and vertically.
  11851. * @param int $t_x movement to the right
  11852. * @param int $t_y movement to the bottom
  11853. * @access public
  11854. * @since 2.1.000 (2008-01-07)
  11855. * @see StartTransform(), StopTransform()
  11856. */
  11857. public function Translate($t_x, $t_y) {
  11858. //calculate elements of transformation matrix
  11859. $tm = array();
  11860. $tm[0] = 1;
  11861. $tm[1] = 0;
  11862. $tm[2] = 0;
  11863. $tm[3] = 1;
  11864. $tm[4] = $t_x * $this->k;
  11865. $tm[5] = -$t_y * $this->k;
  11866. //translate the coordinate system
  11867. $this->Transform($tm);
  11868. }
  11869. /**
  11870. * Rotate object.
  11871. * @param float $angle angle in degrees for counter-clockwise rotation
  11872. * @param int $x abscissa of the rotation center. Default is current x position
  11873. * @param int $y ordinate of the rotation center. Default is current y position
  11874. * @access public
  11875. * @since 2.1.000 (2008-01-07)
  11876. * @see StartTransform(), StopTransform()
  11877. */
  11878. public function Rotate($angle, $x='', $y='') {
  11879. if ($x === '') {
  11880. $x = $this->x;
  11881. }
  11882. if ($y === '') {
  11883. $y = $this->y;
  11884. }
  11885. $y = ($this->h - $y) * $this->k;
  11886. $x *= $this->k;
  11887. //calculate elements of transformation matrix
  11888. $tm = array();
  11889. $tm[0] = cos(deg2rad($angle));
  11890. $tm[1] = sin(deg2rad($angle));
  11891. $tm[2] = -$tm[1];
  11892. $tm[3] = $tm[0];
  11893. $tm[4] = $x + ($tm[1] * $y) - ($tm[0] * $x);
  11894. $tm[5] = $y - ($tm[0] * $y) - ($tm[1] * $x);
  11895. //rotate the coordinate system around ($x,$y)
  11896. $this->Transform($tm);
  11897. }
  11898. /**
  11899. * Skew horizontally.
  11900. * @param float $angle_x angle in degrees between -90 (skew to the left) and 90 (skew to the right)
  11901. * @param int $x abscissa of the skewing center. default is current x position
  11902. * @param int $y ordinate of the skewing center. default is current y position
  11903. * @access public
  11904. * @since 2.1.000 (2008-01-07)
  11905. * @see StartTransform(), StopTransform()
  11906. */
  11907. public function SkewX($angle_x, $x='', $y='') {
  11908. $this->Skew($angle_x, 0, $x, $y);
  11909. }
  11910. /**
  11911. * Skew vertically.
  11912. * @param float $angle_y angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
  11913. * @param int $x abscissa of the skewing center. default is current x position
  11914. * @param int $y ordinate of the skewing center. default is current y position
  11915. * @access public
  11916. * @since 2.1.000 (2008-01-07)
  11917. * @see StartTransform(), StopTransform()
  11918. */
  11919. public function SkewY($angle_y, $x='', $y='') {
  11920. $this->Skew(0, $angle_y, $x, $y);
  11921. }
  11922. /**
  11923. * Skew.
  11924. * @param float $angle_x angle in degrees between -90 (skew to the left) and 90 (skew to the right)
  11925. * @param float $angle_y angle in degrees between -90 (skew to the bottom) and 90 (skew to the top)
  11926. * @param int $x abscissa of the skewing center. default is current x position
  11927. * @param int $y ordinate of the skewing center. default is current y position
  11928. * @access public
  11929. * @since 2.1.000 (2008-01-07)
  11930. * @see StartTransform(), StopTransform()
  11931. */
  11932. public function Skew($angle_x, $angle_y, $x='', $y='') {
  11933. if ($x === '') {
  11934. $x = $this->x;
  11935. }
  11936. if ($y === '') {
  11937. $y = $this->y;
  11938. }
  11939. if (($angle_x <= -90) OR ($angle_x >= 90) OR ($angle_y <= -90) OR ($angle_y >= 90)) {
  11940. $this->Error('Please use values between -90 and +90 degrees for Skewing.');
  11941. }
  11942. $x *= $this->k;
  11943. $y = ($this->h - $y) * $this->k;
  11944. //calculate elements of transformation matrix
  11945. $tm = array();
  11946. $tm[0] = 1;
  11947. $tm[1] = tan(deg2rad($angle_y));
  11948. $tm[2] = tan(deg2rad($angle_x));
  11949. $tm[3] = 1;
  11950. $tm[4] = -$tm[2] * $y;
  11951. $tm[5] = -$tm[1] * $x;
  11952. //skew the coordinate system
  11953. $this->Transform($tm);
  11954. }
  11955. /**
  11956. * Apply graphic transformations.
  11957. * @param array $tm transformation matrix
  11958. * @access protected
  11959. * @since 2.1.000 (2008-01-07)
  11960. * @see StartTransform(), StopTransform()
  11961. */
  11962. protected function Transform($tm) {
  11963. $this->_out(sprintf('%.3F %.3F %.3F %.3F %.3F %.3F cm', $tm[0], $tm[1], $tm[2], $tm[3], $tm[4], $tm[5]));
  11964. // add tranformation matrix
  11965. $this->transfmatrix[$this->transfmatrix_key][] = array('a' => $tm[0], 'b' => $tm[1], 'c' => $tm[2], 'd' => $tm[3], 'e' => $tm[4], 'f' => $tm[5]);
  11966. // update transformation mark
  11967. if ($this->inxobj) {
  11968. // we are inside an XObject template
  11969. if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
  11970. $key = key($this->xobjects[$this->xobjid]['transfmrk']);
  11971. $this->xobjects[$this->xobjid]['transfmrk'][$key] = strlen($this->xobjects[$this->xobjid]['outdata']);
  11972. }
  11973. } elseif (end($this->transfmrk[$this->page]) !== false) {
  11974. $key = key($this->transfmrk[$this->page]);
  11975. $this->transfmrk[$this->page][$key] = $this->pagelen[$this->page];
  11976. }
  11977. }
  11978. // END TRANSFORMATIONS SECTION -------------------------
  11979. // START GRAPHIC FUNCTIONS SECTION ---------------------
  11980. // The following section is based on the code provided by David Hernandez Sanz
  11981. /**
  11982. * Defines the line width. By default, the value equals 0.2 mm. The method can be called before the first page is created and the value is retained from page to page.
  11983. * @param float $width The width.
  11984. * @access public
  11985. * @since 1.0
  11986. * @see Line(), Rect(), Cell(), MultiCell()
  11987. */
  11988. public function SetLineWidth($width) {
  11989. //Set line width
  11990. $this->LineWidth = $width;
  11991. $this->linestyleWidth = sprintf('%.2F w', ($width * $this->k));
  11992. if ($this->page > 0) {
  11993. $this->_out($this->linestyleWidth);
  11994. }
  11995. }
  11996. /**
  11997. * Returns the current the line width.
  11998. * @return int Line width
  11999. * @access public
  12000. * @since 2.1.000 (2008-01-07)
  12001. * @see Line(), SetLineWidth()
  12002. */
  12003. public function GetLineWidth() {
  12004. return $this->LineWidth;
  12005. }
  12006. /**
  12007. * Set line style.
  12008. * @param array $style Line style. Array with keys among the following:
  12009. * <ul>
  12010. * <li>width (float): Width of the line in user units.</li>
  12011. * <li>cap (string): Type of cap to put on the line. Possible values are:
  12012. * butt, round, square. The difference between "square" and "butt" is that
  12013. * "square" projects a flat end past the end of the line.</li>
  12014. * <li>join (string): Type of join. Possible values are: miter, round,
  12015. * bevel.</li>
  12016. * <li>dash (mixed): Dash pattern. Is 0 (without dash) or string with
  12017. * series of length values, which are the lengths of the on and off dashes.
  12018. * For example: "2" represents 2 on, 2 off, 2 on, 2 off, ...; "2,1" is 2 on,
  12019. * 1 off, 2 on, 1 off, ...</li>
  12020. * <li>phase (integer): Modifier on the dash pattern which is used to shift
  12021. * the point at which the pattern starts.</li>
  12022. * <li>color (array): Draw color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K).</li>
  12023. * </ul>
  12024. * @param boolean $ret if true do not send the command.
  12025. * @return string the PDF command
  12026. * @access public
  12027. * @since 2.1.000 (2008-01-08)
  12028. */
  12029. public function SetLineStyle($style, $ret=false) {
  12030. $s = ''; // string to be returned
  12031. if (!is_array($style)) {
  12032. return;
  12033. }
  12034. extract($style);
  12035. if (isset($width)) {
  12036. $this->LineWidth = $width;
  12037. $this->linestyleWidth = sprintf('%.2F w', ($width * $this->k));
  12038. $s .= $this->linestyleWidth.' ';
  12039. }
  12040. if (isset($cap)) {
  12041. $ca = array('butt' => 0, 'round'=> 1, 'square' => 2);
  12042. if (isset($ca[$cap])) {
  12043. $this->linestyleCap = $ca[$cap].' J';
  12044. $s .= $this->linestyleCap.' ';
  12045. }
  12046. }
  12047. if (isset($join)) {
  12048. $ja = array('miter' => 0, 'round' => 1, 'bevel' => 2);
  12049. if (isset($ja[$join])) {
  12050. $this->linestyleJoin = $ja[$join].' j';
  12051. $s .= $this->linestyleJoin.' ';
  12052. }
  12053. }
  12054. if (isset($dash)) {
  12055. $dash_string = '';
  12056. if ($dash) {
  12057. if (preg_match('/^.+,/', $dash) > 0) {
  12058. $tab = explode(',', $dash);
  12059. } else {
  12060. $tab = array($dash);
  12061. }
  12062. $dash_string = '';
  12063. foreach ($tab as $i => $v) {
  12064. if ($i) {
  12065. $dash_string .= ' ';
  12066. }
  12067. $dash_string .= sprintf('%.2F', $v);
  12068. }
  12069. }
  12070. if (!isset($phase) OR !$dash) {
  12071. $phase = 0;
  12072. }
  12073. $this->linestyleDash = sprintf('[%s] %.2F d', $dash_string, $phase);
  12074. $s .= $this->linestyleDash.' ';
  12075. }
  12076. if (isset($color)) {
  12077. $s .= $this->SetDrawColorArray($color, true).' ';
  12078. }
  12079. if (!$ret) {
  12080. $this->_out($s);
  12081. }
  12082. return $s;
  12083. }
  12084. /**
  12085. * Begin a new subpath by moving the current point to coordinates (x, y), omitting any connecting line segment.
  12086. * @param float $x Abscissa of point.
  12087. * @param float $y Ordinate of point.
  12088. * @access protected
  12089. * @since 2.1.000 (2008-01-08)
  12090. */
  12091. protected function _outPoint($x, $y) {
  12092. $this->_out(sprintf('%.2F %.2F m', $x * $this->k, ($this->h - $y) * $this->k));
  12093. }
  12094. /**
  12095. * Append a straight line segment from the current point to the point (x, y).
  12096. * The new current point shall be (x, y).
  12097. * @param float $x Abscissa of end point.
  12098. * @param float $y Ordinate of end point.
  12099. * @access protected
  12100. * @since 2.1.000 (2008-01-08)
  12101. */
  12102. protected function _outLine($x, $y) {
  12103. $this->_out(sprintf('%.2F %.2F l', $x * $this->k, ($this->h - $y) * $this->k));
  12104. }
  12105. /**
  12106. * Append a rectangle to the current path as a complete subpath, with lower-left corner (x, y) and dimensions widthand height in user space.
  12107. * @param float $x Abscissa of upper-left corner (or upper-right corner for RTL language).
  12108. * @param float $y Ordinate of upper-left corner (or upper-right corner for RTL language).
  12109. * @param float $w Width.
  12110. * @param float $h Height.
  12111. * @param string $op options
  12112. * @access protected
  12113. * @since 2.1.000 (2008-01-08)
  12114. */
  12115. protected function _outRect($x, $y, $w, $h, $op) {
  12116. $this->_out(sprintf('%.2F %.2F %.2F %.2F re %s', $x * $this->k, ($this->h - $y) * $this->k, $w * $this->k, -$h * $this->k, $op));
  12117. }
  12118. /**
  12119. * Append a cubic Bézier curve to the current path. The curve shall extend from the current point to the point (x3, y3), using (x1, y1) and (x2, y2) as the Bézier control points.
  12120. * The new current point shall be (x3, y3).
  12121. * @param float $x1 Abscissa of control point 1.
  12122. * @param float $y1 Ordinate of control point 1.
  12123. * @param float $x2 Abscissa of control point 2.
  12124. * @param float $y2 Ordinate of control point 2.
  12125. * @param float $x3 Abscissa of end point.
  12126. * @param float $y3 Ordinate of end point.
  12127. * @access protected
  12128. * @since 2.1.000 (2008-01-08)
  12129. */
  12130. protected function _outCurve($x1, $y1, $x2, $y2, $x3, $y3) {
  12131. $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));
  12132. }
  12133. /**
  12134. * Append a cubic Bézier curve to the current path. The curve shall extend from the current point to the point (x3, y3), using the current point and (x2, y2) as the Bézier control points.
  12135. * The new current point shall be (x3, y3).
  12136. * @param float $x2 Abscissa of control point 2.
  12137. * @param float $y2 Ordinate of control point 2.
  12138. * @param float $x3 Abscissa of end point.
  12139. * @param float $y3 Ordinate of end point.
  12140. * @access protected
  12141. * @since 4.9.019 (2010-04-26)
  12142. */
  12143. protected function _outCurveV($x2, $y2, $x3, $y3) {
  12144. $this->_out(sprintf('%.2F %.2F %.2F %.2F v', $x2 * $this->k, ($this->h - $y2) * $this->k, $x3 * $this->k, ($this->h - $y3) * $this->k));
  12145. }
  12146. /**
  12147. * Append a cubic Bézier curve to the current path. The curve shall extend from the current point to the point (x3, y3), using (x1, y1) and (x3, y3) as the Bézier control points.
  12148. * The new current point shall be (x3, y3).
  12149. * @param float $x1 Abscissa of control point 1.
  12150. * @param float $y1 Ordinate of control point 1.
  12151. * @param float $x2 Abscissa of control point 2.
  12152. * @param float $y2 Ordinate of control point 2.
  12153. * @param float $x3 Abscissa of end point.
  12154. * @param float $y3 Ordinate of end point.
  12155. * @access protected
  12156. * @since 2.1.000 (2008-01-08)
  12157. */
  12158. protected function _outCurveY($x1, $y1, $x3, $y3) {
  12159. $this->_out(sprintf('%.2F %.2F %.2F %.2F y', $x1 * $this->k, ($this->h - $y1) * $this->k, $x3 * $this->k, ($this->h - $y3) * $this->k));
  12160. }
  12161. /**
  12162. * Draws a line between two points.
  12163. * @param float $x1 Abscissa of first point.
  12164. * @param float $y1 Ordinate of first point.
  12165. * @param float $x2 Abscissa of second point.
  12166. * @param float $y2 Ordinate of second point.
  12167. * @param array $style Line style. Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty array).
  12168. * @access public
  12169. * @since 1.0
  12170. * @see SetLineWidth(), SetDrawColor(), SetLineStyle()
  12171. */
  12172. public function Line($x1, $y1, $x2, $y2, $style=array()) {
  12173. if (is_array($style)) {
  12174. $this->SetLineStyle($style);
  12175. }
  12176. $this->_outPoint($x1, $y1);
  12177. $this->_outLine($x2, $y2);
  12178. $this->_out('S');
  12179. }
  12180. /**
  12181. * Draws a rectangle.
  12182. * @param float $x Abscissa of upper-left corner (or upper-right corner for RTL language).
  12183. * @param float $y Ordinate of upper-left corner (or upper-right corner for RTL language).
  12184. * @param float $w Width.
  12185. * @param float $h Height.
  12186. * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
  12187. * @param array $border_style Border style of rectangle. Array with keys among the following:
  12188. * <ul>
  12189. * <li>all: Line style of all borders. Array like for {@link SetLineStyle SetLineStyle}.</li>
  12190. * <li>L, T, R, B or combinations: Line style of left, top, right or bottom border. Array like for {@link SetLineStyle SetLineStyle}.</li>
  12191. * </ul>
  12192. * If a key is not present or is null, not draws the border. Default value: default line style (empty array).
  12193. * @param array $border_style Border style of rectangle. Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty array).
  12194. * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array).
  12195. * @access public
  12196. * @since 1.0
  12197. * @see SetLineStyle()
  12198. */
  12199. public function Rect($x, $y, $w, $h, $style='', $border_style=array(), $fill_color=array()) {
  12200. if (!(false === strpos($style, 'F')) AND !empty($fill_color)) {
  12201. $this->SetFillColorArray($fill_color);
  12202. }
  12203. $op = $this->getPathPaintOperator($style);
  12204. if ((!$border_style) OR (isset($border_style['all']))) {
  12205. if (isset($border_style['all']) AND $border_style['all']) {
  12206. $this->SetLineStyle($border_style['all']);
  12207. $border_style = array();
  12208. }
  12209. }
  12210. $this->_outRect($x, $y, $w, $h, $op);
  12211. if ($border_style) {
  12212. $border_style2 = array();
  12213. foreach ($border_style as $line => $value) {
  12214. $length = strlen($line);
  12215. for ($i = 0; $i < $length; ++$i) {
  12216. $border_style2[$line[$i]] = $value;
  12217. }
  12218. }
  12219. $border_style = $border_style2;
  12220. if (isset($border_style['L']) AND $border_style['L']) {
  12221. $this->Line($x, $y, $x, $y + $h, $border_style['L']);
  12222. }
  12223. if (isset($border_style['T']) AND $border_style['T']) {
  12224. $this->Line($x, $y, $x + $w, $y, $border_style['T']);
  12225. }
  12226. if (isset($border_style['R']) AND $border_style['R']) {
  12227. $this->Line($x + $w, $y, $x + $w, $y + $h, $border_style['R']);
  12228. }
  12229. if (isset($border_style['B']) AND $border_style['B']) {
  12230. $this->Line($x, $y + $h, $x + $w, $y + $h, $border_style['B']);
  12231. }
  12232. }
  12233. }
  12234. /**
  12235. * Draws a Bezier curve.
  12236. * The Bezier curve is a tangent to the line between the control points at
  12237. * either end of the curve.
  12238. * @param float $x0 Abscissa of start point.
  12239. * @param float $y0 Ordinate of start point.
  12240. * @param float $x1 Abscissa of control point 1.
  12241. * @param float $y1 Ordinate of control point 1.
  12242. * @param float $x2 Abscissa of control point 2.
  12243. * @param float $y2 Ordinate of control point 2.
  12244. * @param float $x3 Abscissa of end point.
  12245. * @param float $y3 Ordinate of end point.
  12246. * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
  12247. * @param array $line_style Line style of curve. Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty array).
  12248. * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array).
  12249. * @access public
  12250. * @see SetLineStyle()
  12251. * @since 2.1.000 (2008-01-08)
  12252. */
  12253. public function Curve($x0, $y0, $x1, $y1, $x2, $y2, $x3, $y3, $style='', $line_style=array(), $fill_color=array()) {
  12254. if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
  12255. $this->SetFillColorArray($fill_color);
  12256. }
  12257. $op = $this->getPathPaintOperator($style);
  12258. if ($line_style) {
  12259. $this->SetLineStyle($line_style);
  12260. }
  12261. $this->_outPoint($x0, $y0);
  12262. $this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
  12263. $this->_out($op);
  12264. }
  12265. /**
  12266. * Draws a poly-Bezier curve.
  12267. * Each Bezier curve segment is a tangent to the line between the control points at
  12268. * either end of the curve.
  12269. * @param float $x0 Abscissa of start point.
  12270. * @param float $y0 Ordinate of start point.
  12271. * @param float $segments An array of bezier descriptions. Format: array(x1, y1, x2, y2, x3, y3).
  12272. * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
  12273. * @param array $line_style Line style of curve. Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty array).
  12274. * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array).
  12275. * @access public
  12276. * @see SetLineStyle()
  12277. * @since 3.0008 (2008-05-12)
  12278. */
  12279. public function Polycurve($x0, $y0, $segments, $style='', $line_style=array(), $fill_color=array()) {
  12280. if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
  12281. $this->SetFillColorArray($fill_color);
  12282. }
  12283. $op = $this->getPathPaintOperator($style);
  12284. if ($op == 'f') {
  12285. $line_style = array();
  12286. }
  12287. if ($line_style) {
  12288. $this->SetLineStyle($line_style);
  12289. }
  12290. $this->_outPoint($x0, $y0);
  12291. foreach ($segments as $segment) {
  12292. list($x1, $y1, $x2, $y2, $x3, $y3) = $segment;
  12293. $this->_outCurve($x1, $y1, $x2, $y2, $x3, $y3);
  12294. }
  12295. $this->_out($op);
  12296. }
  12297. /**
  12298. * Draws an ellipse.
  12299. * An ellipse is formed from n Bezier curves.
  12300. * @param float $x0 Abscissa of center point.
  12301. * @param float $y0 Ordinate of center point.
  12302. * @param float $rx Horizontal radius.
  12303. * @param float $ry Vertical radius (if ry = 0 then is a circle, see {@link Circle Circle}). Default value: 0.
  12304. * @param float $angle: Angle oriented (anti-clockwise). Default value: 0.
  12305. * @param float $astart: Angle start of draw line. Default value: 0.
  12306. * @param float $afinish: Angle finish of draw line. Default value: 360.
  12307. * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
  12308. * @param array $line_style Line style of ellipse. Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty array).
  12309. * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array).
  12310. * @param integer $nc Number of curves used to draw a 90 degrees portion of ellipse.
  12311. * @author Nicola Asuni
  12312. * @access public
  12313. * @since 2.1.000 (2008-01-08)
  12314. */
  12315. public function Ellipse($x0, $y0, $rx, $ry='', $angle=0, $astart=0, $afinish=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
  12316. if ($this->empty_string($ry) OR ($ry == 0)) {
  12317. $ry = $rx;
  12318. }
  12319. if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
  12320. $this->SetFillColorArray($fill_color);
  12321. }
  12322. $op = $this->getPathPaintOperator($style);
  12323. if ($op == 'f') {
  12324. $line_style = array();
  12325. }
  12326. if ($line_style) {
  12327. $this->SetLineStyle($line_style);
  12328. }
  12329. $this->_outellipticalarc($x0, $y0, $rx, $ry, $angle, $astart, $afinish, false, $nc);
  12330. $this->_out($op);
  12331. }
  12332. /**
  12333. * Append an elliptical arc to the current path.
  12334. * An ellipse is formed from n Bezier curves.
  12335. * @param float $xc Abscissa of center point.
  12336. * @param float $yc Ordinate of center point.
  12337. * @param float $rx Horizontal radius.
  12338. * @param float $ry Vertical radius (if ry = 0 then is a circle, see {@link Circle Circle}). Default value: 0.
  12339. * @param float $xang: Angle between the X-axis and the major axis of the ellipse. Default value: 0.
  12340. * @param float $angs: Angle start of draw line. Default value: 0.
  12341. * @param float $angf: Angle finish of draw line. Default value: 360.
  12342. * @param boolean $pie if true do not mark the border point (used to draw pie sectors).
  12343. * @param integer $nc Number of curves used to draw a 90 degrees portion of ellipse.
  12344. * @author Nicola Asuni
  12345. * @access protected
  12346. * @since 4.9.019 (2010-04-26)
  12347. */
  12348. protected function _outellipticalarc($xc, $yc, $rx, $ry, $xang=0, $angs=0, $angf=360, $pie=false, $nc=2) {
  12349. $k = $this->k;
  12350. if ($nc < 2) {
  12351. $nc = 2;
  12352. }
  12353. if ($pie) {
  12354. // center of the arc
  12355. $this->_outPoint($xc, $yc);
  12356. }
  12357. $xang = deg2rad((float) $xang);
  12358. $angs = deg2rad((float) $angs);
  12359. $angf = deg2rad((float) $angf);
  12360. $as = atan2((sin($angs) / $ry), (cos($angs) / $rx));
  12361. $af = atan2((sin($angf) / $ry), (cos($angf) / $rx));
  12362. if ($as < 0) {
  12363. $as += (2 * M_PI);
  12364. }
  12365. if ($af < 0) {
  12366. $af += (2 * M_PI);
  12367. }
  12368. if ($as > $af) {
  12369. // reverse rotation go clockwise
  12370. $as -= (2 * M_PI);
  12371. }
  12372. $total_angle = ($af - $as);
  12373. if ($nc < 2) {
  12374. $nc = 2;
  12375. }
  12376. // total arcs to draw
  12377. $nc *= (2 * abs($total_angle) / M_PI);
  12378. $nc = round($nc) + 1;
  12379. // angle of each arc
  12380. $arcang = $total_angle / $nc;
  12381. // center point in PDF coordiantes
  12382. $x0 = $xc;
  12383. $y0 = ($this->h - $yc);
  12384. // starting angle
  12385. $ang = $as;
  12386. $alpha = sin($arcang) * ((sqrt(4 + (3 * pow(tan(($arcang) / 2), 2))) - 1) / 3);
  12387. $cos_xang = cos($xang);
  12388. $sin_xang = sin($xang);
  12389. $cos_ang = cos($ang);
  12390. $sin_ang = sin($ang);
  12391. // first arc point
  12392. $px1 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
  12393. $py1 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
  12394. // first Bezier control point
  12395. $qx1 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
  12396. $qy1 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
  12397. if ($pie) {
  12398. $this->_outLine($px1, $this->h - $py1);
  12399. } else {
  12400. $this->_outPoint($px1, $this->h - $py1);
  12401. }
  12402. // draw arcs
  12403. for ($i = 1; $i <= $nc; ++$i) {
  12404. // starting angle
  12405. $ang = $as + ($i * $arcang);
  12406. $cos_xang = cos($xang);
  12407. $sin_xang = sin($xang);
  12408. $cos_ang = cos($ang);
  12409. $sin_ang = sin($ang);
  12410. // second arc point
  12411. $px2 = $x0 + ($rx * $cos_xang * $cos_ang) - ($ry * $sin_xang * $sin_ang);
  12412. $py2 = $y0 + ($rx * $sin_xang * $cos_ang) + ($ry * $cos_xang * $sin_ang);
  12413. // second Bezier control point
  12414. $qx2 = ($alpha * ((-$rx * $cos_xang * $sin_ang) - ($ry * $sin_xang * $cos_ang)));
  12415. $qy2 = ($alpha * ((-$rx * $sin_xang * $sin_ang) + ($ry * $cos_xang * $cos_ang)));
  12416. // draw arc
  12417. $this->_outCurve(($px1 + $qx1), ($this->h - ($py1 + $qy1)), ($px2 - $qx2), ($this->h - ($py2 - $qy2)), $px2, ($this->h - $py2));
  12418. // move to next point
  12419. $px1 = $px2;
  12420. $py1 = $py2;
  12421. $qx1 = $qx2;
  12422. $qy1 = $qy2;
  12423. }
  12424. if ($pie) {
  12425. $this->_outLine($xc, $yc);
  12426. }
  12427. }
  12428. /**
  12429. * Draws a circle.
  12430. * A circle is formed from n Bezier curves.
  12431. * @param float $x0 Abscissa of center point.
  12432. * @param float $y0 Ordinate of center point.
  12433. * @param float $r Radius.
  12434. * @param float $angstr: Angle start of draw line. Default value: 0.
  12435. * @param float $angend: Angle finish of draw line. Default value: 360.
  12436. * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
  12437. * @param array $line_style Line style of circle. Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty array).
  12438. * @param array $fill_color Fill color. Format: array(red, green, blue). Default value: default color (empty array).
  12439. * @param integer $nc Number of curves used to draw a 90 degrees portion of circle.
  12440. * @access public
  12441. * @since 2.1.000 (2008-01-08)
  12442. */
  12443. public function Circle($x0, $y0, $r, $angstr=0, $angend=360, $style='', $line_style=array(), $fill_color=array(), $nc=2) {
  12444. $this->Ellipse($x0, $y0, $r, $r, 0, $angstr, $angend, $style, $line_style, $fill_color, $nc);
  12445. }
  12446. /**
  12447. * Draws a polygonal line
  12448. * @param array $p Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
  12449. * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
  12450. * @param array $line_style Line style of polygon. Array with keys among the following:
  12451. * <ul>
  12452. * <li>all: Line style of all lines. Array like for {@link SetLineStyle SetLineStyle}.</li>
  12453. * <li>0 to ($np - 1): Line style of each line. Array like for {@link SetLineStyle SetLineStyle}.</li>
  12454. * </ul>
  12455. * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
  12456. * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array).
  12457. * @param boolean $closed if true the polygon is closes, otherwise will remain open
  12458. * @access public
  12459. * @since 4.8.003 (2009-09-15)
  12460. */
  12461. public function PolyLine($p, $style='', $line_style=array(), $fill_color=array()) {
  12462. $this->Polygon($p, $style, $line_style, $fill_color, false);
  12463. }
  12464. /**
  12465. * Draws a polygon.
  12466. * @param array $p Points 0 to ($np - 1). Array with values (x0, y0, x1, y1,..., x(np-1), y(np - 1))
  12467. * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
  12468. * @param array $line_style Line style of polygon. Array with keys among the following:
  12469. * <ul>
  12470. * <li>all: Line style of all lines. Array like for {@link SetLineStyle SetLineStyle}.</li>
  12471. * <li>0 to ($np - 1): Line style of each line. Array like for {@link SetLineStyle SetLineStyle}.</li>
  12472. * </ul>
  12473. * If a key is not present or is null, not draws the line. Default value is default line style (empty array).
  12474. * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array).
  12475. * @param boolean $closed if true the polygon is closes, otherwise will remain open
  12476. * @access public
  12477. * @since 2.1.000 (2008-01-08)
  12478. */
  12479. public function Polygon($p, $style='', $line_style=array(), $fill_color=array(), $closed=true) {
  12480. $nc = count($p); // number of coordinates
  12481. $np = $nc / 2; // number of points
  12482. if ($closed) {
  12483. // close polygon by adding the first 2 points at the end (one line)
  12484. for ($i = 0; $i < 4; ++$i) {
  12485. $p[$nc + $i] = $p[$i];
  12486. }
  12487. // copy style for the last added line
  12488. if (isset($line_style[0])) {
  12489. $line_style[$np] = $line_style[0];
  12490. }
  12491. $nc += 4;
  12492. }
  12493. if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
  12494. $this->SetFillColorArray($fill_color);
  12495. }
  12496. $op = $this->getPathPaintOperator($style);
  12497. if ($op == 'f') {
  12498. $line_style = array();
  12499. }
  12500. $draw = true;
  12501. if ($line_style) {
  12502. if (isset($line_style['all'])) {
  12503. $this->SetLineStyle($line_style['all']);
  12504. } else {
  12505. $draw = false;
  12506. if ($op == 'B') {
  12507. // draw fill
  12508. $op = 'f';
  12509. $this->_outPoint($p[0], $p[1]);
  12510. for ($i = 2; $i < $nc; $i = $i + 2) {
  12511. $this->_outLine($p[$i], $p[$i + 1]);
  12512. }
  12513. $this->_out($op);
  12514. }
  12515. // draw outline
  12516. $this->_outPoint($p[0], $p[1]);
  12517. for ($i = 2; $i < $nc; $i = $i + 2) {
  12518. $line_num = ($i / 2) - 1;
  12519. if (isset($line_style[$line_num])) {
  12520. if ($line_style[$line_num] != 0) {
  12521. if (is_array($line_style[$line_num])) {
  12522. $this->_out('S');
  12523. $this->SetLineStyle($line_style[$line_num]);
  12524. $this->_outPoint($p[$i - 2], $p[$i - 1]);
  12525. $this->_outLine($p[$i], $p[$i + 1]);
  12526. $this->_out('S');
  12527. $this->_outPoint($p[$i], $p[$i + 1]);
  12528. } else {
  12529. $this->_outLine($p[$i], $p[$i + 1]);
  12530. }
  12531. }
  12532. } else {
  12533. $this->_outLine($p[$i], $p[$i + 1]);
  12534. }
  12535. }
  12536. $this->_out($op);
  12537. }
  12538. }
  12539. if ($draw) {
  12540. $this->_outPoint($p[0], $p[1]);
  12541. for ($i = 2; $i < $nc; $i = $i + 2) {
  12542. $this->_outLine($p[$i], $p[$i + 1]);
  12543. }
  12544. $this->_out($op);
  12545. }
  12546. }
  12547. /**
  12548. * Draws a regular polygon.
  12549. * @param float $x0 Abscissa of center point.
  12550. * @param float $y0 Ordinate of center point.
  12551. * @param float $r: Radius of inscribed circle.
  12552. * @param integer $ns Number of sides.
  12553. * @param float $angle Angle oriented (anti-clockwise). Default value: 0.
  12554. * @param boolean $draw_circle Draw inscribed circle or not. Default value: false.
  12555. * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
  12556. * @param array $line_style Line style of polygon sides. Array with keys among the following:
  12557. * <ul>
  12558. * <li>all: Line style of all sides. Array like for {@link SetLineStyle SetLineStyle}.</li>
  12559. * <li>0 to ($ns - 1): Line style of each side. Array like for {@link SetLineStyle SetLineStyle}.</li>
  12560. * </ul>
  12561. * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
  12562. * @param array $fill_color Fill color. Format: array(red, green, blue). Default value: default color (empty array).
  12563. * @param string $circle_style Style of rendering of inscribed circle (if draws). Possible values are:
  12564. * <ul>
  12565. * <li>D or empty string: Draw (default).</li>
  12566. * <li>F: Fill.</li>
  12567. * <li>DF or FD: Draw and fill.</li>
  12568. * <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
  12569. * <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
  12570. * </ul>
  12571. * @param array $circle_outLine_style Line style of inscribed circle (if draws). Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty array).
  12572. * @param array $circle_fill_color Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
  12573. * @access public
  12574. * @since 2.1.000 (2008-01-08)
  12575. */
  12576. 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()) {
  12577. if (3 > $ns) {
  12578. $ns = 3;
  12579. }
  12580. if ($draw_circle) {
  12581. $this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
  12582. }
  12583. $p = array();
  12584. for ($i = 0; $i < $ns; ++$i) {
  12585. $a = $angle + ($i * 360 / $ns);
  12586. $a_rad = deg2rad((float) $a);
  12587. $p[] = $x0 + ($r * sin($a_rad));
  12588. $p[] = $y0 + ($r * cos($a_rad));
  12589. }
  12590. $this->Polygon($p, $style, $line_style, $fill_color);
  12591. }
  12592. /**
  12593. * Draws a star polygon
  12594. * @param float $x0 Abscissa of center point.
  12595. * @param float $y0 Ordinate of center point.
  12596. * @param float $r Radius of inscribed circle.
  12597. * @param integer $nv Number of vertices.
  12598. * @param integer $ng Number of gap (if ($ng % $nv = 1) then is a regular polygon).
  12599. * @param float $angle: Angle oriented (anti-clockwise). Default value: 0.
  12600. * @param boolean $draw_circle: Draw inscribed circle or not. Default value is false.
  12601. * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
  12602. * @param array $line_style Line style of polygon sides. Array with keys among the following:
  12603. * <ul>
  12604. * <li>all: Line style of all sides. Array like for
  12605. * {@link SetLineStyle SetLineStyle}.</li>
  12606. * <li>0 to (n - 1): Line style of each side. Array like for {@link SetLineStyle SetLineStyle}.</li>
  12607. * </ul>
  12608. * If a key is not present or is null, not draws the side. Default value is default line style (empty array).
  12609. * @param array $fill_color Fill color. Format: array(red, green, blue). Default value: default color (empty array).
  12610. * @param string $circle_style Style of rendering of inscribed circle (if draws). Possible values are:
  12611. * <ul>
  12612. * <li>D or empty string: Draw (default).</li>
  12613. * <li>F: Fill.</li>
  12614. * <li>DF or FD: Draw and fill.</li>
  12615. * <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
  12616. * <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
  12617. * </ul>
  12618. * @param array $circle_outLine_style Line style of inscribed circle (if draws). Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty array).
  12619. * @param array $circle_fill_color Fill color of inscribed circle (if draws). Format: array(red, green, blue). Default value: default color (empty array).
  12620. * @access public
  12621. * @since 2.1.000 (2008-01-08)
  12622. */
  12623. 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()) {
  12624. if ($nv < 2) {
  12625. $nv = 2;
  12626. }
  12627. if ($draw_circle) {
  12628. $this->Circle($x0, $y0, $r, 0, 360, $circle_style, $circle_outLine_style, $circle_fill_color);
  12629. }
  12630. $p2 = array();
  12631. $visited = array();
  12632. for ($i = 0; $i < $nv; ++$i) {
  12633. $a = $angle + ($i * 360 / $nv);
  12634. $a_rad = deg2rad((float) $a);
  12635. $p2[] = $x0 + ($r * sin($a_rad));
  12636. $p2[] = $y0 + ($r * cos($a_rad));
  12637. $visited[] = false;
  12638. }
  12639. $p = array();
  12640. $i = 0;
  12641. do {
  12642. $p[] = $p2[$i * 2];
  12643. $p[] = $p2[($i * 2) + 1];
  12644. $visited[$i] = true;
  12645. $i += $ng;
  12646. $i %= $nv;
  12647. } while (!$visited[$i]);
  12648. $this->Polygon($p, $style, $line_style, $fill_color);
  12649. }
  12650. /**
  12651. * Draws a rounded rectangle.
  12652. * @param float $x Abscissa of upper-left corner.
  12653. * @param float $y Ordinate of upper-left corner.
  12654. * @param float $w Width.
  12655. * @param float $h Height.
  12656. * @param float $r the radius of the circle used to round off the corners of the rectangle.
  12657. * @param string $round_corner Draws rounded corner or not. String with a 0 (not rounded i-corner) or 1 (rounded i-corner) in i-position. Positions are, in order and begin to 0: top left, top right, bottom right and bottom left. Default value: all rounded corner ("1111").
  12658. * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
  12659. * @param array $border_style Border style of rectangle. Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty array).
  12660. * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array).
  12661. * @access public
  12662. * @since 2.1.000 (2008-01-08)
  12663. */
  12664. public function RoundedRect($x, $y, $w, $h, $r, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
  12665. $this->RoundedRectXY($x, $y, $w, $h, $r, $r, $round_corner, $style, $border_style, $fill_color);
  12666. }
  12667. /**
  12668. * Draws a rounded rectangle.
  12669. * @param float $x Abscissa of upper-left corner.
  12670. * @param float $y Ordinate of upper-left corner.
  12671. * @param float $w Width.
  12672. * @param float $h Height.
  12673. * @param float $rx the x-axis radius of the ellipse used to round off the corners of the rectangle.
  12674. * @param float $ry the y-axis radius of the ellipse used to round off the corners of the rectangle.
  12675. * @param string $round_corner Draws rounded corner or not. String with a 0 (not rounded i-corner) or 1 (rounded i-corner) in i-position. Positions are, in order and begin to 0: top left, top right, bottom right and bottom left. Default value: all rounded corner ("1111").
  12676. * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
  12677. * @param array $border_style Border style of rectangle. Array like for {@link SetLineStyle SetLineStyle}. Default value: default line style (empty array).
  12678. * @param array $fill_color Fill color. Format: array(GREY) or array(R,G,B) or array(C,M,Y,K). Default value: default color (empty array).
  12679. * @access public
  12680. * @since 4.9.019 (2010-04-22)
  12681. */
  12682. public function RoundedRectXY($x, $y, $w, $h, $rx, $ry, $round_corner='1111', $style='', $border_style=array(), $fill_color=array()) {
  12683. if (($round_corner == '0000') OR (($rx == $ry) AND ($rx == 0))) {
  12684. // Not rounded
  12685. $this->Rect($x, $y, $w, $h, $style, $border_style, $fill_color);
  12686. return;
  12687. }
  12688. // Rounded
  12689. if (!(false === strpos($style, 'F')) AND isset($fill_color)) {
  12690. $this->SetFillColorArray($fill_color);
  12691. }
  12692. $op = $this->getPathPaintOperator($style);
  12693. if ($op == 'f') {
  12694. $border_style = array();
  12695. }
  12696. if ($border_style) {
  12697. $this->SetLineStyle($border_style);
  12698. }
  12699. $MyArc = 4 / 3 * (sqrt(2) - 1);
  12700. $this->_outPoint($x + $rx, $y);
  12701. $xc = $x + $w - $rx;
  12702. $yc = $y + $ry;
  12703. $this->_outLine($xc, $y);
  12704. if ($round_corner[0]) {
  12705. $this->_outCurve($xc + ($rx * $MyArc), $yc - $ry, $xc + $rx, $yc - ($ry * $MyArc), $xc + $rx, $yc);
  12706. } else {
  12707. $this->_outLine($x + $w, $y);
  12708. }
  12709. $xc = $x + $w - $rx;
  12710. $yc = $y + $h - $ry;
  12711. $this->_outLine($x + $w, $yc);
  12712. if ($round_corner[1]) {
  12713. $this->_outCurve($xc + $rx, $yc + ($ry * $MyArc), $xc + ($rx * $MyArc), $yc + $ry, $xc, $yc + $ry);
  12714. } else {
  12715. $this->_outLine($x + $w, $y + $h);
  12716. }
  12717. $xc = $x + $rx;
  12718. $yc = $y + $h - $ry;
  12719. $this->_outLine($xc, $y + $h);
  12720. if ($round_corner[2]) {
  12721. $this->_outCurve($xc - ($rx * $MyArc), $yc + $ry, $xc - $rx, $yc + ($ry * $MyArc), $xc - $rx, $yc);
  12722. } else {
  12723. $this->_outLine($x, $y + $h);
  12724. }
  12725. $xc = $x + $rx;
  12726. $yc = $y + $ry;
  12727. $this->_outLine($x, $yc);
  12728. if ($round_corner[3]) {
  12729. $this->_outCurve($xc - $rx, $yc - ($ry * $MyArc), $xc - ($rx * $MyArc), $yc - $ry, $xc, $yc - $ry);
  12730. } else {
  12731. $this->_outLine($x, $y);
  12732. $this->_outLine($x + $rx, $y);
  12733. }
  12734. $this->_out($op);
  12735. }
  12736. /**
  12737. * Draws a grahic arrow.
  12738. * @param float $x0 Abscissa of first point.
  12739. * @param float $y0 Ordinate of first point.
  12740. * @param float $x0 Abscissa of second point.
  12741. * @param float $y1 Ordinate of second point.
  12742. * @param int $head_style (0 = draw only arrowhead arms, 1 = draw closed arrowhead, but no fill, 2 = closed and filled arrowhead, 3 = filled arrowhead)
  12743. * @param float $arm_size length of arrowhead arms
  12744. * @param int $arm_angle angle between an arm and the shaft
  12745. * @author Piotr Galecki, Nicola Asuni, Andy Meier
  12746. * @since 4.6.018 (2009-07-10)
  12747. */
  12748. public function Arrow($x0, $y0, $x1, $y1, $head_style=0, $arm_size=5, $arm_angle=15) {
  12749. // getting arrow direction angle
  12750. // 0 deg angle is when both arms go along X axis. angle grows clockwise.
  12751. $dir_angle = atan2(($y0 - $y1), ($x0 - $x1));
  12752. if ($dir_angle < 0) {
  12753. $dir_angle += (2 * M_PI);
  12754. }
  12755. $arm_angle = deg2rad($arm_angle);
  12756. $sx1 = $x1;
  12757. $sy1 = $y1;
  12758. if ($head_style > 0) {
  12759. // calculate the stopping point for the arrow shaft
  12760. $sx1 = $x1 + (($arm_size - $this->LineWidth) * cos($dir_angle));
  12761. $sy1 = $y1 + (($arm_size - $this->LineWidth) * sin($dir_angle));
  12762. }
  12763. // main arrow line / shaft
  12764. $this->Line($x0, $y0, $sx1, $sy1);
  12765. // left arrowhead arm tip
  12766. $x2L = $x1 + ($arm_size * cos($dir_angle + $arm_angle));
  12767. $y2L = $y1 + ($arm_size * sin($dir_angle + $arm_angle));
  12768. // right arrowhead arm tip
  12769. $x2R = $x1 + ($arm_size * cos($dir_angle - $arm_angle));
  12770. $y2R = $y1 + ($arm_size * sin($dir_angle - $arm_angle));
  12771. $mode = 'D';
  12772. $style = array();
  12773. switch ($head_style) {
  12774. case 0: {
  12775. // draw only arrowhead arms
  12776. $mode = 'D';
  12777. $style = array(1, 1, 0);
  12778. break;
  12779. }
  12780. case 1: {
  12781. // draw closed arrowhead, but no fill
  12782. $mode = 'D';
  12783. break;
  12784. }
  12785. case 2: {
  12786. // closed and filled arrowhead
  12787. $mode = 'DF';
  12788. break;
  12789. }
  12790. case 3: {
  12791. // filled arrowhead
  12792. $mode = 'F';
  12793. break;
  12794. }
  12795. }
  12796. $this->Polygon(array($x2L, $y2L, $x1, $y1, $x2R, $y2R), $mode, $style, array());
  12797. }
  12798. // END GRAPHIC FUNCTIONS SECTION -----------------------
  12799. // BIDIRECTIONAL TEXT SECTION --------------------------
  12800. /**
  12801. * Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
  12802. * @param string $str string to manipulate.
  12803. * @param bool $setbom if true set the Byte Order Mark (BOM = 0xFEFF)
  12804. * @param bool $forcertl if true forces RTL text direction
  12805. * @return string
  12806. * @access protected
  12807. * @author Nicola Asuni
  12808. * @since 2.1.000 (2008-01-08)
  12809. */
  12810. protected function utf8StrRev($str, $setbom=false, $forcertl=false) {
  12811. return $this->utf8StrArrRev($this->UTF8StringToArray($str), $str, $setbom, $forcertl);
  12812. }
  12813. /**
  12814. * Reverse the RLT substrings array using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
  12815. * @param array $arr array of unicode values.
  12816. * @param string $str string to manipulate (or empty value).
  12817. * @param bool $setbom if true set the Byte Order Mark (BOM = 0xFEFF)
  12818. * @param bool $forcertl if true forces RTL text direction
  12819. * @return string
  12820. * @access protected
  12821. * @author Nicola Asuni
  12822. * @since 4.9.000 (2010-03-27)
  12823. */
  12824. protected function utf8StrArrRev($arr, $str='', $setbom=false, $forcertl=false) {
  12825. return $this->arrUTF8ToUTF16BE($this->utf8Bidi($arr, $str, $forcertl), $setbom);
  12826. }
  12827. /**
  12828. * Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
  12829. * @param array $ta array of characters composing the string.
  12830. * @param string $str string to process
  12831. * @param bool $forcertl if 'R' forces RTL, if 'L' forces LTR
  12832. * @return array of unicode chars
  12833. * @author Nicola Asuni
  12834. * @access protected
  12835. * @since 2.4.000 (2008-03-06)
  12836. */
  12837. protected function utf8Bidi($ta, $str='', $forcertl=false) {
  12838. // paragraph embedding level
  12839. $pel = 0;
  12840. // max level
  12841. $maxlevel = 0;
  12842. if ($this->empty_string($str)) {
  12843. // create string from array
  12844. $str = $this->UTF8ArrSubString($ta);
  12845. }
  12846. // check if string contains arabic text
  12847. if (preg_match($this->unicode->uni_RE_PATTERN_ARABIC, $str)) {
  12848. $arabic = true;
  12849. } else {
  12850. $arabic = false;
  12851. }
  12852. // check if string contains RTL text
  12853. if (!($forcertl OR $arabic OR preg_match($this->unicode->uni_RE_PATTERN_RTL, $str))) {
  12854. return $ta;
  12855. }
  12856. // get number of chars
  12857. $numchars = count($ta);
  12858. if ($forcertl == 'R') {
  12859. $pel = 1;
  12860. } elseif ($forcertl == 'L') {
  12861. $pel = 0;
  12862. } else {
  12863. // P2. In each paragraph, find the first character of type L, AL, or R.
  12864. // 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.
  12865. for ($i=0; $i < $numchars; ++$i) {
  12866. $type = $this->unicode->uni_type[$ta[$i]];
  12867. if ($type == 'L') {
  12868. $pel = 0;
  12869. break;
  12870. } elseif (($type == 'AL') OR ($type == 'R')) {
  12871. $pel = 1;
  12872. break;
  12873. }
  12874. }
  12875. }
  12876. // Current Embedding Level
  12877. $cel = $pel;
  12878. // directional override status
  12879. $dos = 'N';
  12880. $remember = array();
  12881. // start-of-level-run
  12882. $sor = $pel % 2 ? 'R' : 'L';
  12883. $eor = $sor;
  12884. // Array of characters data
  12885. $chardata = Array();
  12886. // 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.
  12887. // In the resolution of levels in rules I1 and I2, the maximum embedding level of 62 can be reached.
  12888. for ($i=0; $i < $numchars; ++$i) {
  12889. if ($ta[$i] == $this->unicode->uni_RLE) {
  12890. // X2. With each RLE, compute the least greater odd embedding level.
  12891. // 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.
  12892. // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
  12893. $next_level = $cel + ($cel % 2) + 1;
  12894. if ($next_level < 62) {
  12895. $remember[] = array('num' => $this->unicode->uni_RLE, 'cel' => $cel, 'dos' => $dos);
  12896. $cel = $next_level;
  12897. $dos = 'N';
  12898. $sor = $eor;
  12899. $eor = $cel % 2 ? 'R' : 'L';
  12900. }
  12901. } elseif ($ta[$i] == $this->unicode->uni_LRE) {
  12902. // X3. With each LRE, compute the least greater even embedding level.
  12903. // 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.
  12904. // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
  12905. $next_level = $cel + 2 - ($cel % 2);
  12906. if ( $next_level < 62 ) {
  12907. $remember[] = array('num' => $this->unicode->uni_LRE, 'cel' => $cel, 'dos' => $dos);
  12908. $cel = $next_level;
  12909. $dos = 'N';
  12910. $sor = $eor;
  12911. $eor = $cel % 2 ? 'R' : 'L';
  12912. }
  12913. } elseif ($ta[$i] == $this->unicode->uni_RLO) {
  12914. // X4. With each RLO, compute the least greater odd embedding level.
  12915. // 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.
  12916. // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
  12917. $next_level = $cel + ($cel % 2) + 1;
  12918. if ($next_level < 62) {
  12919. $remember[] = array('num' => $this->unicode->uni_RLO, 'cel' => $cel, 'dos' => $dos);
  12920. $cel = $next_level;
  12921. $dos = 'R';
  12922. $sor = $eor;
  12923. $eor = $cel % 2 ? 'R' : 'L';
  12924. }
  12925. } elseif ($ta[$i] == $this->unicode->uni_LRO) {
  12926. // X5. With each LRO, compute the least greater even embedding level.
  12927. // 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.
  12928. // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
  12929. $next_level = $cel + 2 - ($cel % 2);
  12930. if ( $next_level < 62 ) {
  12931. $remember[] = array('num' => $this->unicode->uni_LRO, 'cel' => $cel, 'dos' => $dos);
  12932. $cel = $next_level;
  12933. $dos = 'L';
  12934. $sor = $eor;
  12935. $eor = $cel % 2 ? 'R' : 'L';
  12936. }
  12937. } elseif ($ta[$i] == $this->unicode->uni_PDF) {
  12938. // 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.
  12939. if (count($remember)) {
  12940. $last = count($remember ) - 1;
  12941. if (($remember[$last]['num'] == $this->unicode->uni_RLE) OR
  12942. ($remember[$last]['num'] == $this->unicode->uni_LRE) OR
  12943. ($remember[$last]['num'] == $this->unicode->uni_RLO) OR
  12944. ($remember[$last]['num'] == $this->unicode->uni_LRO)) {
  12945. $match = array_pop($remember);
  12946. $cel = $match['cel'];
  12947. $dos = $match['dos'];
  12948. $sor = $eor;
  12949. $eor = ($cel > $match['cel'] ? $cel : $match['cel']) % 2 ? 'R' : 'L';
  12950. }
  12951. }
  12952. } elseif (($ta[$i] != $this->unicode->uni_RLE) AND
  12953. ($ta[$i] != $this->unicode->uni_LRE) AND
  12954. ($ta[$i] != $this->unicode->uni_RLO) AND
  12955. ($ta[$i] != $this->unicode->uni_LRO) AND
  12956. ($ta[$i] != $this->unicode->uni_PDF)) {
  12957. // X6. For all types besides RLE, LRE, RLO, LRO, and PDF:
  12958. // a. Set the level of the current character to the current embedding level.
  12959. // b. Whenever the directional override status is not neutral, reset the current character type to the directional override status.
  12960. if ($dos != 'N') {
  12961. $chardir = $dos;
  12962. } else {
  12963. if (isset($this->unicode->uni_type[$ta[$i]])) {
  12964. $chardir = $this->unicode->uni_type[$ta[$i]];
  12965. } else {
  12966. $chardir = 'L';
  12967. }
  12968. }
  12969. // stores string characters and other information
  12970. $chardata[] = array('char' => $ta[$i], 'level' => $cel, 'type' => $chardir, 'sor' => $sor, 'eor' => $eor);
  12971. }
  12972. } // end for each char
  12973. // X8. All explicit directional embeddings and overrides are completely terminated at the end of each paragraph. Paragraph separators are not included in the embedding.
  12974. // X9. Remove all RLE, LRE, RLO, LRO, PDF, and BN codes.
  12975. // 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.
  12976. // 3.3.3 Resolving Weak Types
  12977. // 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.
  12978. // Nonspacing marks are now resolved based on the previous characters.
  12979. $numchars = count($chardata);
  12980. // 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.
  12981. $prevlevel = -1; // track level changes
  12982. $levcount = 0; // counts consecutive chars at the same level
  12983. for ($i=0; $i < $numchars; ++$i) {
  12984. if ($chardata[$i]['type'] == 'NSM') {
  12985. if ($levcount) {
  12986. $chardata[$i]['type'] = $chardata[$i]['sor'];
  12987. } elseif ($i > 0) {
  12988. $chardata[$i]['type'] = $chardata[($i-1)]['type'];
  12989. }
  12990. }
  12991. if ($chardata[$i]['level'] != $prevlevel) {
  12992. $levcount = 0;
  12993. } else {
  12994. ++$levcount;
  12995. }
  12996. $prevlevel = $chardata[$i]['level'];
  12997. }
  12998. // 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.
  12999. $prevlevel = -1;
  13000. $levcount = 0;
  13001. for ($i=0; $i < $numchars; ++$i) {
  13002. if ($chardata[$i]['char'] == 'EN') {
  13003. for ($j=$levcount; $j >= 0; $j--) {
  13004. if ($chardata[$j]['type'] == 'AL') {
  13005. $chardata[$i]['type'] = 'AN';
  13006. } elseif (($chardata[$j]['type'] == 'L') OR ($chardata[$j]['type'] == 'R')) {
  13007. break;
  13008. }
  13009. }
  13010. }
  13011. if ($chardata[$i]['level'] != $prevlevel) {
  13012. $levcount = 0;
  13013. } else {
  13014. ++$levcount;
  13015. }
  13016. $prevlevel = $chardata[$i]['level'];
  13017. }
  13018. // W3. Change all ALs to R.
  13019. for ($i=0; $i < $numchars; ++$i) {
  13020. if ($chardata[$i]['type'] == 'AL') {
  13021. $chardata[$i]['type'] = 'R';
  13022. }
  13023. }
  13024. // 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.
  13025. $prevlevel = -1;
  13026. $levcount = 0;
  13027. for ($i=0; $i < $numchars; ++$i) {
  13028. if (($levcount > 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
  13029. if (($chardata[$i]['type'] == 'ES') AND ($chardata[($i-1)]['type'] == 'EN') AND ($chardata[($i+1)]['type'] == 'EN')) {
  13030. $chardata[$i]['type'] = 'EN';
  13031. } elseif (($chardata[$i]['type'] == 'CS') AND ($chardata[($i-1)]['type'] == 'EN') AND ($chardata[($i+1)]['type'] == 'EN')) {
  13032. $chardata[$i]['type'] = 'EN';
  13033. } elseif (($chardata[$i]['type'] == 'CS') AND ($chardata[($i-1)]['type'] == 'AN') AND ($chardata[($i+1)]['type'] == 'AN')) {
  13034. $chardata[$i]['type'] = 'AN';
  13035. }
  13036. }
  13037. if ($chardata[$i]['level'] != $prevlevel) {
  13038. $levcount = 0;
  13039. } else {
  13040. ++$levcount;
  13041. }
  13042. $prevlevel = $chardata[$i]['level'];
  13043. }
  13044. // W5. A sequence of European terminators adjacent to European numbers changes to all European numbers.
  13045. $prevlevel = -1;
  13046. $levcount = 0;
  13047. for ($i=0; $i < $numchars; ++$i) {
  13048. if ($chardata[$i]['type'] == 'ET') {
  13049. if (($levcount > 0) AND ($chardata[($i-1)]['type'] == 'EN')) {
  13050. $chardata[$i]['type'] = 'EN';
  13051. } else {
  13052. $j = $i+1;
  13053. while (($j < $numchars) AND ($chardata[$j]['level'] == $prevlevel)) {
  13054. if ($chardata[$j]['type'] == 'EN') {
  13055. $chardata[$i]['type'] = 'EN';
  13056. break;
  13057. } elseif ($chardata[$j]['type'] != 'ET') {
  13058. break;
  13059. }
  13060. ++$j;
  13061. }
  13062. }
  13063. }
  13064. if ($chardata[$i]['level'] != $prevlevel) {
  13065. $levcount = 0;
  13066. } else {
  13067. ++$levcount;
  13068. }
  13069. $prevlevel = $chardata[$i]['level'];
  13070. }
  13071. // W6. Otherwise, separators and terminators change to Other Neutral.
  13072. $prevlevel = -1;
  13073. $levcount = 0;
  13074. for ($i=0; $i < $numchars; ++$i) {
  13075. if (($chardata[$i]['type'] == 'ET') OR ($chardata[$i]['type'] == 'ES') OR ($chardata[$i]['type'] == 'CS')) {
  13076. $chardata[$i]['type'] = 'ON';
  13077. }
  13078. if ($chardata[$i]['level'] != $prevlevel) {
  13079. $levcount = 0;
  13080. } else {
  13081. ++$levcount;
  13082. }
  13083. $prevlevel = $chardata[$i]['level'];
  13084. }
  13085. //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.
  13086. $prevlevel = -1;
  13087. $levcount = 0;
  13088. for ($i=0; $i < $numchars; ++$i) {
  13089. if ($chardata[$i]['char'] == 'EN') {
  13090. for ($j=$levcount; $j >= 0; $j--) {
  13091. if ($chardata[$j]['type'] == 'L') {
  13092. $chardata[$i]['type'] = 'L';
  13093. } elseif ($chardata[$j]['type'] == 'R') {
  13094. break;
  13095. }
  13096. }
  13097. }
  13098. if ($chardata[$i]['level'] != $prevlevel) {
  13099. $levcount = 0;
  13100. } else {
  13101. ++$levcount;
  13102. }
  13103. $prevlevel = $chardata[$i]['level'];
  13104. }
  13105. // 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.
  13106. $prevlevel = -1;
  13107. $levcount = 0;
  13108. for ($i=0; $i < $numchars; ++$i) {
  13109. if (($levcount > 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
  13110. if (($chardata[$i]['type'] == 'N') AND ($chardata[($i-1)]['type'] == 'L') AND ($chardata[($i+1)]['type'] == 'L')) {
  13111. $chardata[$i]['type'] = 'L';
  13112. } elseif (($chardata[$i]['type'] == 'N') AND
  13113. (($chardata[($i-1)]['type'] == 'R') OR ($chardata[($i-1)]['type'] == 'EN') OR ($chardata[($i-1)]['type'] == 'AN')) AND
  13114. (($chardata[($i+1)]['type'] == 'R') OR ($chardata[($i+1)]['type'] == 'EN') OR ($chardata[($i+1)]['type'] == 'AN'))) {
  13115. $chardata[$i]['type'] = 'R';
  13116. } elseif ($chardata[$i]['type'] == 'N') {
  13117. // N2. Any remaining neutrals take the embedding direction
  13118. $chardata[$i]['type'] = $chardata[$i]['sor'];
  13119. }
  13120. } elseif (($levcount == 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) {
  13121. // first char
  13122. if (($chardata[$i]['type'] == 'N') AND ($chardata[$i]['sor'] == 'L') AND ($chardata[($i+1)]['type'] == 'L')) {
  13123. $chardata[$i]['type'] = 'L';
  13124. } elseif (($chardata[$i]['type'] == 'N') AND
  13125. (($chardata[$i]['sor'] == 'R') OR ($chardata[$i]['sor'] == 'EN') OR ($chardata[$i]['sor'] == 'AN')) AND
  13126. (($chardata[($i+1)]['type'] == 'R') OR ($chardata[($i+1)]['type'] == 'EN') OR ($chardata[($i+1)]['type'] == 'AN'))) {
  13127. $chardata[$i]['type'] = 'R';
  13128. } elseif ($chardata[$i]['type'] == 'N') {
  13129. // N2. Any remaining neutrals take the embedding direction
  13130. $chardata[$i]['type'] = $chardata[$i]['sor'];
  13131. }
  13132. } elseif (($levcount > 0) AND ((($i+1) == $numchars) OR (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] != $prevlevel))) {
  13133. //last char
  13134. if (($chardata[$i]['type'] == 'N') AND ($chardata[($i-1)]['type'] == 'L') AND ($chardata[$i]['eor'] == 'L')) {
  13135. $chardata[$i]['type'] = 'L';
  13136. } elseif (($chardata[$i]['type'] == 'N') AND
  13137. (($chardata[($i-1)]['type'] == 'R') OR ($chardata[($i-1)]['type'] == 'EN') OR ($chardata[($i-1)]['type'] == 'AN')) AND
  13138. (($chardata[$i]['eor'] == 'R') OR ($chardata[$i]['eor'] == 'EN') OR ($chardata[$i]['eor'] == 'AN'))) {
  13139. $chardata[$i]['type'] = 'R';
  13140. } elseif ($chardata[$i]['type'] == 'N') {
  13141. // N2. Any remaining neutrals take the embedding direction
  13142. $chardata[$i]['type'] = $chardata[$i]['sor'];
  13143. }
  13144. } elseif ($chardata[$i]['type'] == 'N') {
  13145. // N2. Any remaining neutrals take the embedding direction
  13146. $chardata[$i]['type'] = $chardata[$i]['sor'];
  13147. }
  13148. if ($chardata[$i]['level'] != $prevlevel) {
  13149. $levcount = 0;
  13150. } else {
  13151. ++$levcount;
  13152. }
  13153. $prevlevel = $chardata[$i]['level'];
  13154. }
  13155. // 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.
  13156. // I2. For all characters with an odd (right-to-left) embedding direction, those of type L, EN or AN go up one level.
  13157. for ($i=0; $i < $numchars; ++$i) {
  13158. $odd = $chardata[$i]['level'] % 2;
  13159. if ($odd) {
  13160. if (($chardata[$i]['type'] == 'L') OR ($chardata[$i]['type'] == 'AN') OR ($chardata[$i]['type'] == 'EN')) {
  13161. $chardata[$i]['level'] += 1;
  13162. }
  13163. } else {
  13164. if ($chardata[$i]['type'] == 'R') {
  13165. $chardata[$i]['level'] += 1;
  13166. } elseif (($chardata[$i]['type'] == 'AN') OR ($chardata[$i]['type'] == 'EN')) {
  13167. $chardata[$i]['level'] += 2;
  13168. }
  13169. }
  13170. $maxlevel = max($chardata[$i]['level'],$maxlevel);
  13171. }
  13172. // L1. On each line, reset the embedding level of the following characters to the paragraph embedding level:
  13173. // 1. Segment separators,
  13174. // 2. Paragraph separators,
  13175. // 3. Any sequence of whitespace characters preceding a segment separator or paragraph separator, and
  13176. // 4. Any sequence of white space characters at the end of the line.
  13177. for ($i=0; $i < $numchars; ++$i) {
  13178. if (($chardata[$i]['type'] == 'B') OR ($chardata[$i]['type'] == 'S')) {
  13179. $chardata[$i]['level'] = $pel;
  13180. } elseif ($chardata[$i]['type'] == 'WS') {
  13181. $j = $i+1;
  13182. while ($j < $numchars) {
  13183. if ((($chardata[$j]['type'] == 'B') OR ($chardata[$j]['type'] == 'S')) OR
  13184. (($j == ($numchars-1)) AND ($chardata[$j]['type'] == 'WS'))) {
  13185. $chardata[$i]['level'] = $pel;
  13186. break;
  13187. } elseif ($chardata[$j]['type'] != 'WS') {
  13188. break;
  13189. }
  13190. ++$j;
  13191. }
  13192. }
  13193. }
  13194. // Arabic Shaping
  13195. // 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.
  13196. if ($arabic) {
  13197. $endedletter = array(1569,1570,1571,1572,1573,1575,1577,1583,1584,1585,1586,1608,1688);
  13198. $alfletter = array(1570,1571,1573,1575);
  13199. $chardata2 = $chardata;
  13200. $laaletter = false;
  13201. $charAL = array();
  13202. $x = 0;
  13203. for ($i=0; $i < $numchars; ++$i) {
  13204. if (($this->unicode->uni_type[$chardata[$i]['char']] == 'AL') OR ($chardata[$i]['char'] == 32) OR ($chardata[$i]['char'] == 8204)) {
  13205. $charAL[$x] = $chardata[$i];
  13206. $charAL[$x]['i'] = $i;
  13207. $chardata[$i]['x'] = $x;
  13208. ++$x;
  13209. }
  13210. }
  13211. $numAL = $x;
  13212. for ($i=0; $i < $numchars; ++$i) {
  13213. $thischar = $chardata[$i];
  13214. if ($i > 0) {
  13215. $prevchar = $chardata[($i-1)];
  13216. } else {
  13217. $prevchar = false;
  13218. }
  13219. if (($i+1) < $numchars) {
  13220. $nextchar = $chardata[($i+1)];
  13221. } else {
  13222. $nextchar = false;
  13223. }
  13224. if ($this->unicode->uni_type[$thischar['char']] == 'AL') {
  13225. $x = $thischar['x'];
  13226. if ($x > 0) {
  13227. $prevchar = $charAL[($x-1)];
  13228. } else {
  13229. $prevchar = false;
  13230. }
  13231. if (($x+1) < $numAL) {
  13232. $nextchar = $charAL[($x+1)];
  13233. } else {
  13234. $nextchar = false;
  13235. }
  13236. // if laa letter
  13237. if (($prevchar !== false) AND ($prevchar['char'] == 1604) AND (in_array($thischar['char'], $alfletter))) {
  13238. $arabicarr = $this->unicode->uni_laa_array;
  13239. $laaletter = true;
  13240. if ($x > 1) {
  13241. $prevchar = $charAL[($x-2)];
  13242. } else {
  13243. $prevchar = false;
  13244. }
  13245. } else {
  13246. $arabicarr = $this->unicode->uni_arabicsubst;
  13247. $laaletter = false;
  13248. }
  13249. if (($prevchar !== false) AND ($nextchar !== false) AND
  13250. (($this->unicode->uni_type[$prevchar['char']] == 'AL') OR ($this->unicode->uni_type[$prevchar['char']] == 'NSM')) AND
  13251. (($this->unicode->uni_type[$nextchar['char']] == 'AL') OR ($this->unicode->uni_type[$nextchar['char']] == 'NSM')) AND
  13252. ($prevchar['type'] == $thischar['type']) AND
  13253. ($nextchar['type'] == $thischar['type']) AND
  13254. ($nextchar['char'] != 1567)) {
  13255. if (in_array($prevchar['char'], $endedletter)) {
  13256. if (isset($arabicarr[$thischar['char']][2])) {
  13257. // initial
  13258. $chardata2[$i]['char'] = $arabicarr[$thischar['char']][2];
  13259. }
  13260. } else {
  13261. if (isset($arabicarr[$thischar['char']][3])) {
  13262. // medial
  13263. $chardata2[$i]['char'] = $arabicarr[$thischar['char']][3];
  13264. }
  13265. }
  13266. } elseif (($nextchar !== false) AND
  13267. (($this->unicode->uni_type[$nextchar['char']] == 'AL') OR ($this->unicode->uni_type[$nextchar['char']] == 'NSM')) AND
  13268. ($nextchar['type'] == $thischar['type']) AND
  13269. ($nextchar['char'] != 1567)) {
  13270. if (isset($arabicarr[$chardata[$i]['char']][2])) {
  13271. // initial
  13272. $chardata2[$i]['char'] = $arabicarr[$thischar['char']][2];
  13273. }
  13274. } elseif ((($prevchar !== false) AND
  13275. (($this->unicode->uni_type[$prevchar['char']] == 'AL') OR ($this->unicode->uni_type[$prevchar['char']] == 'NSM')) AND
  13276. ($prevchar['type'] == $thischar['type'])) OR
  13277. (($nextchar !== false) AND ($nextchar['char'] == 1567))) {
  13278. // final
  13279. if (($i > 1) AND ($thischar['char'] == 1607) AND
  13280. ($chardata[$i-1]['char'] == 1604) AND
  13281. ($chardata[$i-2]['char'] == 1604)) {
  13282. //Allah Word
  13283. // mark characters to delete with false
  13284. $chardata2[$i-2]['char'] = false;
  13285. $chardata2[$i-1]['char'] = false;
  13286. $chardata2[$i]['char'] = 65010;
  13287. } else {
  13288. if (($prevchar !== false) AND in_array($prevchar['char'], $endedletter)) {
  13289. if (isset($arabicarr[$thischar['char']][0])) {
  13290. // isolated
  13291. $chardata2[$i]['char'] = $arabicarr[$thischar['char']][0];
  13292. }
  13293. } else {
  13294. if (isset($arabicarr[$thischar['char']][1])) {
  13295. // final
  13296. $chardata2[$i]['char'] = $arabicarr[$thischar['char']][1];
  13297. }
  13298. }
  13299. }
  13300. } elseif (isset($arabicarr[$thischar['char']][0])) {
  13301. // isolated
  13302. $chardata2[$i]['char'] = $arabicarr[$thischar['char']][0];
  13303. }
  13304. // if laa letter
  13305. if ($laaletter) {
  13306. // mark characters to delete with false
  13307. $chardata2[($charAL[($x-1)]['i'])]['char'] = false;
  13308. }
  13309. } // end if AL (Arabic Letter)
  13310. } // end for each char
  13311. /*
  13312. * Combining characters that can occur with Arabic Shadda (0651 HEX, 1617 DEC) are replaced.
  13313. * Putting the combining mark and shadda in the same glyph allows us to avoid the two marks overlapping each other in an illegible manner.
  13314. */
  13315. $cw = &$this->CurrentFont['cw'];
  13316. for ($i = 0; $i < ($numchars-1); ++$i) {
  13317. if (($chardata2[$i]['char'] == 1617) AND (isset($this->unicode->uni_diacritics[($chardata2[$i+1]['char'])]))) {
  13318. // check if the subtitution font is defined on current font
  13319. if (isset($cw[($this->unicode->uni_diacritics[($chardata2[$i+1]['char'])])])) {
  13320. $chardata2[$i]['char'] = false;
  13321. $chardata2[$i+1]['char'] = $this->unicode->uni_diacritics[($chardata2[$i+1]['char'])];
  13322. }
  13323. }
  13324. }
  13325. // remove marked characters
  13326. foreach ($chardata2 as $key => $value) {
  13327. if ($value['char'] === false) {
  13328. unset($chardata2[$key]);
  13329. }
  13330. }
  13331. $chardata = array_values($chardata2);
  13332. $numchars = count($chardata);
  13333. unset($chardata2);
  13334. unset($arabicarr);
  13335. unset($laaletter);
  13336. unset($charAL);
  13337. }
  13338. // 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.
  13339. for ($j=$maxlevel; $j > 0; $j--) {
  13340. $ordarray = Array();
  13341. $revarr = Array();
  13342. $onlevel = false;
  13343. for ($i=0; $i < $numchars; ++$i) {
  13344. if ($chardata[$i]['level'] >= $j) {
  13345. $onlevel = true;
  13346. if (isset($this->unicode->uni_mirror[$chardata[$i]['char']])) {
  13347. // 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.
  13348. $chardata[$i]['char'] = $this->unicode->uni_mirror[$chardata[$i]['char']];
  13349. }
  13350. $revarr[] = $chardata[$i];
  13351. } else {
  13352. if ($onlevel) {
  13353. $revarr = array_reverse($revarr);
  13354. $ordarray = array_merge($ordarray, $revarr);
  13355. $revarr = Array();
  13356. $onlevel = false;
  13357. }
  13358. $ordarray[] = $chardata[$i];
  13359. }
  13360. }
  13361. if ($onlevel) {
  13362. $revarr = array_reverse($revarr);
  13363. $ordarray = array_merge($ordarray, $revarr);
  13364. }
  13365. $chardata = $ordarray;
  13366. }
  13367. $ordarray = array();
  13368. for ($i=0; $i < $numchars; ++$i) {
  13369. $ordarray[] = $chardata[$i]['char'];
  13370. // store char values for subsetting
  13371. $this->CurrentFont['subsetchars'][$chardata[$i]['char']] = true;
  13372. }
  13373. // update font subsetchars
  13374. $this->setFontSubBuffer($this->CurrentFont['fontkey'], 'subsetchars', $this->CurrentFont['subsetchars']);
  13375. return $ordarray;
  13376. }
  13377. // END OF BIDIRECTIONAL TEXT SECTION -------------------
  13378. /**
  13379. * Adds a bookmark.
  13380. * @param string $txt bookmark description.
  13381. * @param int $level bookmark level (minimum value is 0).
  13382. * @param float $y Y position in user units of the bookmark on the selected page (default = -1 = current position; 0 = page start;).
  13383. * @param int $page target page number (leave empty for current page).
  13384. * @access public
  13385. * @author Olivier Plathey, Nicola Asuni
  13386. * @since 2.1.002 (2008-02-12)
  13387. */
  13388. public function Bookmark($txt, $level=0, $y=-1, $page='') {
  13389. if ($level < 0) {
  13390. $level = 0;
  13391. }
  13392. if (isset($this->outlines[0])) {
  13393. $lastoutline = end($this->outlines);
  13394. $maxlevel = $lastoutline['l'] + 1;
  13395. } else {
  13396. $maxlevel = 0;
  13397. }
  13398. if ($level > $maxlevel) {
  13399. $level = $maxlevel;
  13400. }
  13401. if ($y == -1) {
  13402. $y = $this->GetY();
  13403. }
  13404. if (empty($page)) {
  13405. $page = $this->PageNo();
  13406. if (empty($page)) {
  13407. return;
  13408. }
  13409. }
  13410. $this->outlines[] = array('t' => $txt, 'l' => $level, 'y' => $y, 'p' => $page);
  13411. }
  13412. /**
  13413. * Create a bookmark PDF string.
  13414. * @access protected
  13415. * @author Olivier Plathey, Nicola Asuni
  13416. * @since 2.1.002 (2008-02-12)
  13417. */
  13418. protected function _putbookmarks() {
  13419. $nb = count($this->outlines);
  13420. if ($nb == 0) {
  13421. return;
  13422. }
  13423. // get sorting columns
  13424. $outline_p = array();
  13425. $outline_y = array();
  13426. foreach ($this->outlines as $key => $row) {
  13427. $outline_p[$key] = $row['p'];
  13428. $outline_k[$key] = $key;
  13429. }
  13430. // sort outlines by page and original position
  13431. array_multisort($outline_p, SORT_NUMERIC, SORT_ASC, $outline_k, SORT_NUMERIC, SORT_ASC, $this->outlines);
  13432. $lru = array();
  13433. $level = 0;
  13434. foreach ($this->outlines as $i => $o) {
  13435. if ($o['l'] > 0) {
  13436. $parent = $lru[($o['l'] - 1)];
  13437. //Set parent and last pointers
  13438. $this->outlines[$i]['parent'] = $parent;
  13439. $this->outlines[$parent]['last'] = $i;
  13440. if ($o['l'] > $level) {
  13441. //Level increasing: set first pointer
  13442. $this->outlines[$parent]['first'] = $i;
  13443. }
  13444. } else {
  13445. $this->outlines[$i]['parent'] = $nb;
  13446. }
  13447. if (($o['l'] <= $level) AND ($i > 0)) {
  13448. //Set prev and next pointers
  13449. $prev = $lru[$o['l']];
  13450. $this->outlines[$prev]['next'] = $i;
  13451. $this->outlines[$i]['prev'] = $prev;
  13452. }
  13453. $lru[$o['l']] = $i;
  13454. $level = $o['l'];
  13455. }
  13456. //Outline items
  13457. $n = $this->n + 1;
  13458. $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';
  13459. foreach ($this->outlines as $i => $o) {
  13460. if (isset($this->page_obj_id[($o['p'])])) {
  13461. $oid = $this->_newobj();
  13462. // covert HTML title to string
  13463. $title = preg_replace($nltags, "\n", $o['t']);
  13464. $title = preg_replace("/[\r]+/si", '', $title);
  13465. $title = preg_replace("/[\n]+/si", "\n", $title);
  13466. $title = strip_tags($title);
  13467. $title = $this->stringTrim($title);
  13468. $out = '<</Title '.$this->_textstring($title, $oid);
  13469. $out .= ' /Parent '.($n + $o['parent']).' 0 R';
  13470. if (isset($o['prev'])) {
  13471. $out .= ' /Prev '.($n + $o['prev']).' 0 R';
  13472. }
  13473. if (isset($o['next'])) {
  13474. $out .= ' /Next '.($n + $o['next']).' 0 R';
  13475. }
  13476. if (isset($o['first'])) {
  13477. $out .= ' /First '.($n + $o['first']).' 0 R';
  13478. }
  13479. if (isset($o['last'])) {
  13480. $out .= ' /Last '.($n + $o['last']).' 0 R';
  13481. }
  13482. $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)));
  13483. $out .= ' /Count 0 >>';
  13484. $out .= "\n".'endobj';
  13485. $this->_out($out);
  13486. }
  13487. }
  13488. //Outline root
  13489. $this->OutlineRoot = $this->_newobj();
  13490. $this->_out('<< /Type /Outlines /First '.$n.' 0 R /Last '.($n + $lru[0]).' 0 R >>'."\n".'endobj');
  13491. }
  13492. // --- JAVASCRIPT ------------------------------------------------------
  13493. /**
  13494. * Adds a javascript
  13495. * @param string $script Javascript code
  13496. * @access public
  13497. * @author Johannes Güntert, Nicola Asuni
  13498. * @since 2.1.002 (2008-02-12)
  13499. */
  13500. public function IncludeJS($script) {
  13501. $this->javascript .= $script;
  13502. }
  13503. /**
  13504. * Adds a javascript object and return object ID
  13505. * @param string $script Javascript code
  13506. * @param boolean $onload if true executes this object when opening the document
  13507. * @return int internal object ID
  13508. * @access public
  13509. * @author Nicola Asuni
  13510. * @since 4.8.000 (2009-09-07)
  13511. */
  13512. public function addJavascriptObject($script, $onload=false) {
  13513. ++$this->n;
  13514. $this->js_objects[$this->n] = array('n' => $this->n, 'js' => $script, 'onload' => $onload);
  13515. return $this->n;
  13516. }
  13517. /**
  13518. * Create a javascript PDF string.
  13519. * @access protected
  13520. * @author Johannes Güntert, Nicola Asuni
  13521. * @since 2.1.002 (2008-02-12)
  13522. */
  13523. protected function _putjavascript() {
  13524. if (empty($this->javascript) AND empty($this->js_objects)) {
  13525. return;
  13526. }
  13527. if (strpos($this->javascript, 'this.addField') > 0) {
  13528. if (!$this->ur['enabled']) {
  13529. //$this->setUserRights();
  13530. }
  13531. // the following two lines are used to avoid form fields duplication after saving
  13532. // The addField method only works when releasing user rights (UR3)
  13533. $jsa = sprintf("ftcpdfdocsaved=this.addField('%s','%s',%d,[%.2F,%.2F,%.2F,%.2F]);", 'tcpdfdocsaved', 'text', 0, 0, 1, 0, 1);
  13534. $jsb = "getField('tcpdfdocsaved').value='saved';";
  13535. $this->javascript = $jsa."\n".$this->javascript."\n".$jsb;
  13536. }
  13537. $this->n_js = $this->_newobj();
  13538. $out = ' << /Names [';
  13539. if (!empty($this->javascript)) {
  13540. $out .= ' (EmbeddedJS) '.($this->n + 1).' 0 R';
  13541. }
  13542. if (!empty($this->js_objects)) {
  13543. foreach ($this->js_objects as $key => $val) {
  13544. if ($val['onload']) {
  13545. $out .= ' (JS'.$key.') '.$key.' 0 R';
  13546. }
  13547. }
  13548. }
  13549. $out .= ' ] >>';
  13550. $out .= "\n".'endobj';
  13551. $this->_out($out);
  13552. // default Javascript object
  13553. if (!empty($this->javascript)) {
  13554. $obj_id = $this->_newobj();
  13555. $out = '<< /S /JavaScript';
  13556. $out .= ' /JS '.$this->_textstring($this->javascript, $obj_id);
  13557. $out .= ' >>';
  13558. $out .= "\n".'endobj';
  13559. $this->_out($out);
  13560. }
  13561. // additional Javascript objects
  13562. if (!empty($this->js_objects)) {
  13563. foreach ($this->js_objects as $key => $val) {
  13564. $out = $this->_getobj($key)."\n".' << /S /JavaScript /JS '.$this->_textstring($val['js'], $key).' >>'."\n".'endobj';
  13565. $this->_out($out);
  13566. }
  13567. }
  13568. }
  13569. /**
  13570. * Convert color to javascript color.
  13571. * @param string $color color name or #RRGGBB
  13572. * @access protected
  13573. * @author Denis Van Nuffelen, Nicola Asuni
  13574. * @since 2.1.002 (2008-02-12)
  13575. */
  13576. protected function _JScolor($color) {
  13577. static $aColors = array('transparent', 'black', 'white', 'red', 'green', 'blue', 'cyan', 'magenta', 'yellow', 'dkGray', 'gray', 'ltGray');
  13578. if (substr($color,0,1) == '#') {
  13579. return sprintf("['RGB',%.3F,%.3F,%.3F]", hexdec(substr($color,1,2))/255, hexdec(substr($color,3,2))/255, hexdec(substr($color,5,2))/255);
  13580. }
  13581. if (!in_array($color,$aColors)) {
  13582. $this->Error('Invalid color: '.$color);
  13583. }
  13584. return 'color.'.$color;
  13585. }
  13586. /**
  13587. * Adds a javascript form field.
  13588. * @param string $type field type
  13589. * @param string $name field name
  13590. * @param int $x horizontal position
  13591. * @param int $y vertical position
  13592. * @param int $w width
  13593. * @param int $h height
  13594. * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
  13595. * @access protected
  13596. * @author Denis Van Nuffelen, Nicola Asuni
  13597. * @since 2.1.002 (2008-02-12)
  13598. */
  13599. protected function _addfield($type, $name, $x, $y, $w, $h, $prop) {
  13600. if ($this->rtl) {
  13601. $x = $x - $w;
  13602. }
  13603. // the followind avoid fields duplication after saving the document
  13604. $this->javascript .= "if(getField('tcpdfdocsaved').value != 'saved') {";
  13605. $k = $this->k;
  13606. $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";
  13607. $this->javascript .= 'f'.$name.'.textSize='.$this->FontSizePt.";\n";
  13608. while (list($key, $val) = each($prop)) {
  13609. if (strcmp(substr($key, -5), 'Color') == 0) {
  13610. $val = $this->_JScolor($val);
  13611. } else {
  13612. $val = "'".$val."'";
  13613. }
  13614. $this->javascript .= 'f'.$name.'.'.$key.'='.$val.";\n";
  13615. }
  13616. if ($this->rtl) {
  13617. $this->x -= $w;
  13618. } else {
  13619. $this->x += $w;
  13620. }
  13621. $this->javascript .= '}';
  13622. }
  13623. // --- FORM FIELDS -----------------------------------------------------
  13624. /**
  13625. * Convert JavaScript form fields properties array to Annotation Properties array.
  13626. * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
  13627. * @return array of annotation properties
  13628. * @access protected
  13629. * @author Nicola Asuni
  13630. * @since 4.8.000 (2009-09-06)
  13631. */
  13632. protected function getAnnotOptFromJSProp($prop) {
  13633. if (isset($prop['aopt']) AND is_array($prop['aopt'])) {
  13634. // the annotation options area lready defined
  13635. return $prop['aopt'];
  13636. }
  13637. $opt = array(); // value to be returned
  13638. // alignment: Controls how the text is laid out within the text field.
  13639. if (isset($prop['alignment'])) {
  13640. switch ($prop['alignment']) {
  13641. case 'left': {
  13642. $opt['q'] = 0;
  13643. break;
  13644. }
  13645. case 'center': {
  13646. $opt['q'] = 1;
  13647. break;
  13648. }
  13649. case 'right': {
  13650. $opt['q'] = 2;
  13651. break;
  13652. }
  13653. default: {
  13654. $opt['q'] = ($this->rtl)?2:0;
  13655. break;
  13656. }
  13657. }
  13658. }
  13659. // lineWidth: Specifies the thickness of the border when stroking the perimeter of a field's rectangle.
  13660. if (isset($prop['lineWidth'])) {
  13661. $linewidth = intval($prop['lineWidth']);
  13662. } else {
  13663. $linewidth = 1;
  13664. }
  13665. // borderStyle: The border style for a field.
  13666. if (isset($prop['borderStyle'])) {
  13667. switch ($prop['borderStyle']) {
  13668. case 'border.d':
  13669. case 'dashed': {
  13670. $opt['border'] = array(0, 0, $linewidth, array(3, 2));
  13671. $opt['bs'] = array('w'=>$linewidth, 's'=>'D', 'd'=>array(3, 2));
  13672. break;
  13673. }
  13674. case 'border.b':
  13675. case 'beveled': {
  13676. $opt['border'] = array(0, 0, $linewidth);
  13677. $opt['bs'] = array('w'=>$linewidth, 's'=>'B');
  13678. break;
  13679. }
  13680. case 'border.i':
  13681. case 'inset': {
  13682. $opt['border'] = array(0, 0, $linewidth);
  13683. $opt['bs'] = array('w'=>$linewidth, 's'=>'I');
  13684. break;
  13685. }
  13686. case 'border.u':
  13687. case 'underline': {
  13688. $opt['border'] = array(0, 0, $linewidth);
  13689. $opt['bs'] = array('w'=>$linewidth, 's'=>'U');
  13690. break;
  13691. }
  13692. default:
  13693. case 'border.s':
  13694. case 'solid': {
  13695. $opt['border'] = array(0, 0, $linewidth);
  13696. $opt['bs'] = array('w'=>$linewidth, 's'=>'S');
  13697. break;
  13698. }
  13699. }
  13700. }
  13701. if (isset($prop['border']) AND is_array($prop['border'])) {
  13702. $opt['border'] = $prop['border'];
  13703. }
  13704. if (!isset($opt['mk'])) {
  13705. $opt['mk'] = array();
  13706. }
  13707. if (!isset($opt['mk']['if'])) {
  13708. $opt['mk']['if'] = array();
  13709. }
  13710. $opt['mk']['if']['a'] = array(0.5, 0.5);
  13711. // buttonAlignX: Controls how space is distributed from the left of the button face with respect to the icon.
  13712. if (isset($prop['buttonAlignX'])) {
  13713. $opt['mk']['if']['a'][0] = $prop['buttonAlignX'];
  13714. }
  13715. // buttonAlignY: Controls how unused space is distributed from the bottom of the button face with respect to the icon.
  13716. if (isset($prop['buttonAlignY'])) {
  13717. $opt['mk']['if']['a'][1] = $prop['buttonAlignY'];
  13718. }
  13719. // buttonFitBounds: If true, the extent to which the icon may be scaled is set to the bounds of the button field.
  13720. if (isset($prop['buttonFitBounds']) AND ($prop['buttonFitBounds'] == 'true')) {
  13721. $opt['mk']['if']['fb'] = true;
  13722. }
  13723. // buttonScaleHow: Controls how the icon is scaled (if necessary) to fit inside the button face.
  13724. if (isset($prop['buttonScaleHow'])) {
  13725. switch ($prop['buttonScaleHow']) {
  13726. case 'scaleHow.proportional': {
  13727. $opt['mk']['if']['s'] = 'P';
  13728. break;
  13729. }
  13730. case 'scaleHow.anamorphic': {
  13731. $opt['mk']['if']['s'] = 'A';
  13732. break;
  13733. }
  13734. }
  13735. }
  13736. // buttonScaleWhen: Controls when an icon is scaled to fit inside the button face.
  13737. if (isset($prop['buttonScaleWhen'])) {
  13738. switch ($prop['buttonScaleWhen']) {
  13739. case 'scaleWhen.always': {
  13740. $opt['mk']['if']['sw'] = 'A';
  13741. break;
  13742. }
  13743. case 'scaleWhen.never': {
  13744. $opt['mk']['if']['sw'] = 'N';
  13745. break;
  13746. }
  13747. case 'scaleWhen.tooBig': {
  13748. $opt['mk']['if']['sw'] = 'B';
  13749. break;
  13750. }
  13751. case 'scaleWhen.tooSmall': {
  13752. $opt['mk']['if']['sw'] = 'S';
  13753. break;
  13754. }
  13755. }
  13756. }
  13757. // buttonPosition: Controls how the text and the icon of the button are positioned with respect to each other within the button face.
  13758. if (isset($prop['buttonPosition'])) {
  13759. switch ($prop['buttonPosition']) {
  13760. case 0:
  13761. case 'position.textOnly': {
  13762. $opt['mk']['tp'] = 0;
  13763. break;
  13764. }
  13765. case 1:
  13766. case 'position.iconOnly': {
  13767. $opt['mk']['tp'] = 1;
  13768. break;
  13769. }
  13770. case 2:
  13771. case 'position.iconTextV': {
  13772. $opt['mk']['tp'] = 2;
  13773. break;
  13774. }
  13775. case 3:
  13776. case 'position.textIconV': {
  13777. $opt['mk']['tp'] = 3;
  13778. break;
  13779. }
  13780. case 4:
  13781. case 'position.iconTextH': {
  13782. $opt['mk']['tp'] = 4;
  13783. break;
  13784. }
  13785. case 5:
  13786. case 'position.textIconH': {
  13787. $opt['mk']['tp'] = 5;
  13788. break;
  13789. }
  13790. case 6:
  13791. case 'position.overlay': {
  13792. $opt['mk']['tp'] = 6;
  13793. break;
  13794. }
  13795. }
  13796. }
  13797. // fillColor: Specifies the background color for a field.
  13798. if (isset($prop['fillColor'])) {
  13799. if (is_array($prop['fillColor'])) {
  13800. $opt['mk']['bg'] = $prop['fillColor'];
  13801. } else {
  13802. $opt['mk']['bg'] = $this->convertHTMLColorToDec($prop['fillColor']);
  13803. }
  13804. }
  13805. // 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.
  13806. if (isset($prop['strokeColor'])) {
  13807. if (is_array($prop['strokeColor'])) {
  13808. $opt['mk']['bc'] = $prop['strokeColor'];
  13809. } else {
  13810. $opt['mk']['bc'] = $this->convertHTMLColorToDec($prop['strokeColor']);
  13811. }
  13812. }
  13813. // rotation: The rotation of a widget in counterclockwise increments.
  13814. if (isset($prop['rotation'])) {
  13815. $opt['mk']['r'] = $prop['rotation'];
  13816. }
  13817. // charLimit: Limits the number of characters that a user can type into a text field.
  13818. if (isset($prop['charLimit'])) {
  13819. $opt['maxlen'] = intval($prop['charLimit']);
  13820. }
  13821. if (!isset($ff)) {
  13822. $ff = 0;
  13823. }
  13824. // readonly: The read-only characteristic of a field. If a field is read-only, the user can see the field but cannot change it.
  13825. if (isset($prop['readonly']) AND ($prop['readonly'] == 'true')) {
  13826. $ff += 1 << 0;
  13827. }
  13828. // required: Specifies whether a field requires a value.
  13829. if (isset($prop['required']) AND ($prop['required'] == 'true')) {
  13830. $ff += 1 << 1;
  13831. }
  13832. // multiline: Controls how text is wrapped within the field.
  13833. if (isset($prop['multiline']) AND ($prop['multiline'] == 'true')) {
  13834. $ff += 1 << 12;
  13835. }
  13836. // password: Specifies whether the field should display asterisks when data is entered in the field.
  13837. if (isset($prop['password']) AND ($prop['password'] == 'true')) {
  13838. $ff += 1 << 13;
  13839. }
  13840. // NoToggleToOff: If set, exactly one radio button shall be selected at all times; selecting the currently selected button has no effect.
  13841. if (isset($prop['NoToggleToOff']) AND ($prop['NoToggleToOff'] == 'true')) {
  13842. $ff += 1 << 14;
  13843. }
  13844. // Radio: If set, the field is a set of radio buttons.
  13845. if (isset($prop['Radio']) AND ($prop['Radio'] == 'true')) {
  13846. $ff += 1 << 15;
  13847. }
  13848. // Pushbutton: If set, the field is a pushbutton that does not retain a permanent value.
  13849. if (isset($prop['Pushbutton']) AND ($prop['Pushbutton'] == 'true')) {
  13850. $ff += 1 << 16;
  13851. }
  13852. // Combo: If set, the field is a combo box; if clear, the field is a list box.
  13853. if (isset($prop['Combo']) AND ($prop['Combo'] == 'true')) {
  13854. $ff += 1 << 17;
  13855. }
  13856. // editable: Controls whether a combo box is editable.
  13857. if (isset($prop['editable']) AND ($prop['editable'] == 'true')) {
  13858. $ff += 1 << 18;
  13859. }
  13860. // Sort: If set, the field's option items shall be sorted alphabetically.
  13861. if (isset($prop['Sort']) AND ($prop['Sort'] == 'true')) {
  13862. $ff += 1 << 19;
  13863. }
  13864. // fileSelect: If true, sets the file-select flag in the Options tab of the text field (Field is Used for File Selection).
  13865. if (isset($prop['fileSelect']) AND ($prop['fileSelect'] == 'true')) {
  13866. $ff += 1 << 20;
  13867. }
  13868. // multipleSelection: If true, indicates that a list box allows a multiple selection of items.
  13869. if (isset($prop['multipleSelection']) AND ($prop['multipleSelection'] == 'true')) {
  13870. $ff += 1 << 21;
  13871. }
  13872. // doNotSpellCheck: If true, spell checking is not performed on this editable text field.
  13873. if (isset($prop['doNotSpellCheck']) AND ($prop['doNotSpellCheck'] == 'true')) {
  13874. $ff += 1 << 22;
  13875. }
  13876. // doNotScroll: If true, the text field does not scroll and the user, therefore, is limited by the rectangular region designed for the field.
  13877. if (isset($prop['doNotScroll']) AND ($prop['doNotScroll'] == 'true')) {
  13878. $ff += 1 << 23;
  13879. }
  13880. // 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.
  13881. if (isset($prop['comb']) AND ($prop['comb'] == 'true')) {
  13882. $ff += 1 << 24;
  13883. }
  13884. // 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.
  13885. if (isset($prop['radiosInUnison']) AND ($prop['radiosInUnison'] == 'true')) {
  13886. $ff += 1 << 25;
  13887. }
  13888. // richText: If true, the field allows rich text formatting.
  13889. if (isset($prop['richText']) AND ($prop['richText'] == 'true')) {
  13890. $ff += 1 << 25;
  13891. }
  13892. // commitOnSelChange: Controls whether a field value is committed after a selection change.
  13893. if (isset($prop['commitOnSelChange']) AND ($prop['commitOnSelChange'] == 'true')) {
  13894. $ff += 1 << 26;
  13895. }
  13896. $opt['ff'] = $ff;
  13897. // defaultValue: The default value of a field - that is, the value that the field is set to when the form is reset.
  13898. if (isset($prop['defaultValue'])) {
  13899. $opt['dv'] = $prop['defaultValue'];
  13900. }
  13901. $f = 4; // default value for annotation flags
  13902. // readonly: The read-only characteristic of a field. If a field is read-only, the user can see the field but cannot change it.
  13903. if (isset($prop['readonly']) AND ($prop['readonly'] == 'true')) {
  13904. $f += 1 << 6;
  13905. }
  13906. // display: Controls whether the field is hidden or visible on screen and in print.
  13907. if (isset($prop['display'])) {
  13908. if ($prop['display'] == 'display.visible') {
  13909. //
  13910. } elseif ($prop['display'] == 'display.hidden') {
  13911. $f += 1 << 1;
  13912. } elseif ($prop['display'] == 'display.noPrint') {
  13913. $f -= 1 << 2;
  13914. } elseif ($prop['display'] == 'display.noView') {
  13915. $f += 1 << 5;
  13916. }
  13917. }
  13918. $opt['f'] = $f;
  13919. // currentValueIndices: Reads and writes single or multiple values of a list box or combo box.
  13920. if (isset($prop['currentValueIndices']) AND is_array($prop['currentValueIndices'])) {
  13921. $opt['i'] = $prop['currentValueIndices'];
  13922. }
  13923. // value: The value of the field data that the user has entered.
  13924. if (isset($prop['value'])) {
  13925. if (is_array($prop['value'])) {
  13926. $opt['opt'] = array();
  13927. foreach ($prop['value'] AS $key => $optval) {
  13928. // exportValues: An array of strings representing the export values for the field.
  13929. if (isset($prop['exportValues'][$key])) {
  13930. $opt['opt'][$key] = array($prop['exportValues'][$key], $prop['value'][$key]);
  13931. } else {
  13932. $opt['opt'][$key] = $prop['value'][$key];
  13933. }
  13934. }
  13935. } else {
  13936. $opt['v'] = $prop['value'];
  13937. }
  13938. }
  13939. // richValue: This property specifies the text contents and formatting of a rich text field.
  13940. if (isset($prop['richValue'])) {
  13941. $opt['rv'] = $prop['richValue'];
  13942. }
  13943. // submitName: If nonempty, used during form submission instead of name. Only applicable if submitting in HTML format (that is, URL-encoded).
  13944. if (isset($prop['submitName'])) {
  13945. $opt['tm'] = $prop['submitName'];
  13946. }
  13947. // name: Fully qualified field name.
  13948. if (isset($prop['name'])) {
  13949. $opt['t'] = $prop['name'];
  13950. }
  13951. // userName: The user name (short description string) of the field.
  13952. if (isset($prop['userName'])) {
  13953. $opt['tu'] = $prop['userName'];
  13954. }
  13955. // highlight: Defines how a button reacts when a user clicks it.
  13956. if (isset($prop['highlight'])) {
  13957. switch ($prop['highlight']) {
  13958. case 'none':
  13959. case 'highlight.n': {
  13960. $opt['h'] = 'N';
  13961. break;
  13962. }
  13963. case 'invert':
  13964. case 'highlight.i': {
  13965. $opt['h'] = 'i';
  13966. break;
  13967. }
  13968. case 'push':
  13969. case 'highlight.p': {
  13970. $opt['h'] = 'P';
  13971. break;
  13972. }
  13973. case 'outline':
  13974. case 'highlight.o': {
  13975. $opt['h'] = 'O';
  13976. break;
  13977. }
  13978. }
  13979. }
  13980. // Unsupported options:
  13981. // - calcOrderIndex: Changes the calculation order of fields in the document.
  13982. // - delay: Delays the redrawing of a field's appearance.
  13983. // - defaultStyle: This property defines the default style attributes for the form field.
  13984. // - style: Allows the user to set the glyph style of a check box or radio button.
  13985. // - textColor, textFont, textSize
  13986. return $opt;
  13987. }
  13988. /**
  13989. * Set default properties for form fields.
  13990. * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
  13991. * @access public
  13992. * @author Nicola Asuni
  13993. * @since 4.8.000 (2009-09-06)
  13994. */
  13995. public function setFormDefaultProp($prop=array()) {
  13996. $this->default_form_prop = $prop;
  13997. }
  13998. /**
  13999. * Return the default properties for form fields.
  14000. * @return array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
  14001. * @access public
  14002. * @author Nicola Asuni
  14003. * @since 4.8.000 (2009-09-06)
  14004. */
  14005. public function getFormDefaultProp() {
  14006. return $this->default_form_prop;
  14007. }
  14008. /**
  14009. * Creates a text field
  14010. * @param string $name field name
  14011. * @param float $w Width of the rectangle
  14012. * @param float $h Height of the rectangle
  14013. * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
  14014. * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
  14015. * @param float $x Abscissa of the upper-left corner of the rectangle
  14016. * @param float $y Ordinate of the upper-left corner of the rectangle
  14017. * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
  14018. * @access public
  14019. * @author Nicola Asuni
  14020. * @since 4.8.000 (2009-09-07)
  14021. */
  14022. public function TextField($name, $w, $h, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
  14023. if ($x === '') {
  14024. $x = $this->x;
  14025. }
  14026. if ($y === '') {
  14027. $y = $this->y;
  14028. }
  14029. // check page for no-write regions and adapt page margins if necessary
  14030. $this->checkPageRegions($h, $x, $y);
  14031. if ($js) {
  14032. $this->_addfield('text', $name, $x, $y, $w, $h, $prop);
  14033. return;
  14034. }
  14035. // get default style
  14036. $prop = array_merge($this->getFormDefaultProp(), $prop);
  14037. // get annotation data
  14038. $popt = $this->getAnnotOptFromJSProp($prop);
  14039. // set default appearance stream
  14040. $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
  14041. $fontstyle = sprintf('/F%d %.2F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
  14042. $popt['da'] = $fontstyle;
  14043. $popt['ap'] = array();
  14044. $popt['ap']['n'] = 'q BT '.$fontstyle.' ET Q';
  14045. // merge options
  14046. $opt = array_merge($popt, $opt);
  14047. // remove some conflicting options
  14048. unset($opt['bs']);
  14049. // set remaining annotation data
  14050. $opt['Subtype'] = 'Widget';
  14051. $opt['ft'] = 'Tx';
  14052. $opt['t'] = $name;
  14053. /*
  14054. Additional annotation's parameters (check _putannotsobj() method):
  14055. //$opt['f']
  14056. //$opt['ap']
  14057. //$opt['as']
  14058. //$opt['bs']
  14059. //$opt['be']
  14060. //$opt['c']
  14061. //$opt['border']
  14062. //$opt['h']
  14063. //$opt['mk']
  14064. //$opt['mk']['r']
  14065. //$opt['mk']['bc']
  14066. //$opt['mk']['bg']
  14067. //$opt['mk']['ca']
  14068. //$opt['mk']['rc']
  14069. //$opt['mk']['ac']
  14070. //$opt['mk']['i']
  14071. //$opt['mk']['ri']
  14072. //$opt['mk']['ix']
  14073. //$opt['mk']['if']
  14074. //$opt['mk']['if']['sw']
  14075. //$opt['mk']['if']['s']
  14076. //$opt['mk']['if']['a']
  14077. //$opt['mk']['if']['fb']
  14078. //$opt['mk']['tp']
  14079. //$opt['tu']
  14080. //$opt['tm']
  14081. //$opt['ff']
  14082. //$opt['v']
  14083. //$opt['dv']
  14084. //$opt['a']
  14085. //$opt['aa']
  14086. //$opt['q']
  14087. */
  14088. $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
  14089. if ($this->rtl) {
  14090. $this->x -= $w;
  14091. } else {
  14092. $this->x += $w;
  14093. }
  14094. }
  14095. /**
  14096. * Creates a RadioButton field
  14097. * @param string $name field name
  14098. * @param int $w width
  14099. * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
  14100. * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
  14101. * @param string $onvalue value to be returned if selected.
  14102. * @param boolean $checked define the initial state.
  14103. * @param float $x Abscissa of the upper-left corner of the rectangle
  14104. * @param float $y Ordinate of the upper-left corner of the rectangle
  14105. * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
  14106. * @access public
  14107. * @author Nicola Asuni
  14108. * @since 4.8.000 (2009-09-07)
  14109. */
  14110. public function RadioButton($name, $w, $prop=array(), $opt=array(), $onvalue='On', $checked=false, $x='', $y='', $js=false) {
  14111. if ($x === '') {
  14112. $x = $this->x;
  14113. }
  14114. if ($y === '') {
  14115. $y = $this->y;
  14116. }
  14117. // check page for no-write regions and adapt page margins if necessary
  14118. $this->checkPageRegions($w, $x, $y);
  14119. if ($js) {
  14120. $this->_addfield('radiobutton', $name, $x, $y, $w, $w, $prop);
  14121. return;
  14122. }
  14123. if ($this->empty_string($onvalue)) {
  14124. $onvalue = 'On';
  14125. }
  14126. if ($checked) {
  14127. $defval = $onvalue;
  14128. } else {
  14129. $defval = 'Off';
  14130. }
  14131. // set data for parent group
  14132. if (!isset($this->radiobutton_groups[$this->page])) {
  14133. $this->radiobutton_groups[$this->page] = array();
  14134. }
  14135. if (!isset($this->radiobutton_groups[$this->page][$name])) {
  14136. $this->radiobutton_groups[$this->page][$name] = array();
  14137. ++$this->n;
  14138. $this->radiobutton_groups[$this->page][$name]['n'] = $this->n;
  14139. $this->radio_groups[] = $this->n;
  14140. $kid = ($this->n + 2);
  14141. } else {
  14142. $kid = ($this->n + 1);
  14143. }
  14144. // save object ID to be added on Kids entry on parent object
  14145. $this->radiobutton_groups[$this->page][$name][] = array('kid' => $kid, 'def' => $defval);
  14146. // get default style
  14147. $prop = array_merge($this->getFormDefaultProp(), $prop);
  14148. $prop['NoToggleToOff'] = 'true';
  14149. $prop['Radio'] = 'true';
  14150. $prop['borderStyle'] = 'inset';
  14151. // get annotation data
  14152. $popt = $this->getAnnotOptFromJSProp($prop);
  14153. // set additional default values
  14154. $font = 'zapfdingbats';
  14155. $this->AddFont($font);
  14156. $tmpfont = $this->getFontBuffer($font);
  14157. $this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
  14158. $fontstyle = sprintf('/F%d %.2F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
  14159. $popt['da'] = $fontstyle;
  14160. $popt['ap'] = array();
  14161. $popt['ap']['n'] = array();
  14162. $popt['ap']['n'][$onvalue] = 'q BT '.$fontstyle.' 0 0 Td (8) Tj ET Q';
  14163. $popt['ap']['n']['Off'] = 'q BT '.$fontstyle.' 0 0 Td (8) Tj ET Q';
  14164. if (!isset($popt['mk'])) {
  14165. $popt['mk'] = array();
  14166. }
  14167. $popt['mk']['ca'] = '(l)';
  14168. // merge options
  14169. $opt = array_merge($popt, $opt);
  14170. // set remaining annotation data
  14171. $opt['Subtype'] = 'Widget';
  14172. $opt['ft'] = 'Btn';
  14173. if ($checked) {
  14174. $opt['v'] = array('/'.$onvalue);
  14175. $opt['as'] = $onvalue;
  14176. } else {
  14177. $opt['as'] = 'Off';
  14178. }
  14179. $this->Annotation($x, $y, $w, $w, $name, $opt, 0);
  14180. if ($this->rtl) {
  14181. $this->x -= $w;
  14182. } else {
  14183. $this->x += $w;
  14184. }
  14185. }
  14186. /**
  14187. * Creates a List-box field
  14188. * @param string $name field name
  14189. * @param int $w width
  14190. * @param int $h height
  14191. * @param array $values array containing the list of values.
  14192. * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
  14193. * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
  14194. * @param float $x Abscissa of the upper-left corner of the rectangle
  14195. * @param float $y Ordinate of the upper-left corner of the rectangle
  14196. * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
  14197. * @access public
  14198. * @author Nicola Asuni
  14199. * @since 4.8.000 (2009-09-07)
  14200. */
  14201. public function ListBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
  14202. if ($x === '') {
  14203. $x = $this->x;
  14204. }
  14205. if ($y === '') {
  14206. $y = $this->y;
  14207. }
  14208. // check page for no-write regions and adapt page margins if necessary
  14209. $this->checkPageRegions($h, $x, $y);
  14210. if ($js) {
  14211. $this->_addfield('listbox', $name, $x, $y, $w, $h, $prop);
  14212. $s = '';
  14213. foreach ($values as $value) {
  14214. $s .= "'".addslashes($value)."',";
  14215. }
  14216. $this->javascript .= 'f'.$name.'.setItems(['.substr($s, 0, -1)."]);\n";
  14217. return;
  14218. }
  14219. // get default style
  14220. $prop = array_merge($this->getFormDefaultProp(), $prop);
  14221. // get annotation data
  14222. $popt = $this->getAnnotOptFromJSProp($prop);
  14223. // set additional default values
  14224. $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
  14225. $fontstyle = sprintf('/F%d %.2F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
  14226. $popt['da'] = $fontstyle;
  14227. $popt['ap'] = array();
  14228. $popt['ap']['n'] = 'q BT '.$fontstyle.' ET Q';
  14229. // merge options
  14230. $opt = array_merge($popt, $opt);
  14231. // set remaining annotation data
  14232. $opt['Subtype'] = 'Widget';
  14233. $opt['ft'] = 'Ch';
  14234. $opt['t'] = $name;
  14235. $opt['opt'] = $values;
  14236. $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
  14237. if ($this->rtl) {
  14238. $this->x -= $w;
  14239. } else {
  14240. $this->x += $w;
  14241. }
  14242. }
  14243. /**
  14244. * Creates a Combo-box field
  14245. * @param string $name field name
  14246. * @param int $w width
  14247. * @param int $h height
  14248. * @param array $values array containing the list of values.
  14249. * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
  14250. * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
  14251. * @param float $x Abscissa of the upper-left corner of the rectangle
  14252. * @param float $y Ordinate of the upper-left corner of the rectangle
  14253. * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
  14254. * @access public
  14255. * @author Nicola Asuni
  14256. * @since 4.8.000 (2009-09-07)
  14257. */
  14258. public function ComboBox($name, $w, $h, $values, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
  14259. if ($x === '') {
  14260. $x = $this->x;
  14261. }
  14262. if ($y === '') {
  14263. $y = $this->y;
  14264. }
  14265. // check page for no-write regions and adapt page margins if necessary
  14266. $this->checkPageRegions($h, $x, $y);
  14267. if ($js) {
  14268. $this->_addfield('combobox', $name, $x, $y, $w, $h, $prop);
  14269. $s = '';
  14270. foreach ($values as $value) {
  14271. $s .= "'".addslashes($value)."',";
  14272. }
  14273. $this->javascript .= 'f'.$name.'.setItems(['.substr($s, 0, -1)."]);\n";
  14274. return;
  14275. }
  14276. // get default style
  14277. $prop = array_merge($this->getFormDefaultProp(), $prop);
  14278. $prop['Combo'] = true;
  14279. // get annotation data
  14280. $popt = $this->getAnnotOptFromJSProp($prop);
  14281. // set additional default options
  14282. $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
  14283. $fontstyle = sprintf('/F%d %.2F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
  14284. $popt['da'] = $fontstyle;
  14285. $popt['ap'] = array();
  14286. $popt['ap']['n'] = 'q BT '.$fontstyle.' ET Q';
  14287. // merge options
  14288. $opt = array_merge($popt, $opt);
  14289. // set remaining annotation data
  14290. $opt['Subtype'] = 'Widget';
  14291. $opt['ft'] = 'Ch';
  14292. $opt['t'] = $name;
  14293. $opt['opt'] = $values;
  14294. $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
  14295. if ($this->rtl) {
  14296. $this->x -= $w;
  14297. } else {
  14298. $this->x += $w;
  14299. }
  14300. }
  14301. /**
  14302. * Creates a CheckBox field
  14303. * @param string $name field name
  14304. * @param int $w width
  14305. * @param boolean $checked define the initial state.
  14306. * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
  14307. * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
  14308. * @param string $onvalue value to be returned if selected.
  14309. * @param float $x Abscissa of the upper-left corner of the rectangle
  14310. * @param float $y Ordinate of the upper-left corner of the rectangle
  14311. * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
  14312. * @access public
  14313. * @author Nicola Asuni
  14314. * @since 4.8.000 (2009-09-07)
  14315. */
  14316. public function CheckBox($name, $w, $checked=false, $prop=array(), $opt=array(), $onvalue='Yes', $x='', $y='', $js=false) {
  14317. if ($x === '') {
  14318. $x = $this->x;
  14319. }
  14320. if ($y === '') {
  14321. $y = $this->y;
  14322. }
  14323. // check page for no-write regions and adapt page margins if necessary
  14324. $this->checkPageRegions($w, $x, $y);
  14325. if ($js) {
  14326. $this->_addfield('checkbox', $name, $x, $y, $w, $w, $prop);
  14327. return;
  14328. }
  14329. if (!isset($prop['value'])) {
  14330. $prop['value'] = array('Yes');
  14331. }
  14332. // get default style
  14333. $prop = array_merge($this->getFormDefaultProp(), $prop);
  14334. $prop['borderStyle'] = 'inset';
  14335. // get annotation data
  14336. $popt = $this->getAnnotOptFromJSProp($prop);
  14337. // set additional default options
  14338. $font = 'zapfdingbats';
  14339. $this->AddFont($font);
  14340. $tmpfont = $this->getFontBuffer($font);
  14341. $this->annotation_fonts[$tmpfont['fontkey']] = $tmpfont['i'];
  14342. $fontstyle = sprintf('/F%d %.2F Tf %s', $tmpfont['i'], $this->FontSizePt, $this->TextColor);
  14343. $popt['da'] = $fontstyle;
  14344. $popt['ap'] = array();
  14345. $popt['ap']['n'] = array();
  14346. $popt['ap']['n']['Yes'] = 'q BT '.$fontstyle.' 0 0 Td (8) Tj ET Q';
  14347. $popt['ap']['n']['Off'] = 'q BT '.$fontstyle.' 0 0 Td (8) Tj ET Q';
  14348. // merge options
  14349. $opt = array_merge($popt, $opt);
  14350. // set remaining annotation data
  14351. $opt['Subtype'] = 'Widget';
  14352. $opt['ft'] = 'Btn';
  14353. $opt['t'] = $name;
  14354. $opt['opt'] = array($onvalue);
  14355. if ($checked) {
  14356. $opt['v'] = array('/0');
  14357. $opt['as'] = 'Yes';
  14358. } else {
  14359. $opt['v'] = array('/Off');
  14360. $opt['as'] = 'Off';
  14361. }
  14362. $this->Annotation($x, $y, $w, $w, $name, $opt, 0);
  14363. if ($this->rtl) {
  14364. $this->x -= $w;
  14365. } else {
  14366. $this->x += $w;
  14367. }
  14368. }
  14369. /**
  14370. * Creates a button field
  14371. * @param string $name field name
  14372. * @param int $w width
  14373. * @param int $h height
  14374. * @param string $caption caption.
  14375. * @param mixed $action action triggered by pressing the button. Use a string to specify a javascript action. Use an array to specify a form action options as on section 12.7.5 of PDF32000_2008.
  14376. * @param array $prop javascript field properties. Possible values are described on official Javascript for Acrobat API reference.
  14377. * @param array $opt annotation parameters. Possible values are described on official PDF32000_2008 reference.
  14378. * @param float $x Abscissa of the upper-left corner of the rectangle
  14379. * @param float $y Ordinate of the upper-left corner of the rectangle
  14380. * @param boolean $js if true put the field using JavaScript (requires Acrobat Writer to be rendered).
  14381. * @access public
  14382. * @author Nicola Asuni
  14383. * @since 4.8.000 (2009-09-07)
  14384. */
  14385. public function Button($name, $w, $h, $caption, $action, $prop=array(), $opt=array(), $x='', $y='', $js=false) {
  14386. if ($x === '') {
  14387. $x = $this->x;
  14388. }
  14389. if ($y === '') {
  14390. $y = $this->y;
  14391. }
  14392. // check page for no-write regions and adapt page margins if necessary
  14393. $this->checkPageRegions($h, $x, $y);
  14394. if ($js) {
  14395. $this->_addfield('button', $name, $this->x, $this->y, $w, $h, $prop);
  14396. $this->javascript .= 'f'.$name.".buttonSetCaption('".addslashes($caption)."');\n";
  14397. $this->javascript .= 'f'.$name.".setAction('MouseUp','".addslashes($action)."');\n";
  14398. $this->javascript .= 'f'.$name.".highlight='push';\n";
  14399. $this->javascript .= 'f'.$name.".print=false;\n";
  14400. return;
  14401. }
  14402. // get default style
  14403. $prop = array_merge($this->getFormDefaultProp(), $prop);
  14404. $prop['Pushbutton'] = 'true';
  14405. $prop['highlight'] = 'push';
  14406. $prop['display'] = 'display.noPrint';
  14407. // get annotation data
  14408. $popt = $this->getAnnotOptFromJSProp($prop);
  14409. $this->annotation_fonts[$this->CurrentFont['fontkey']] = $this->CurrentFont['i'];
  14410. $fontstyle = sprintf('/F%d %.2F Tf %s', $this->CurrentFont['i'], $this->FontSizePt, $this->TextColor);
  14411. $popt['da'] = $fontstyle;
  14412. $popt['ap'] = array();
  14413. $popt['ap']['n'] = 'q BT '.$fontstyle.' ET Q';
  14414. // set additional default options
  14415. if (!isset($popt['mk'])) {
  14416. $popt['mk'] = array();
  14417. }
  14418. $ann_obj_id = ($this->n + 1);
  14419. if (!empty($action) AND !is_array($action)) {
  14420. $ann_obj_id = ($this->n + 2);
  14421. }
  14422. $popt['mk']['ca'] = $this->_textstring($caption, $ann_obj_id);
  14423. $popt['mk']['rc'] = $this->_textstring($caption, $ann_obj_id);
  14424. $popt['mk']['ac'] = $this->_textstring($caption, $ann_obj_id);
  14425. // merge options
  14426. $opt = array_merge($popt, $opt);
  14427. // set remaining annotation data
  14428. $opt['Subtype'] = 'Widget';
  14429. $opt['ft'] = 'Btn';
  14430. $opt['t'] = $caption;
  14431. $opt['v'] = $name;
  14432. if (!empty($action)) {
  14433. if (is_array($action)) {
  14434. // form action options as on section 12.7.5 of PDF32000_2008.
  14435. $opt['aa'] = '/D <<';
  14436. $bmode = array('SubmitForm', 'ResetForm', 'ImportData');
  14437. foreach ($action AS $key => $val) {
  14438. if (($key == 'S') AND in_array($val, $bmode)) {
  14439. $opt['aa'] .= ' /S /'.$val;
  14440. } elseif (($key == 'F') AND (!empty($val))) {
  14441. $opt['aa'] .= ' /F '.$this->_datastring($val, $ann_obj_id);
  14442. } elseif (($key == 'Fields') AND is_array($val) AND !empty($val)) {
  14443. $opt['aa'] .= ' /Fields [';
  14444. foreach ($val AS $field) {
  14445. $opt['aa'] .= ' '.$this->_textstring($field, $ann_obj_id);
  14446. }
  14447. $opt['aa'] .= ']';
  14448. } elseif (($key == 'Flags')) {
  14449. $ff = 0;
  14450. if (is_array($val)) {
  14451. foreach ($val AS $flag) {
  14452. switch ($flag) {
  14453. case 'Include/Exclude': {
  14454. $ff += 1 << 0;
  14455. break;
  14456. }
  14457. case 'IncludeNoValueFields': {
  14458. $ff += 1 << 1;
  14459. break;
  14460. }
  14461. case 'ExportFormat': {
  14462. $ff += 1 << 2;
  14463. break;
  14464. }
  14465. case 'GetMethod': {
  14466. $ff += 1 << 3;
  14467. break;
  14468. }
  14469. case 'SubmitCoordinates': {
  14470. $ff += 1 << 4;
  14471. break;
  14472. }
  14473. case 'XFDF': {
  14474. $ff += 1 << 5;
  14475. break;
  14476. }
  14477. case 'IncludeAppendSaves': {
  14478. $ff += 1 << 6;
  14479. break;
  14480. }
  14481. case 'IncludeAnnotations': {
  14482. $ff += 1 << 7;
  14483. break;
  14484. }
  14485. case 'SubmitPDF': {
  14486. $ff += 1 << 8;
  14487. break;
  14488. }
  14489. case 'CanonicalFormat': {
  14490. $ff += 1 << 9;
  14491. break;
  14492. }
  14493. case 'ExclNonUserAnnots': {
  14494. $ff += 1 << 10;
  14495. break;
  14496. }
  14497. case 'ExclFKey': {
  14498. $ff += 1 << 11;
  14499. break;
  14500. }
  14501. case 'EmbedForm': {
  14502. $ff += 1 << 13;
  14503. break;
  14504. }
  14505. }
  14506. }
  14507. } else {
  14508. $ff = intval($val);
  14509. }
  14510. $opt['aa'] .= ' /Flags '.$ff;
  14511. }
  14512. }
  14513. $opt['aa'] .= ' >>';
  14514. } else {
  14515. // Javascript action or raw action command
  14516. $js_obj_id = $this->addJavascriptObject($action);
  14517. $opt['aa'] = '/D '.$js_obj_id.' 0 R';
  14518. }
  14519. }
  14520. $this->Annotation($x, $y, $w, $h, $name, $opt, 0);
  14521. if ($this->rtl) {
  14522. $this->x -= $w;
  14523. } else {
  14524. $this->x += $w;
  14525. }
  14526. }
  14527. // --- END FORMS FIELDS ------------------------------------------------
  14528. /**
  14529. * Add certification signature (DocMDP or UR3)
  14530. * You can set only one signature type
  14531. * @access protected
  14532. * @author Nicola Asuni
  14533. * @since 4.6.008 (2009-05-07)
  14534. */
  14535. protected function _putsignature() {
  14536. if ((!$this->sign) OR (!isset($this->signature_data['cert_type']))) {
  14537. return;
  14538. }
  14539. $out = $this->_getobj($this->sig_obj_id + 1)."\n";
  14540. $out .= '<< /Type /Sig';
  14541. $out .= ' /Filter /Adobe.PPKLite';
  14542. $out .= ' /SubFilter /adbe.pkcs7.detached';
  14543. $out .= ' '.$this->byterange_string;
  14544. $out .= ' /Contents<'.str_repeat('0', $this->signature_max_length).'>';
  14545. $out .= ' /Reference ['; // array of signature reference dictionaries
  14546. $out .= ' << /Type /SigRef';
  14547. if ($this->signature_data['cert_type'] > 0) {
  14548. $out .= ' /TransformMethod /DocMDP';
  14549. $out .= ' /TransformParams <<';
  14550. $out .= ' /Type /TransformParams';
  14551. $out .= ' /V /1.2';
  14552. $out .= ' /P '.$this->signature_data['cert_type'];
  14553. } else {
  14554. $out .= ' /TransformMethod /UR3';
  14555. $out .= ' /TransformParams <<';
  14556. $out .= ' /Type /TransformParams';
  14557. $out .= ' /V /2.2';
  14558. if (!$this->empty_string($this->ur['document'])) {
  14559. $out .= ' /Document['.$this->ur['document'].']';
  14560. }
  14561. if (!$this->empty_string($this->ur['form'])) {
  14562. $out .= ' /Form['.$this->ur['form'].']';
  14563. }
  14564. if (!$this->empty_string($this->ur['signature'])) {
  14565. $out .= ' /Signature['.$this->ur['signature'].']';
  14566. }
  14567. if (!$this->empty_string($this->ur['annots'])) {
  14568. $out .= ' /Annots['.$this->ur['annots'].']';
  14569. }
  14570. if (!$this->empty_string($this->ur['ef'])) {
  14571. $out .= ' /EF['.$this->ur['ef'].']';
  14572. }
  14573. if (!$this->empty_string($this->ur['formex'])) {
  14574. $out .= ' /FormEX['.$this->ur['formex'].']';
  14575. }
  14576. }
  14577. $out .= ' >>'; // close TransformParams
  14578. // optional digest data (values must be calculated and replaced later)
  14579. //$out .= ' /Data ********** 0 R';
  14580. //$out .= ' /DigestMethod/MD5';
  14581. //$out .= ' /DigestLocation[********** 34]';
  14582. //$out .= ' /DigestValue<********************************>';
  14583. $out .= ' >>';
  14584. $out .= ' ]'; // end of reference
  14585. if (isset($this->signature_data['info']['Name']) AND !$this->empty_string($this->signature_data['info']['Name'])) {
  14586. $out .= ' /Name '.$this->_textstring($this->signature_data['info']['Name']);
  14587. }
  14588. if (isset($this->signature_data['info']['Location']) AND !$this->empty_string($this->signature_data['info']['Location'])) {
  14589. $out .= ' /Location '.$this->_textstring($this->signature_data['info']['Location']);
  14590. }
  14591. if (isset($this->signature_data['info']['Reason']) AND !$this->empty_string($this->signature_data['info']['Reason'])) {
  14592. $out .= ' /Reason '.$this->_textstring($this->signature_data['info']['Reason']);
  14593. }
  14594. if (isset($this->signature_data['info']['ContactInfo']) AND !$this->empty_string($this->signature_data['info']['ContactInfo'])) {
  14595. $out .= ' /ContactInfo '.$this->_textstring($this->signature_data['info']['ContactInfo']);
  14596. }
  14597. $out .= ' /M '.$this->_datestring();
  14598. $out .= ' >>';
  14599. $out .= "\n".'endobj';
  14600. $this->_out($out);
  14601. }
  14602. /**
  14603. * Set User's Rights for PDF Reader
  14604. * WARNING: This is experimental and currently do not work.
  14605. * Check the PDF Reference 8.7.1 Transform Methods,
  14606. * Table 8.105 Entries in the UR transform parameters dictionary
  14607. * @param boolean $enable if true enable user's rights on PDF reader
  14608. * @param string $document Names specifying additional document-wide usage rights for the document. The only defined value is "/FullSave", which permits a user to save the document along with modified form and/or annotation data.
  14609. * @param string $annots Names specifying additional annotation-related usage rights for the document. Valid names in PDF 1.5 and later are /Create/Delete/Modify/Copy/Import/Export, which permit the user to perform the named operation on annotations.
  14610. * @param string $form Names specifying additional form-field-related usage rights for the document. Valid names are: /Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate
  14611. * @param string $signature Names specifying additional signature-related usage rights for the document. The only defined value is /Modify, which permits a user to apply a digital signature to an existing signature form field or clear a signed signature form field.
  14612. * @param string $ef Names specifying additional usage rights for named embedded files in the document. Valid names are /Create/Delete/Modify/Import, which permit the user to perform the named operation on named embedded files
  14613. Names specifying additional embedded-files-related usage rights for the document.
  14614. * @param string $formex Names specifying additional form-field-related usage rights. The only valid name is BarcodePlaintext, which permits text form field data to be encoded as a plaintext two-dimensional barcode.
  14615. * @access public
  14616. * @author Nicola Asuni
  14617. * @since 2.9.000 (2008-03-26)
  14618. */
  14619. public function setUserRights(
  14620. $enable=true,
  14621. $document='/FullSave',
  14622. $annots='/Create/Delete/Modify/Copy/Import/Export',
  14623. $form='/Add/Delete/FillIn/Import/Export/SubmitStandalone/SpawnTemplate',
  14624. $signature='/Modify',
  14625. $ef='/Create/Delete/Modify/Import',
  14626. $formex='') {
  14627. $this->ur['enabled'] = $enable;
  14628. $this->ur['document'] = $document;
  14629. $this->ur['annots'] = $annots;
  14630. $this->ur['form'] = $form;
  14631. $this->ur['signature'] = $signature;
  14632. $this->ur['ef'] = $ef;
  14633. $this->ur['formex'] = $formex;
  14634. if (!$this->sign) {
  14635. $this->setSignature('', '', '', '', 0, array());
  14636. }
  14637. }
  14638. /**
  14639. * Enable document signature (requires the OpenSSL Library).
  14640. * The digital signature improve document authenticity and integrity and allows o enable extra features on Acrobat Reader.
  14641. * To create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
  14642. * To export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
  14643. * To convert pfx certificate to pem: openssl pkcs12 -in tcpdf.pfx -out tcpdf.crt -nodes
  14644. * @param mixed $signing_cert signing certificate (string or filename prefixed with 'file://')
  14645. * @param mixed $private_key private key (string or filename prefixed with 'file://')
  14646. * @param string $private_key_password password
  14647. * @param string $extracerts specifies the name of a file containing a bunch of extra certificates to include in the signature which can for example be used to help the recipient to verify the certificate that you used.
  14648. * @param int $cert_type The access permissions granted for this document. Valid values shall be: 1 = No changes to the document shall be permitted; any change to the document shall invalidate the signature; 2 = Permitted changes shall be filling in forms, instantiating page templates, and signing; other changes shall invalidate the signature; 3 = Permitted changes shall be the same as for 2, as well as annotation creation, deletion, and modification; other changes shall invalidate the signature.
  14649. * @param array $info array of option information: Name, Location, Reason, ContactInfo.
  14650. * @access public
  14651. * @author Nicola Asuni
  14652. * @since 4.6.005 (2009-04-24)
  14653. */
  14654. public function setSignature($signing_cert='', $private_key='', $private_key_password='', $extracerts='', $cert_type=2, $info=array()) {
  14655. // to create self-signed signature: openssl req -x509 -nodes -days 365000 -newkey rsa:1024 -keyout tcpdf.crt -out tcpdf.crt
  14656. // to export crt to p12: openssl pkcs12 -export -in tcpdf.crt -out tcpdf.p12
  14657. // to convert pfx certificate to pem: openssl
  14658. // OpenSSL> pkcs12 -in <cert.pfx> -out <cert.crt> -nodes
  14659. $this->sign = true;
  14660. ++$this->n;
  14661. $this->sig_obj_id = $this->n; // signature widget
  14662. ++$this->n; // signature object ($this->sig_obj_id + 1)
  14663. $this->signature_data = array();
  14664. if (strlen($signing_cert) == 0) {
  14665. $signing_cert = 'file://'.dirname(__FILE__).'/tcpdf.crt';
  14666. $private_key_password = 'tcpdfdemo';
  14667. }
  14668. if (strlen($private_key) == 0) {
  14669. $private_key = $signing_cert;
  14670. }
  14671. $this->signature_data['signcert'] = $signing_cert;
  14672. $this->signature_data['privkey'] = $private_key;
  14673. $this->signature_data['password'] = $private_key_password;
  14674. $this->signature_data['extracerts'] = $extracerts;
  14675. $this->signature_data['cert_type'] = $cert_type;
  14676. $this->signature_data['info'] = $info;
  14677. }
  14678. /**
  14679. * Set the digital signature appearance (a cliccable rectangle area to get signature properties)
  14680. * @param float $x Abscissa of the upper-left corner.
  14681. * @param float $y Ordinate of the upper-left corner.
  14682. * @param float $w Width of the signature area.
  14683. * @param float $h Height of the signature area.
  14684. * @param int $page option page number (if < 0 the current page is used).
  14685. * @access public
  14686. * @author Nicola Asuni
  14687. * @since 5.3.011 (2010-06-17)
  14688. */
  14689. public function setSignatureAppearance($x=0, $y=0, $w=0, $h=0, $page=-1) {
  14690. if (($page < 1) OR ($page > $this->numpages)) {
  14691. $this->signature_appearance['page'] = $this->page;
  14692. } else {
  14693. $this->signature_appearance['page'] = intval($page);
  14694. }
  14695. $a = $x * $this->k;
  14696. $b = $this->pagedim[($this->signature_appearance['page'])]['h'] - (($y + $h) * $this->k);
  14697. $c = $w * $this->k;
  14698. $d = $h * $this->k;
  14699. $this->signature_appearance['rect'] = sprintf('%.2F %.2F %.2F %.2F', $a, $b, $a+$c, $b+$d);
  14700. }
  14701. /**
  14702. * Create a new page group.
  14703. * NOTE: call this function before calling AddPage()
  14704. * @param int $page starting group page (leave empty for next page).
  14705. * @access public
  14706. * @since 3.0.000 (2008-03-27)
  14707. */
  14708. public function startPageGroup($page='') {
  14709. if (empty($page)) {
  14710. $page = $this->page + 1;
  14711. }
  14712. $this->newpagegroup[$page] = true;
  14713. }
  14714. /**
  14715. * Defines an alias for the total number of pages.
  14716. * It will be substituted as the document is closed.
  14717. * @param string $alias The alias.
  14718. * @access public
  14719. * @since 1.4
  14720. * @see getAliasNbPages(), PageNo(), Footer()
  14721. */
  14722. public function AliasNbPages($alias='{nb}') {
  14723. $this->AliasNbPages = $alias;
  14724. }
  14725. /**
  14726. * Returns the string alias used for the total number of pages.
  14727. * If the current font is unicode type, the returned string is surrounded by additional curly braces.
  14728. * @return string
  14729. * @access public
  14730. * @since 4.0.018 (2008-08-08)
  14731. * @see AliasNbPages(), PageNo(), Footer()
  14732. */
  14733. public function getAliasNbPages() {
  14734. if ($this->isUnicodeFont()) {
  14735. return '{'.$this->AliasNbPages.'}';
  14736. }
  14737. return $this->AliasNbPages;
  14738. }
  14739. /**
  14740. * Defines an alias for the page number.
  14741. * It will be substituted as the document is closed.
  14742. * @param string $alias The alias.
  14743. * @access public
  14744. * @since 4.5.000 (2009-01-02)
  14745. * @see getAliasNbPages(), PageNo(), Footer()
  14746. */
  14747. public function AliasNumPage($alias='{pnb}') {
  14748. //Define an alias for total number of pages
  14749. $this->AliasNumPage = $alias;
  14750. }
  14751. /**
  14752. * Returns the string alias used for the page number.
  14753. * If the current font is unicode type, the returned string is surrounded by additional curly braces.
  14754. * @return string
  14755. * @access public
  14756. * @since 4.5.000 (2009-01-02)
  14757. * @see AliasNbPages(), PageNo(), Footer()
  14758. */
  14759. public function getAliasNumPage() {
  14760. if ($this->isUnicodeFont()) {
  14761. return '{'.$this->AliasNumPage.'}';
  14762. }
  14763. return $this->AliasNumPage;
  14764. }
  14765. /**
  14766. * Return the current page in the group.
  14767. * @return current page in the group
  14768. * @access public
  14769. * @since 3.0.000 (2008-03-27)
  14770. */
  14771. public function getGroupPageNo() {
  14772. return $this->pagegroups[$this->currpagegroup];
  14773. }
  14774. /**
  14775. * Returns the current group page number formatted as a string.
  14776. * @access public
  14777. * @since 4.3.003 (2008-11-18)
  14778. * @see PaneNo(), formatPageNumber()
  14779. */
  14780. public function getGroupPageNoFormatted() {
  14781. return $this->formatPageNumber($this->getGroupPageNo());
  14782. }
  14783. /**
  14784. * Return the alias of the current page group
  14785. * If the current font is unicode type, the returned string is surrounded by additional curly braces.
  14786. * (will be replaced by the total number of pages in this group).
  14787. * @return alias of the current page group
  14788. * @access public
  14789. * @since 3.0.000 (2008-03-27)
  14790. */
  14791. public function getPageGroupAlias() {
  14792. if ($this->isUnicodeFont()) {
  14793. return '{'.$this->currpagegroup.'}';
  14794. }
  14795. return $this->currpagegroup;
  14796. }
  14797. /**
  14798. * Return the alias for the page number on the current page group
  14799. * If the current font is unicode type, the returned string is surrounded by additional curly braces.
  14800. * (will be replaced by the total number of pages in this group).
  14801. * @return alias of the current page group
  14802. * @access public
  14803. * @since 4.5.000 (2009-01-02)
  14804. */
  14805. public function getPageNumGroupAlias() {
  14806. if ($this->isUnicodeFont()) {
  14807. return '{'.str_replace('{nb', '{pnb', $this->currpagegroup).'}';
  14808. }
  14809. return str_replace('{nb', '{pnb', $this->currpagegroup);
  14810. }
  14811. /**
  14812. * Format the page numbers.
  14813. * This method can be overriden for custom formats.
  14814. * @param int $num page number
  14815. * @access protected
  14816. * @since 4.2.005 (2008-11-06)
  14817. */
  14818. protected function formatPageNumber($num) {
  14819. return number_format((float)$num, 0, '', '.');
  14820. }
  14821. /**
  14822. * Format the page numbers on the Table Of Content.
  14823. * This method can be overriden for custom formats.
  14824. * @param int $num page number
  14825. * @access protected
  14826. * @since 4.5.001 (2009-01-04)
  14827. * @see addTOC(), addHTMLTOC()
  14828. */
  14829. protected function formatTOCPageNumber($num) {
  14830. return number_format((float)$num, 0, '', '.');
  14831. }
  14832. /**
  14833. * Returns the current page number formatted as a string.
  14834. * @access public
  14835. * @since 4.2.005 (2008-11-06)
  14836. * @see PaneNo(), formatPageNumber()
  14837. */
  14838. public function PageNoFormatted() {
  14839. return $this->formatPageNumber($this->PageNo());
  14840. }
  14841. /**
  14842. * Put visibility settings.
  14843. * @access protected
  14844. * @since 3.0.000 (2008-03-27)
  14845. */
  14846. protected function _putocg() {
  14847. $this->n_ocg_print = $this->_newobj();
  14848. $this->_out('<< /Type /OCG /Name '.$this->_textstring('print', $this->n_ocg_print).' /Usage << /Print <</PrintState /ON>> /View <</ViewState /OFF>> >> >>'."\n".'endobj');
  14849. $this->n_ocg_view = $this->_newobj();
  14850. $this->_out('<< /Type /OCG /Name '.$this->_textstring('view', $this->n_ocg_view).' /Usage << /Print <</PrintState /OFF>> /View <</ViewState /ON>> >> >>'."\n".'endobj');
  14851. }
  14852. /**
  14853. * Set the visibility of the successive elements.
  14854. * This can be useful, for instance, to put a background
  14855. * image or color that will show on screen but won't print.
  14856. * @param string $v visibility mode. Legal values are: all, print, screen.
  14857. * @access public
  14858. * @since 3.0.000 (2008-03-27)
  14859. */
  14860. public function setVisibility($v) {
  14861. if ($this->openMarkedContent) {
  14862. // close existing open marked-content
  14863. $this->_out('EMC');
  14864. $this->openMarkedContent = false;
  14865. }
  14866. switch($v) {
  14867. case 'print': {
  14868. $this->_out('/OC /OC1 BDC');
  14869. $this->openMarkedContent = true;
  14870. break;
  14871. }
  14872. case 'screen': {
  14873. $this->_out('/OC /OC2 BDC');
  14874. $this->openMarkedContent = true;
  14875. break;
  14876. }
  14877. case 'all': {
  14878. $this->_out('');
  14879. break;
  14880. }
  14881. default: {
  14882. $this->Error('Incorrect visibility: '.$v);
  14883. break;
  14884. }
  14885. }
  14886. $this->visibility = $v;
  14887. }
  14888. /**
  14889. * Add transparency parameters to the current extgstate
  14890. * @param array $params parameters
  14891. * @return the number of extgstates
  14892. * @access protected
  14893. * @since 3.0.000 (2008-03-27)
  14894. */
  14895. protected function addExtGState($parms) {
  14896. $n = count($this->extgstates) + 1;
  14897. // check if this ExtGState already exist
  14898. for ($i = 1; $i < $n; ++$i) {
  14899. if ($this->extgstates[$i]['parms'] == $parms) {
  14900. // return reference to existing ExtGState
  14901. return $i;
  14902. }
  14903. }
  14904. $this->extgstates[$n]['parms'] = $parms;
  14905. return $n;
  14906. }
  14907. /**
  14908. * Add an extgstate
  14909. * @param array $gs extgstate
  14910. * @access protected
  14911. * @since 3.0.000 (2008-03-27)
  14912. */
  14913. protected function setExtGState($gs) {
  14914. $this->_out(sprintf('/GS%d gs', $gs));
  14915. }
  14916. /**
  14917. * Put extgstates for object transparency
  14918. * @param array $gs extgstate
  14919. * @access protected
  14920. * @since 3.0.000 (2008-03-27)
  14921. */
  14922. protected function _putextgstates() {
  14923. $ne = count($this->extgstates);
  14924. for ($i = 1; $i <= $ne; ++$i) {
  14925. $this->extgstates[$i]['n'] = $this->_newobj();
  14926. $out = '<< /Type /ExtGState';
  14927. foreach ($this->extgstates[$i]['parms'] as $k => $v) {
  14928. if (is_float($v)) {
  14929. $v = sprintf('%.2F', $v);
  14930. }
  14931. $out .= ' /'.$k.' '.$v;
  14932. }
  14933. $out .= ' >>';
  14934. $out .= "\n".'endobj';
  14935. $this->_out($out);
  14936. }
  14937. }
  14938. /**
  14939. * Set alpha for stroking (CA) and non-stroking (ca) operations.
  14940. * @param float $alpha real value from 0 (transparent) to 1 (opaque)
  14941. * @param string $bm blend mode, one of the following: Normal, Multiply, Screen, Overlay, Darken, Lighten, ColorDodge, ColorBurn, HardLight, SoftLight, Difference, Exclusion, Hue, Saturation, Color, Luminosity
  14942. * @access public
  14943. * @since 3.0.000 (2008-03-27)
  14944. */
  14945. public function setAlpha($alpha, $bm='Normal') {
  14946. $gs = $this->addExtGState(array('ca' => $alpha, 'CA' => $alpha, 'BM' => '/'.$bm, 'AIS' => 'false'));
  14947. $this->setExtGState($gs);
  14948. }
  14949. /**
  14950. * Set the default JPEG compression quality (1-100)
  14951. * @param int $quality JPEG quality, integer between 1 and 100
  14952. * @access public
  14953. * @since 3.0.000 (2008-03-27)
  14954. */
  14955. public function setJPEGQuality($quality) {
  14956. if (($quality < 1) OR ($quality > 100)) {
  14957. $quality = 75;
  14958. }
  14959. $this->jpeg_quality = intval($quality);
  14960. }
  14961. /**
  14962. * Set the default number of columns in a row for HTML tables.
  14963. * @param int $cols number of columns
  14964. * @access public
  14965. * @since 3.0.014 (2008-06-04)
  14966. */
  14967. public function setDefaultTableColumns($cols=4) {
  14968. $this->default_table_columns = intval($cols);
  14969. }
  14970. /**
  14971. * Set the height of the cell (line height) respect the font height.
  14972. * @param int $h cell proportion respect font height (typical value = 1.25).
  14973. * @access public
  14974. * @since 3.0.014 (2008-06-04)
  14975. */
  14976. public function setCellHeightRatio($h) {
  14977. $this->cell_height_ratio = $h;
  14978. }
  14979. /**
  14980. * return the height of cell repect font height.
  14981. * @access public
  14982. * @since 4.0.012 (2008-07-24)
  14983. */
  14984. public function getCellHeightRatio() {
  14985. return $this->cell_height_ratio;
  14986. }
  14987. /**
  14988. * Set the PDF version (check PDF reference for valid values).
  14989. * Default value is 1.t
  14990. * @access public
  14991. * @since 3.1.000 (2008-06-09)
  14992. */
  14993. public function setPDFVersion($version='1.7') {
  14994. $this->PDFVersion = $version;
  14995. }
  14996. /**
  14997. * Set the viewer preferences dictionary controlling the way the document is to be presented on the screen or in print.
  14998. * (see Section 8.1 of PDF reference, "Viewer Preferences").
  14999. * <ul><li>HideToolbar boolean (Optional) A flag specifying whether to hide the viewer application's tool bars when the document is active. Default value: false.</li><li>HideMenubar boolean (Optional) A flag specifying whether to hide the viewer application's menu bar when the document is active. Default value: false.</li><li>HideWindowUI boolean (Optional) A flag specifying whether to hide user interface elements in the document's window (such as scroll bars and navigation controls), leaving only the document's contents displayed. Default value: false.</li><li>FitWindow boolean (Optional) A flag specifying whether to resize the document's window to fit the size of the first displayed page. Default value: false.</li><li>CenterWindow boolean (Optional) A flag specifying whether to position the document's window in the center of the screen. Default value: false.</li><li>DisplayDocTitle boolean (Optional; PDF 1.4) A flag specifying whether the window's title bar should display the document title taken from the Title entry of the document information dictionary (see Section 10.2.1, "Document Information Dictionary"). If false, the title bar should instead display the name of the PDF file containing the document. Default value: false.</li><li>NonFullScreenPageMode name (Optional) The document's page mode, specifying how to display the document on exiting full-screen mode:<ul><li>UseNone Neither document outline nor thumbnail images visible</li><li>UseOutlines Document outline visible</li><li>UseThumbs Thumbnail images visible</li><li>UseOC Optional content group panel visible</li></ul>This entry is meaningful only if the value of the PageMode entry in the catalog dictionary (see Section 3.6.1, "Document Catalog") is FullScreen; it is ignored otherwise. Default value: UseNone.</li><li>ViewArea name (Optional; PDF 1.4) The name of the page boundary representing the area of a page to be displayed when viewing the document on the screen. Valid values are (see Section 10.10.1, "Page Boundaries").:<ul><li>MediaBox</li><li>CropBox (default)</li><li>BleedBox</li><li>TrimBox</li><li>ArtBox</li></ul></li><li>ViewClip name (Optional; PDF 1.4) The name of the page boundary to which the contents of a page are to be clipped when viewing the document on the screen. Valid values are (see Section 10.10.1, "Page Boundaries").:<ul><li>MediaBox</li><li>CropBox (default)</li><li>BleedBox</li><li>TrimBox</li><li>ArtBox</li></ul></li><li>PrintArea name (Optional; PDF 1.4) The name of the page boundary representing the area of a page to be rendered when printing the document. Valid values are (see Section 10.10.1, "Page Boundaries").:<ul><li>MediaBox</li><li>CropBox (default)</li><li>BleedBox</li><li>TrimBox</li><li>ArtBox</li></ul></li><li>PrintClip name (Optional; PDF 1.4) The name of the page boundary to which the contents of a page are to be clipped when printing the document. Valid values are (see Section 10.10.1, "Page Boundaries").:<ul><li>MediaBox</li><li>CropBox (default)</li><li>BleedBox</li><li>TrimBox</li><li>ArtBox</li></ul></li><li>PrintScaling name (Optional; PDF 1.6) The page scaling option to be selected when a print dialog is displayed for this document. Valid values are: <ul><li>None, which indicates that the print dialog should reflect no page scaling</li><li>AppDefault (default), which indicates that applications should use the current print scaling</li></ul></li><li>Duplex name (Optional; PDF 1.7) The paper handling option to use when printing the file from the print dialog. The following values are valid:<ul><li>Simplex - Print single-sided</li><li>DuplexFlipShortEdge - Duplex and flip on the short edge of the sheet</li><li>DuplexFlipLongEdge - Duplex and flip on the long edge of the sheet</li></ul>Default value: none</li><li>PickTrayByPDFSize boolean (Optional; PDF 1.7) A flag specifying whether the PDF page size is used to select the input paper tray. This setting influences only the preset values used to populate the print dialog presented by a PDF viewer application. If PickTrayByPDFSize is true, the check box in the print dialog associated with input paper tray is checked. Note: This setting has no effect on Mac OS systems, which do not provide the ability to pick the input tray by size.</li><li>PrintPageRange array (Optional; PDF 1.7) The page numbers used to initialize the print dialog box when the file is printed. The first page of the PDF file is denoted by 1. Each pair consists of the first and last pages in the sub-range. An odd number of integers causes this entry to be ignored. Negative numbers cause the entire array to be ignored. Default value: as defined by PDF viewer application</li><li>NumCopies integer (Optional; PDF 1.7) The number of copies to be printed when the print dialog is opened for this file. Supported values are the integers 2 through 5. Values outside this range are ignored. Default value: as defined by PDF viewer application, but typically 1</li></ul>
  15000. * @param array $preferences array of options.
  15001. * @author Nicola Asuni
  15002. * @access public
  15003. * @since 3.1.000 (2008-06-09)
  15004. */
  15005. public function setViewerPreferences($preferences) {
  15006. $this->viewer_preferences = $preferences;
  15007. }
  15008. /**
  15009. * Paints color transition registration bars
  15010. * @param float $x abscissa of the top left corner of the rectangle.
  15011. * @param float $y ordinate of the top left corner of the rectangle.
  15012. * @param float $w width of the rectangle.
  15013. * @param float $h height of the rectangle.
  15014. * @param boolean $transition if true prints tcolor transitions to white.
  15015. * @param boolean $vertical if true prints bar vertically.
  15016. * @param string $colors colors to print, one letter per color separated by comma (for example 'A,W,R,G,B,C,M,Y,K'): A=black, W=white, R=red, G=green, B=blue, C=cyan, M=magenta, Y=yellow, K=black.
  15017. * @author Nicola Asuni
  15018. * @since 4.9.000 (2010-03-26)
  15019. * @access public
  15020. */
  15021. public function colorRegistrationBar($x, $y, $w, $h, $transition=true, $vertical=false, $colors='A,R,G,B,C,M,Y,K') {
  15022. $bars = explode(',', $colors);
  15023. $numbars = count($bars); // number of bars to print
  15024. // set bar measures
  15025. if ($vertical) {
  15026. $coords = array(0, 0, 0, 1);
  15027. $wb = $w / $numbars; // bar width
  15028. $hb = $h; // bar height
  15029. $xd = $wb; // delta x
  15030. $yd = 0; // delta y
  15031. } else {
  15032. $coords = array(1, 0, 0, 0);
  15033. $wb = $w; // bar width
  15034. $hb = $h / $numbars; // bar height
  15035. $xd = 0; // delta x
  15036. $yd = $hb; // delta y
  15037. }
  15038. $xb = $x;
  15039. $yb = $y;
  15040. foreach ($bars as $col) {
  15041. switch ($col) {
  15042. // set transition colors
  15043. case 'A': { // BLACK
  15044. $col_a = array(255);
  15045. $col_b = array(0);
  15046. break;
  15047. }
  15048. case 'W': { // WHITE
  15049. $col_a = array(0);
  15050. $col_b = array(255);
  15051. break;
  15052. }
  15053. case 'R': { // R
  15054. $col_a = array(255,255,255);
  15055. $col_b = array(255,0,0);
  15056. break;
  15057. }
  15058. case 'G': { // G
  15059. $col_a = array(255,255,255);
  15060. $col_b = array(0,255,0);
  15061. break;
  15062. }
  15063. case 'B': { // B
  15064. $col_a = array(255,255,255);
  15065. $col_b = array(0,0,255);
  15066. break;
  15067. }
  15068. case 'C': { // C
  15069. $col_a = array(0,0,0,0);
  15070. $col_b = array(100,0,0,0);
  15071. break;
  15072. }
  15073. case 'M': { // M
  15074. $col_a = array(0,0,0,0);
  15075. $col_b = array(0,100,0,0);
  15076. break;
  15077. }
  15078. case 'Y': { // Y
  15079. $col_a = array(0,0,0,0);
  15080. $col_b = array(0,0,100,0);
  15081. break;
  15082. }
  15083. case 'K': { // K
  15084. $col_a = array(0,0,0,0);
  15085. $col_b = array(0,0,0,100);
  15086. break;
  15087. }
  15088. default: { // GRAY
  15089. $col_a = array(255);
  15090. $col_b = array(0);
  15091. break;
  15092. }
  15093. }
  15094. if ($transition) {
  15095. // color gradient
  15096. $this->LinearGradient($xb, $yb, $wb, $hb, $col_a, $col_b, $coords);
  15097. } else {
  15098. // color rectangle
  15099. $this->SetFillColorArray($col_b);
  15100. $this->Rect($xb, $yb, $wb, $hb, 'F', array());
  15101. }
  15102. $xb += $xd;
  15103. $yb += $yd;
  15104. }
  15105. }
  15106. /**
  15107. * Paints crop mark
  15108. * @param float $x abscissa of the crop mark center.
  15109. * @param float $y ordinate of the crop mark center.
  15110. * @param float $w width of the crop mark.
  15111. * @param float $h height of the crop mark.
  15112. * @param string $type type of crop mark, one sybol per type separated by comma: A = top left, B = top right, C = bottom left, D = bottom right.
  15113. * @param array $color crop mark color (default black).
  15114. * @author Nicola Asuni
  15115. * @since 4.9.000 (2010-03-26)
  15116. * @access public
  15117. */
  15118. public function cropMark($x, $y, $w, $h, $type='A,B,C,D', $color=array(0,0,0)) {
  15119. $this->SetLineStyle(array('width' => (0.5 / $this->k), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $color));
  15120. $crops = explode(',', $type);
  15121. $numcrops = count($crops); // number of crop marks to print
  15122. $dw = $w / 4; // horizontal space to leave before the intersection point
  15123. $dh = $h / 4; // vertical space to leave before the intersection point
  15124. foreach ($crops as $crop) {
  15125. switch ($crop) {
  15126. case 'A': {
  15127. $x1 = $x;
  15128. $y1 = $y - $h;
  15129. $x2 = $x;
  15130. $y2 = $y - $dh;
  15131. $x3 = $x - $w;
  15132. $y3 = $y;
  15133. $x4 = $x - $dw;
  15134. $y4 = $y;
  15135. break;
  15136. }
  15137. case 'B': {
  15138. $x1 = $x;
  15139. $y1 = $y - $h;
  15140. $x2 = $x;
  15141. $y2 = $y - $dh;
  15142. $x3 = $x + $dw;
  15143. $y3 = $y;
  15144. $x4 = $x + $w;
  15145. $y4 = $y;
  15146. break;
  15147. }
  15148. case 'C': {
  15149. $x1 = $x - $w;
  15150. $y1 = $y;
  15151. $x2 = $x - $dw;
  15152. $y2 = $y;
  15153. $x3 = $x;
  15154. $y3 = $y + $dh;
  15155. $x4 = $x;
  15156. $y4 = $y + $h;
  15157. break;
  15158. }
  15159. case 'D': {
  15160. $x1 = $x + $dw;
  15161. $y1 = $y;
  15162. $x2 = $x + $w;
  15163. $y2 = $y;
  15164. $x3 = $x;
  15165. $y3 = $y + $dh;
  15166. $x4 = $x;
  15167. $y4 = $y + $h;
  15168. break;
  15169. }
  15170. }
  15171. $this->Line($x1, $y1, $x2, $y2);
  15172. $this->Line($x3, $y3, $x4, $y4);
  15173. }
  15174. }
  15175. /**
  15176. * Paints a registration mark
  15177. * @param float $x abscissa of the registration mark center.
  15178. * @param float $y ordinate of the registration mark center.
  15179. * @param float $r radius of the crop mark.
  15180. * @param boolean $double if true print two concentric crop marks.
  15181. * @param array $cola crop mark color (default black).
  15182. * @param array $colb second crop mark color.
  15183. * @author Nicola Asuni
  15184. * @since 4.9.000 (2010-03-26)
  15185. * @access public
  15186. */
  15187. public function registrationMark($x, $y, $r, $double=false, $cola=array(0,0,0), $colb=array(255,255,255)) {
  15188. $line_style = array('width' => (0.5 / $this->k), 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => $cola);
  15189. $this->SetFillColorArray($cola);
  15190. $this->PieSector($x, $y, $r, 90, 180, 'F');
  15191. $this->PieSector($x, $y, $r, 270, 360, 'F');
  15192. $this->Circle($x, $y, $r, 0, 360, 'C', $line_style, array(), 8);
  15193. if ($double) {
  15194. $r2 = $r * 0.5;
  15195. $this->SetFillColorArray($colb);
  15196. $this->PieSector($x, $y, $r2, 90, 180, 'F');
  15197. $this->PieSector($x, $y, $r2, 270, 360, 'F');
  15198. $this->SetFillColorArray($cola);
  15199. $this->PieSector($x, $y, $r2, 0, 90, 'F');
  15200. $this->PieSector($x, $y, $r2, 180, 270, 'F');
  15201. $this->Circle($x, $y, $r2, 0, 360, 'C', $line_style, array(), 8);
  15202. }
  15203. }
  15204. /**
  15205. * Paints a linear colour gradient.
  15206. * @param float $x abscissa of the top left corner of the rectangle.
  15207. * @param float $y ordinate of the top left corner of the rectangle.
  15208. * @param float $w width of the rectangle.
  15209. * @param float $h height of the rectangle.
  15210. * @param array $col1 first color (Grayscale, RGB or CMYK components).
  15211. * @param array $col2 second color (Grayscale, RGB or CMYK components).
  15212. * @param array $coords array of the form (x1, y1, x2, y2) which defines the gradient vector (see linear_gradient_coords.jpg). The default value is from left to right (x1=0, y1=0, x2=1, y2=0).
  15213. * @author Andreas Würmser, Nicola Asuni
  15214. * @since 3.1.000 (2008-06-09)
  15215. * @access public
  15216. */
  15217. public function LinearGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0,0,1,0)) {
  15218. $this->Clip($x, $y, $w, $h);
  15219. $this->Gradient(2, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
  15220. }
  15221. /**
  15222. * Paints a radial colour gradient.
  15223. * @param float $x abscissa of the top left corner of the rectangle.
  15224. * @param float $y ordinate of the top left corner of the rectangle.
  15225. * @param float $w width of the rectangle.
  15226. * @param float $h height of the rectangle.
  15227. * @param array $col1 first color (Grayscale, RGB or CMYK components).
  15228. * @param array $col2 second color (Grayscale, RGB or CMYK components).
  15229. * @param array $coords array of the form (fx, fy, cx, cy, r) where (fx, fy) is the starting point of the gradient with color1, (cx, cy) is the center of the circle with color2, and r is the radius of the circle (see radial_gradient_coords.jpg). (fx, fy) should be inside the circle, otherwise some areas will not be defined.
  15230. * @author Andreas Würmser, Nicola Asuni
  15231. * @since 3.1.000 (2008-06-09)
  15232. * @access public
  15233. */
  15234. public function RadialGradient($x, $y, $w, $h, $col1=array(), $col2=array(), $coords=array(0.5,0.5,0.5,0.5,1)) {
  15235. $this->Clip($x, $y, $w, $h);
  15236. $this->Gradient(3, $coords, array(array('color' => $col1, 'offset' => 0, 'exponent' => 1), array('color' => $col2, 'offset' => 1, 'exponent' => 1)), array(), false);
  15237. }
  15238. /**
  15239. * Paints a coons patch mesh.
  15240. * @param float $x abscissa of the top left corner of the rectangle.
  15241. * @param float $y ordinate of the top left corner of the rectangle.
  15242. * @param float $w width of the rectangle.
  15243. * @param float $h height of the rectangle.
  15244. * @param array $col1 first color (lower left corner) (RGB components).
  15245. * @param array $col2 second color (lower right corner) (RGB components).
  15246. * @param array $col3 third color (upper right corner) (RGB components).
  15247. * @param array $col4 fourth color (upper left corner) (RGB components).
  15248. * @param array $coords <ul><li>for one patch mesh: array(float x1, float y1, .... float x12, float y12): 12 pairs of coordinates (normally from 0 to 1) which specify the Bezier control points that define the patch. First pair is the lower left edge point, next is its right control point (control point 2). Then the other points are defined in the order: control point 1, edge point, control point 2 going counter-clockwise around the patch. Last (x12, y12) is the first edge point's left control point (control point 1).</li><li>for two or more patch meshes: array[number of patches]: arrays with the following keys for each patch: f: where to put that patch (0 = first patch, 1, 2, 3 = right, top and left of precedent patch - I didn't figure this out completely - just try and error ;-) points: 12 pairs of coordinates of the Bezier control points as above for the first patch, 8 pairs of coordinates for the following patches, ignoring the coordinates already defined by the precedent patch (I also didn't figure out the order of these - also: try and see what's happening) colors: must be 4 colors for the first patch, 2 colors for the following patches</li></ul>
  15249. * @param array $coords_min minimum value used by the coordinates. If a coordinate's value is smaller than this it will be cut to coords_min. default: 0
  15250. * @param array $coords_max maximum value used by the coordinates. If a coordinate's value is greater than this it will be cut to coords_max. default: 1
  15251. * @param boolean $antialias A flag indicating whether to filter the shading function to prevent aliasing artifacts.
  15252. * @author Andreas Würmser, Nicola Asuni
  15253. * @since 3.1.000 (2008-06-09)
  15254. * @access public
  15255. */
  15256. 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) {
  15257. $this->Clip($x, $y, $w, $h);
  15258. $n = count($this->gradients) + 1;
  15259. $this->gradients[$n] = array();
  15260. $this->gradients[$n]['type'] = 6; //coons patch mesh
  15261. $this->gradients[$n]['coords'] = array();
  15262. $this->gradients[$n]['antialias'] = $antialias;
  15263. $this->gradients[$n]['colors'] = array();
  15264. $this->gradients[$n]['transparency'] = false;
  15265. //check the coords array if it is the simple array or the multi patch array
  15266. if (!isset($coords[0]['f'])) {
  15267. //simple array -> convert to multi patch array
  15268. if (!isset($col1[1])) {
  15269. $col1[1] = $col1[2] = $col1[0];
  15270. }
  15271. if (!isset($col2[1])) {
  15272. $col2[1] = $col2[2] = $col2[0];
  15273. }
  15274. if (!isset($col3[1])) {
  15275. $col3[1] = $col3[2] = $col3[0];
  15276. }
  15277. if (!isset($col4[1])) {
  15278. $col4[1] = $col4[2] = $col4[0];
  15279. }
  15280. $patch_array[0]['f'] = 0;
  15281. $patch_array[0]['points'] = $coords;
  15282. $patch_array[0]['colors'][0]['r'] = $col1[0];
  15283. $patch_array[0]['colors'][0]['g'] = $col1[1];
  15284. $patch_array[0]['colors'][0]['b'] = $col1[2];
  15285. $patch_array[0]['colors'][1]['r'] = $col2[0];
  15286. $patch_array[0]['colors'][1]['g'] = $col2[1];
  15287. $patch_array[0]['colors'][1]['b'] = $col2[2];
  15288. $patch_array[0]['colors'][2]['r'] = $col3[0];
  15289. $patch_array[0]['colors'][2]['g'] = $col3[1];
  15290. $patch_array[0]['colors'][2]['b'] = $col3[2];
  15291. $patch_array[0]['colors'][3]['r'] = $col4[0];
  15292. $patch_array[0]['colors'][3]['g'] = $col4[1];
  15293. $patch_array[0]['colors'][3]['b'] = $col4[2];
  15294. } else {
  15295. //multi patch array
  15296. $patch_array = $coords;
  15297. }
  15298. $bpcd = 65535; //16 bits per coordinate
  15299. //build the data stream
  15300. $this->gradients[$n]['stream'] = '';
  15301. $count_patch = count($patch_array);
  15302. for ($i=0; $i < $count_patch; ++$i) {
  15303. $this->gradients[$n]['stream'] .= chr($patch_array[$i]['f']); //start with the edge flag as 8 bit
  15304. $count_points = count($patch_array[$i]['points']);
  15305. for ($j=0; $j < $count_points; ++$j) {
  15306. //each point as 16 bit
  15307. $patch_array[$i]['points'][$j] = (($patch_array[$i]['points'][$j] - $coords_min) / ($coords_max - $coords_min)) * $bpcd;
  15308. if ($patch_array[$i]['points'][$j] < 0) {
  15309. $patch_array[$i]['points'][$j] = 0;
  15310. }
  15311. if ($patch_array[$i]['points'][$j] > $bpcd) {
  15312. $patch_array[$i]['points'][$j] = $bpcd;
  15313. }
  15314. $this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] / 256));
  15315. $this->gradients[$n]['stream'] .= chr(floor($patch_array[$i]['points'][$j] % 256));
  15316. }
  15317. $count_cols = count($patch_array[$i]['colors']);
  15318. for ($j=0; $j < $count_cols; ++$j) {
  15319. //each color component as 8 bit
  15320. $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['r']);
  15321. $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['g']);
  15322. $this->gradients[$n]['stream'] .= chr($patch_array[$i]['colors'][$j]['b']);
  15323. }
  15324. }
  15325. //paint the gradient
  15326. $this->_out('/Sh'.$n.' sh');
  15327. //restore previous Graphic State
  15328. $this->_out('Q');
  15329. }
  15330. /**
  15331. * Set a rectangular clipping area.
  15332. * @param float $x abscissa of the top left corner of the rectangle (or top right corner for RTL mode).
  15333. * @param float $y ordinate of the top left corner of the rectangle.
  15334. * @param float $w width of the rectangle.
  15335. * @param float $h height of the rectangle.
  15336. * @author Andreas Würmser, Nicola Asuni
  15337. * @since 3.1.000 (2008-06-09)
  15338. * @access protected
  15339. */
  15340. protected function Clip($x, $y, $w, $h) {
  15341. if ($this->rtl) {
  15342. $x = $this->w - $x - $w;
  15343. }
  15344. //save current Graphic State
  15345. $s = 'q';
  15346. //set clipping area
  15347. $s .= sprintf(' %.2F %.2F %.2F %.2F re W n', $x*$this->k, ($this->h-$y)*$this->k, $w*$this->k, -$h*$this->k);
  15348. //set up transformation matrix for gradient
  15349. $s .= sprintf(' %.3F 0 0 %.3F %.3F %.3F cm', $w*$this->k, $h*$this->k, $x*$this->k, ($this->h-($y+$h))*$this->k);
  15350. $this->_out($s);
  15351. }
  15352. /**
  15353. * Output gradient.
  15354. * @param int $type type of gradient (1 Function-based shading; 2 Axial shading; 3 Radial shading; 4 Free-form Gouraud-shaded triangle mesh; 5 Lattice-form Gouraud-shaded triangle mesh; 6 Coons patch mesh; 7 Tensor-product patch mesh). (Not all types are currently supported)
  15355. * @param array $coords array of coordinates.
  15356. * @param array $stops array gradient color components: color = array of GRAY, RGB or CMYK color components; offset = (0 to 1) represents a location along the gradient vector; exponent = exponent of the exponential interpolation function (default = 1).
  15357. * @param array $background An array of colour components appropriate to the colour space, specifying a single background colour value.
  15358. * @param boolean $antialias A flag indicating whether to filter the shading function to prevent aliasing artifacts.
  15359. * @author Nicola Asuni
  15360. * @since 3.1.000 (2008-06-09)
  15361. * @access public
  15362. */
  15363. public function Gradient($type, $coords, $stops, $background=array(), $antialias=false) {
  15364. $n = count($this->gradients) + 1;
  15365. $this->gradients[$n] = array();
  15366. $this->gradients[$n]['type'] = $type;
  15367. $this->gradients[$n]['coords'] = $coords;
  15368. $this->gradients[$n]['antialias'] = $antialias;
  15369. $this->gradients[$n]['colors'] = array();
  15370. $this->gradients[$n]['transparency'] = false;
  15371. // color space
  15372. $numcolspace = count($stops[0]['color']);
  15373. $bcolor = array_values($background);
  15374. switch($numcolspace) {
  15375. case 4: { // CMYK
  15376. $this->gradients[$n]['colspace'] = 'DeviceCMYK';
  15377. if (!empty($background)) {
  15378. $this->gradients[$n]['background'] = sprintf('%.3F %.3F %.3F %.3F', $bcolor[0]/100, $bcolor[1]/100, $bcolor[2]/100, $bcolor[3]/100);
  15379. }
  15380. break;
  15381. }
  15382. case 3: { // RGB
  15383. $this->gradients[$n]['colspace'] = 'DeviceRGB';
  15384. if (!empty($background)) {
  15385. $this->gradients[$n]['background'] = sprintf('%.3F %.3F %.3F', $bcolor[0]/255, $bcolor[1]/255, $bcolor[2]/255);
  15386. }
  15387. break;
  15388. }
  15389. case 1: { // Gray scale
  15390. $this->gradients[$n]['colspace'] = 'DeviceGray';
  15391. if (!empty($background)) {
  15392. $this->gradients[$n]['background'] = sprintf('%.3F', $bcolor[0]/255);
  15393. }
  15394. break;
  15395. }
  15396. }
  15397. $num_stops = count($stops);
  15398. $last_stop_id = $num_stops - 1;
  15399. foreach ($stops as $key => $stop) {
  15400. $this->gradients[$n]['colors'][$key] = array();
  15401. // offset represents a location along the gradient vector
  15402. if (isset($stop['offset'])) {
  15403. $this->gradients[$n]['colors'][$key]['offset'] = $stop['offset'];
  15404. } else {
  15405. if ($key == 0) {
  15406. $this->gradients[$n]['colors'][$key]['offset'] = 0;
  15407. } elseif ($key == $last_stop_id) {
  15408. $this->gradients[$n]['colors'][$key]['offset'] = 1;
  15409. } else {
  15410. $offsetstep = (1 - $this->gradients[$n]['colors'][($key - 1)]['offset']) / ($num_stops - $key);
  15411. $this->gradients[$n]['colors'][$key]['offset'] = $this->gradients[$n]['colors'][($key - 1)]['offset'] + $offsetstep;
  15412. }
  15413. }
  15414. if (isset($stop['opacity'])) {
  15415. $this->gradients[$n]['colors'][$key]['opacity'] = $stop['opacity'];
  15416. if ($stop['opacity'] < 1) {
  15417. $this->gradients[$n]['transparency'] = true;
  15418. }
  15419. } else {
  15420. $this->gradients[$n]['colors'][$key]['opacity'] = 1;
  15421. }
  15422. // exponent for the exponential interpolation function
  15423. if (isset($stop['exponent'])) {
  15424. $this->gradients[$n]['colors'][$key]['exponent'] = $stop['exponent'];
  15425. } else {
  15426. $this->gradients[$n]['colors'][$key]['exponent'] = 1;
  15427. }
  15428. // set colors
  15429. $color = array_values($stop['color']);
  15430. switch($numcolspace) {
  15431. case 4: { // CMYK
  15432. $this->gradients[$n]['colors'][$key]['color'] = sprintf('%.3F %.3F %.3F %.3F', $color[0]/100, $color[1]/100, $color[2]/100, $color[3]/100);
  15433. break;
  15434. }
  15435. case 3: { // RGB
  15436. $this->gradients[$n]['colors'][$key]['color'] = sprintf('%.3F %.3F %.3F', $color[0]/255, $color[1]/255, $color[2]/255);
  15437. break;
  15438. }
  15439. case 1: { // Gray scale
  15440. $this->gradients[$n]['colors'][$key]['color'] = sprintf('%.3F', $color[0]/255);
  15441. break;
  15442. }
  15443. }
  15444. }
  15445. if ($this->gradients[$n]['transparency']) {
  15446. // paint luminosity gradient
  15447. $this->_out('/TGS'.$n.' gs');
  15448. }
  15449. //paint the gradient
  15450. $this->_out('/Sh'.$n.' sh');
  15451. //restore previous Graphic State
  15452. $this->_out('Q');
  15453. }
  15454. /**
  15455. * Output gradient shaders.
  15456. * @author Nicola Asuni
  15457. * @since 3.1.000 (2008-06-09)
  15458. * @access protected
  15459. */
  15460. function _putshaders() {
  15461. $idt = count($this->gradients); //index for transparency gradients
  15462. foreach ($this->gradients as $id => $grad) {
  15463. if (($grad['type'] == 2) OR ($grad['type'] == 3)) {
  15464. $fc = $this->_newobj();
  15465. $out = '<<';
  15466. $out .= ' /FunctionType 3';
  15467. $out .= ' /Domain [0 1]';
  15468. $functions = '';
  15469. $bounds = '';
  15470. $encode = '';
  15471. $i = 1;
  15472. $num_cols = count($grad['colors']);
  15473. $lastcols = $num_cols - 1;
  15474. for ($i = 1; $i < $num_cols; ++$i) {
  15475. $functions .= ($fc + $i).' 0 R ';
  15476. if ($i < $lastcols) {
  15477. $bounds .= sprintf('%.3F ', $grad['colors'][$i]['offset']);
  15478. }
  15479. $encode .= '0 1 ';
  15480. }
  15481. $out .= ' /Functions ['.trim($functions).']';
  15482. $out .= ' /Bounds ['.trim($bounds).']';
  15483. $out .= ' /Encode ['.trim($encode).']';
  15484. $out .= ' >>';
  15485. $out .= "\n".'endobj';
  15486. $this->_out($out);
  15487. for ($i = 1; $i < $num_cols; ++$i) {
  15488. $this->_newobj();
  15489. $out = '<<';
  15490. $out .= ' /FunctionType 2';
  15491. $out .= ' /Domain [0 1]';
  15492. $out .= ' /C0 ['.$grad['colors'][($i - 1)]['color'].']';
  15493. $out .= ' /C1 ['.$grad['colors'][$i]['color'].']';
  15494. $out .= ' /N '.$grad['colors'][$i]['exponent'];
  15495. $out .= ' >>';
  15496. $out .= "\n".'endobj';
  15497. $this->_out($out);
  15498. }
  15499. // set transparency fuctions
  15500. if ($grad['transparency']) {
  15501. $ft = $this->_newobj();
  15502. $out = '<<';
  15503. $out .= ' /FunctionType 3';
  15504. $out .= ' /Domain [0 1]';
  15505. $functions = '';
  15506. $i = 1;
  15507. $num_cols = count($grad['colors']);
  15508. for ($i = 1; $i < $num_cols; ++$i) {
  15509. $functions .= ($ft + $i).' 0 R ';
  15510. }
  15511. $out .= ' /Functions ['.trim($functions).']';
  15512. $out .= ' /Bounds ['.trim($bounds).']';
  15513. $out .= ' /Encode ['.trim($encode).']';
  15514. $out .= ' >>';
  15515. $out .= "\n".'endobj';
  15516. $this->_out($out);
  15517. for ($i = 1; $i < $num_cols; ++$i) {
  15518. $this->_newobj();
  15519. $out = '<<';
  15520. $out .= ' /FunctionType 2';
  15521. $out .= ' /Domain [0 1]';
  15522. $out .= ' /C0 ['.$grad['colors'][($i - 1)]['opacity'].']';
  15523. $out .= ' /C1 ['.$grad['colors'][$i]['opacity'].']';
  15524. $out .= ' /N '.$grad['colors'][$i]['exponent'];
  15525. $out .= ' >>';
  15526. $out .= "\n".'endobj';
  15527. $this->_out($out);
  15528. }
  15529. }
  15530. }
  15531. // set shading object
  15532. $this->_newobj();
  15533. $out = '<< /ShadingType '.$grad['type'];
  15534. if (isset($grad['colspace'])) {
  15535. $out .= ' /ColorSpace /'.$grad['colspace'];
  15536. } else {
  15537. $out .= ' /ColorSpace /DeviceRGB';
  15538. }
  15539. if (isset($grad['background']) AND !empty($grad['background'])) {
  15540. $out .= ' /Background ['.$grad['background'].']';
  15541. }
  15542. if (isset($grad['antialias']) AND ($grad['antialias'] === true)) {
  15543. $out .= ' /AntiAlias true';
  15544. }
  15545. if ($grad['type'] == 2) {
  15546. $out .= ' '.sprintf('/Coords [%.3F %.3F %.3F %.3F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3]);
  15547. $out .= ' /Domain [0 1]';
  15548. $out .= ' /Function '.$fc.' 0 R';
  15549. $out .= ' /Extend [true true]';
  15550. $out .= ' >>';
  15551. } elseif ($grad['type'] == 3) {
  15552. //x0, y0, r0, x1, y1, r1
  15553. //at this this time radius of inner circle is 0
  15554. $out .= ' '.sprintf('/Coords [%.3F %.3F 0 %.3F %.3F %.3F]', $grad['coords'][0], $grad['coords'][1], $grad['coords'][2], $grad['coords'][3], $grad['coords'][4]);
  15555. $out .= ' /Domain [0 1]';
  15556. $out .= ' /Function '.$fc.' 0 R';
  15557. $out .= ' /Extend [true true]';
  15558. $out .= ' >>';
  15559. } elseif ($grad['type'] == 6) {
  15560. $out .= ' /BitsPerCoordinate 16';
  15561. $out .= ' /BitsPerComponent 8';
  15562. $out .= ' /Decode[0 1 0 1 0 1 0 1 0 1]';
  15563. $out .= ' /BitsPerFlag 8';
  15564. $stream = $this->_getrawstream($grad['stream']);
  15565. $out .= ' /Length '.strlen($stream);
  15566. $out .= ' >>';
  15567. $out .= ' stream'."\n".$stream."\n".'endstream';
  15568. }
  15569. $out .= "\n".'endobj';
  15570. $this->_out($out);
  15571. if ($grad['transparency']) {
  15572. $shading_transparency = preg_replace('/\/ColorSpace \/[^\s]+/si', '/ColorSpace /DeviceGray', $out);
  15573. $shading_transparency = preg_replace('/\/Function [0-9]+ /si', '/Function '.$ft.' ', $shading_transparency);
  15574. }
  15575. $this->gradients[$id]['id'] = $this->n;
  15576. // set pattern object
  15577. $this->_newobj();
  15578. $out = '<< /Type /Pattern /PatternType 2';
  15579. $out .= ' /Shading '.$this->gradients[$id]['id'].' 0 R';
  15580. $out .= ' >>';
  15581. $out .= "\n".'endobj';
  15582. $this->_out($out);
  15583. $this->gradients[$id]['pattern'] = $this->n;
  15584. // set shading and pattern for transparency mask
  15585. if ($grad['transparency']) {
  15586. // luminosity pattern
  15587. $idgs = $id + $idt;
  15588. $this->_newobj();
  15589. $this->_out($shading_transparency);
  15590. $this->gradients[$idgs]['id'] = $this->n;
  15591. $this->_newobj();
  15592. $out = '<< /Type /Pattern /PatternType 2';
  15593. $out .= ' /Shading '.$this->gradients[$idgs]['id'].' 0 R';
  15594. $out .= ' >>';
  15595. $out .= "\n".'endobj';
  15596. $this->_out($out);
  15597. $this->gradients[$idgs]['pattern'] = $this->n;
  15598. // luminosity XObject
  15599. $oid = $this->_newobj();
  15600. $this->xobjects['LX'.$oid] = array('n' => $oid);
  15601. $filter = '';
  15602. $stream = 'q /a0 gs /Pattern cs /p'.$idgs.' scn 0 0 '.$this->wPt.' '.$this->hPt.' re f Q';
  15603. if ($this->compress) {
  15604. $filter = ' /Filter /FlateDecode';
  15605. $stream = gzcompress($stream);
  15606. }
  15607. $stream = $this->_getrawstream($stream);
  15608. $out = '<< /Type /XObject /Subtype /Form /FormType 1'.$filter;
  15609. $out .= ' /Length '.strlen($stream);
  15610. $rect = sprintf('%.2F %.2F', $this->wPt, $this->hPt);
  15611. $out .= ' /BBox [0 0 '.$rect.']';
  15612. $out .= ' /Group << /Type /Group /S /Transparency /CS /DeviceGray >>';
  15613. $out .= ' /Resources <<';
  15614. $out .= ' /ExtGState << /a0 << /ca 1 /CA 1 >> >>';
  15615. $out .= ' /Pattern << /p'.$idgs.' '.$this->gradients[$idgs]['pattern'].' 0 R >>';
  15616. $out .= ' >>';
  15617. $out .= ' >> ';
  15618. $out .= ' stream'."\n".$stream."\n".'endstream';
  15619. $out .= "\n".'endobj';
  15620. $this->_out($out);
  15621. // SMask
  15622. $this->_newobj();
  15623. $out = '<< /Type /Mask /S /Luminosity /G '.($this->n - 1).' 0 R >>'."\n".'endobj';
  15624. $this->_out($out);
  15625. // ExtGState
  15626. $this->_newobj();
  15627. $out = '<< /Type /ExtGState /SMask '.($this->n - 1).' 0 R /AIS false >>'."\n".'endobj';
  15628. $this->_out($out);
  15629. $this->extgstates[] = array('n' => $this->n, 'name' => 'TGS'.$id);
  15630. }
  15631. }
  15632. }
  15633. /**
  15634. * Draw the sector of a circle.
  15635. * It can be used for instance to render pie charts.
  15636. * @param float $xc abscissa of the center.
  15637. * @param float $yc ordinate of the center.
  15638. * @param float $r radius.
  15639. * @param float $a start angle (in degrees).
  15640. * @param float $b end angle (in degrees).
  15641. * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
  15642. * @param float $cw: indicates whether to go clockwise (default: true).
  15643. * @param float $o: origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock). Default: 90.
  15644. * @author Maxime Delorme, Nicola Asuni
  15645. * @since 3.1.000 (2008-06-09)
  15646. * @access public
  15647. */
  15648. public function PieSector($xc, $yc, $r, $a, $b, $style='FD', $cw=true, $o=90) {
  15649. $this->PieSectorXY($xc, $yc, $r, $r, $a, $b, $style, $cw, $o);
  15650. }
  15651. /**
  15652. * Draw the sector of an ellipse.
  15653. * It can be used for instance to render pie charts.
  15654. * @param float $xc abscissa of the center.
  15655. * @param float $yc ordinate of the center.
  15656. * @param float $rx the x-axis radius.
  15657. * @param float $ry the y-axis radius.
  15658. * @param float $a start angle (in degrees).
  15659. * @param float $b end angle (in degrees).
  15660. * @param string $style Style of rendering. See the getPathPaintOperator() function for more information.
  15661. * @param float $cw: indicates whether to go clockwise.
  15662. * @param float $o: origin of angles (0 for 3 o'clock, 90 for noon, 180 for 9 o'clock, 270 for 6 o'clock).
  15663. * @param integer $nc Number of curves used to draw a 90 degrees portion of arc.
  15664. * @author Maxime Delorme, Nicola Asuni
  15665. * @since 3.1.000 (2008-06-09)
  15666. * @access public
  15667. */
  15668. public function PieSectorXY($xc, $yc, $rx, $ry, $a, $b, $style='FD', $cw=false, $o=0, $nc=2) {
  15669. if ($this->rtl) {
  15670. $xc = $this->w - $xc;
  15671. }
  15672. $op = $this->getPathPaintOperator($style);
  15673. if ($op == 'f') {
  15674. $line_style = array();
  15675. }
  15676. if ($cw) {
  15677. $d = $b;
  15678. $b = 360 - $a + $o;
  15679. $a = 360 - $d + $o;
  15680. } else {
  15681. $b += $o;
  15682. $a += $o;
  15683. }
  15684. $this->_outellipticalarc($xc, $yc, $rx, $ry, 0, $a, $b, true, $nc);
  15685. $this->_out($op);
  15686. }
  15687. /**
  15688. * Embed vector-based Adobe Illustrator (AI) or AI-compatible EPS files.
  15689. * NOTE: EPS is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
  15690. * Only vector drawing is supported, not text or bitmap.
  15691. * Although the script was successfully tested with various AI format versions, best results are probably achieved with files that were exported in the AI3 format (tested with Illustrator CS2, Freehand MX and Photoshop CS2).
  15692. * @param string $file Name of the file containing the image.
  15693. * @param float $x Abscissa of the upper-left corner.
  15694. * @param float $y Ordinate of the upper-left corner.
  15695. * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
  15696. * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
  15697. * @param mixed $link URL or identifier returned by AddLink().
  15698. * @param boolean useBoundingBox specifies whether to position the bounding box (true) or the complete canvas (false) at location (x,y). Default value is true.
  15699. * @param string $align Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
  15700. * @param string $palign Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
  15701. * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
  15702. * @param boolean $fitonpage if true the image is resized to not exceed page dimensions.
  15703. * @author Valentin Schmidt, Nicola Asuni
  15704. * @since 3.1.000 (2008-06-09)
  15705. * @access public
  15706. */
  15707. public function ImageEps($file, $x='', $y='', $w=0, $h=0, $link='', $useBoundingBox=true, $align='', $palign='', $border=0, $fitonpage=false) {
  15708. if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
  15709. // convert EPS to raster image using GD or ImageMagick libraries
  15710. return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
  15711. }
  15712. if ($x === '') {
  15713. $x = $this->x;
  15714. }
  15715. if ($y === '') {
  15716. $y = $this->y;
  15717. }
  15718. // check page for no-write regions and adapt page margins if necessary
  15719. $this->checkPageRegions($h, $x, $y);
  15720. $k = $this->k;
  15721. $data = file_get_contents($file);
  15722. if ($data === false) {
  15723. $this->Error('EPS file not found: '.$file);
  15724. }
  15725. $regs = array();
  15726. // EPS/AI compatibility check (only checks files created by Adobe Illustrator!)
  15727. preg_match("/%%Creator:([^\r\n]+)/", $data, $regs); # find Creator
  15728. if (count($regs) > 1) {
  15729. $version_str = trim($regs[1]); # e.g. "Adobe Illustrator(R) 8.0"
  15730. if (strpos($version_str, 'Adobe Illustrator') !== false) {
  15731. $versexp = explode(' ', $version_str);
  15732. $version = (float)array_pop($versexp);
  15733. if ($version >= 9) {
  15734. $this->Error('This version of Adobe Illustrator file is not supported: '.$file);
  15735. }
  15736. }
  15737. }
  15738. // strip binary bytes in front of PS-header
  15739. $start = strpos($data, '%!PS-Adobe');
  15740. if ($start > 0) {
  15741. $data = substr($data, $start);
  15742. }
  15743. // find BoundingBox params
  15744. preg_match("/%%BoundingBox:([^\r\n]+)/", $data, $regs);
  15745. if (count($regs) > 1) {
  15746. list($x1, $y1, $x2, $y2) = explode(' ', trim($regs[1]));
  15747. } else {
  15748. $this->Error('No BoundingBox found in EPS file: '.$file);
  15749. }
  15750. $start = strpos($data, '%%EndSetup');
  15751. if ($start === false) {
  15752. $start = strpos($data, '%%EndProlog');
  15753. }
  15754. if ($start === false) {
  15755. $start = strpos($data, '%%BoundingBox');
  15756. }
  15757. $data = substr($data, $start);
  15758. $end = strpos($data, '%%PageTrailer');
  15759. if ($end===false) {
  15760. $end = strpos($data, 'showpage');
  15761. }
  15762. if ($end) {
  15763. $data = substr($data, 0, $end);
  15764. }
  15765. // calculate image width and height on document
  15766. if (($w <= 0) AND ($h <= 0)) {
  15767. $w = ($x2 - $x1) / $k;
  15768. $h = ($y2 - $y1) / $k;
  15769. } elseif ($w <= 0) {
  15770. $w = ($x2-$x1) / $k * ($h / (($y2 - $y1) / $k));
  15771. } elseif ($h <= 0) {
  15772. $h = ($y2 - $y1) / $k * ($w / (($x2 - $x1) / $k));
  15773. }
  15774. // fit the image on available space
  15775. $this->fitBlock($w, $h, $x, $y, $fitonpage);
  15776. if ($this->rasterize_vector_images) {
  15777. // convert EPS to raster image using GD or ImageMagick libraries
  15778. return $this->Image($file, $x, $y, $w, $h, 'EPS', $link, $align, true, 300, $palign, false, false, $border, false, false, $fitonpage);
  15779. }
  15780. // set scaling factors
  15781. $scale_x = $w / (($x2 - $x1) / $k);
  15782. $scale_y = $h / (($y2 - $y1) / $k);
  15783. // set alignment
  15784. $this->img_rb_y = $y + $h;
  15785. // set alignment
  15786. if ($this->rtl) {
  15787. if ($palign == 'L') {
  15788. $ximg = $this->lMargin;
  15789. } elseif ($palign == 'C') {
  15790. $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
  15791. } elseif ($palign == 'R') {
  15792. $ximg = $this->w - $this->rMargin - $w;
  15793. } else {
  15794. $ximg = $x - $w;
  15795. }
  15796. $this->img_rb_x = $ximg;
  15797. } else {
  15798. if ($palign == 'L') {
  15799. $ximg = $this->lMargin;
  15800. } elseif ($palign == 'C') {
  15801. $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
  15802. } elseif ($palign == 'R') {
  15803. $ximg = $this->w - $this->rMargin - $w;
  15804. } else {
  15805. $ximg = $x;
  15806. }
  15807. $this->img_rb_x = $ximg + $w;
  15808. }
  15809. if ($useBoundingBox) {
  15810. $dx = $ximg * $k - $x1;
  15811. $dy = $y * $k - $y1;
  15812. } else {
  15813. $dx = $ximg * $k;
  15814. $dy = $y * $k;
  15815. }
  15816. // save the current graphic state
  15817. $this->_out('q'.$this->epsmarker);
  15818. // translate
  15819. $this->_out(sprintf('%.3F %.3F %.3F %.3F %.3F %.3F cm', 1, 0, 0, 1, $dx, $dy + ($this->hPt - (2 * $y * $k) - ($y2 - $y1))));
  15820. // scale
  15821. if (isset($scale_x)) {
  15822. $this->_out(sprintf('%.3F %.3F %.3F %.3F %.3F %.3F cm', $scale_x, 0, 0, $scale_y, $x1 * (1 - $scale_x), $y2 * (1 - $scale_y)));
  15823. }
  15824. // handle pc/unix/mac line endings
  15825. preg_match('/[\r\n]+/s', $data, $regs);
  15826. $lines = explode($regs[0], $data);
  15827. $u=0;
  15828. $cnt = count($lines);
  15829. for ($i=0; $i < $cnt; ++$i) {
  15830. $line = $lines[$i];
  15831. if (($line == '') OR ($line{0} == '%')) {
  15832. continue;
  15833. }
  15834. $len = strlen($line);
  15835. $chunks = explode(' ', $line);
  15836. $cmd = array_pop($chunks);
  15837. // RGB
  15838. if (($cmd == 'Xa') OR ($cmd == 'XA')) {
  15839. $b = array_pop($chunks);
  15840. $g = array_pop($chunks);
  15841. $r = array_pop($chunks);
  15842. $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!
  15843. continue;
  15844. }
  15845. switch ($cmd) {
  15846. case 'm':
  15847. case 'l':
  15848. case 'v':
  15849. case 'y':
  15850. case 'c':
  15851. case 'k':
  15852. case 'K':
  15853. case 'g':
  15854. case 'G':
  15855. case 's':
  15856. case 'S':
  15857. case 'J':
  15858. case 'j':
  15859. case 'w':
  15860. case 'M':
  15861. case 'd':
  15862. case 'n': {
  15863. $this->_out($line);
  15864. break;
  15865. }
  15866. case 'x': {// custom fill color
  15867. list($c,$m,$y,$k) = $chunks;
  15868. $this->_out(''.$c.' '.$m.' '.$y.' '.$k.' k');
  15869. break;
  15870. }
  15871. case 'X': { // custom stroke color
  15872. list($c,$m,$y,$k) = $chunks;
  15873. $this->_out(''.$c.' '.$m.' '.$y.' '.$k.' K');
  15874. break;
  15875. }
  15876. case 'Y':
  15877. case 'N':
  15878. case 'V':
  15879. case 'L':
  15880. case 'C': {
  15881. $line{$len-1} = strtolower($cmd);
  15882. $this->_out($line);
  15883. break;
  15884. }
  15885. case 'b':
  15886. case 'B': {
  15887. $this->_out($cmd . '*');
  15888. break;
  15889. }
  15890. case 'f':
  15891. case 'F': {
  15892. if ($u > 0) {
  15893. $isU = false;
  15894. $max = min($i+5, $cnt);
  15895. for ($j=$i+1; $j < $max; ++$j) {
  15896. $isU = ($isU OR (($lines[$j] == 'U') OR ($lines[$j] == '*U')));
  15897. }
  15898. if ($isU) {
  15899. $this->_out('f*');
  15900. }
  15901. } else {
  15902. $this->_out('f*');
  15903. }
  15904. break;
  15905. }
  15906. case '*u': {
  15907. ++$u;
  15908. break;
  15909. }
  15910. case '*U': {
  15911. --$u;
  15912. break;
  15913. }
  15914. }
  15915. }
  15916. // restore previous graphic state
  15917. $this->_out($this->epsmarker.'Q');
  15918. if (!empty($border)) {
  15919. $bx = $this->x;
  15920. $by = $this->y;
  15921. $this->x = $ximg;
  15922. if ($this->rtl) {
  15923. $this->x += $w;
  15924. }
  15925. $this->y = $y;
  15926. $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
  15927. $this->x = $bx;
  15928. $this->y = $by;
  15929. }
  15930. if ($link) {
  15931. $this->Link($ximg, $y, $w, $h, $link, 0);
  15932. }
  15933. // set pointer to align the next text/objects
  15934. switch($align) {
  15935. case 'T':{
  15936. $this->y = $y;
  15937. $this->x = $this->img_rb_x;
  15938. break;
  15939. }
  15940. case 'M':{
  15941. $this->y = $y + round($h/2);
  15942. $this->x = $this->img_rb_x;
  15943. break;
  15944. }
  15945. case 'B':{
  15946. $this->y = $this->img_rb_y;
  15947. $this->x = $this->img_rb_x;
  15948. break;
  15949. }
  15950. case 'N':{
  15951. $this->SetY($this->img_rb_y);
  15952. break;
  15953. }
  15954. default:{
  15955. break;
  15956. }
  15957. }
  15958. $this->endlinex = $this->img_rb_x;
  15959. }
  15960. /**
  15961. * Set document barcode.
  15962. * @param string $bc barcode
  15963. * @access public
  15964. */
  15965. public function setBarcode($bc='') {
  15966. $this->barcode = $bc;
  15967. }
  15968. /**
  15969. * Get current barcode.
  15970. * @return string
  15971. * @access public
  15972. * @since 4.0.012 (2008-07-24)
  15973. */
  15974. public function getBarcode() {
  15975. return $this->barcode;
  15976. }
  15977. /**
  15978. * Print a Linear Barcode.
  15979. * @param string $code code to print
  15980. * @param string $type type of barcode (see barcodes.php for supported formats).
  15981. * @param int $x x position in user units (empty string = current x position)
  15982. * @param int $y y position in user units (empty string = current y position)
  15983. * @param int $w width in user units (empty string = remaining page width)
  15984. * @param int $h height in user units (empty string = remaining page height)
  15985. * @param float $xres width of the smallest bar in user units (empty string = default value = 0.4mm)
  15986. * @param array $style array of options:<ul>
  15987. * <li>boolean $style['border'] if true prints a border</li>
  15988. * <li>int $style['padding'] padding to leave around the barcode in user units (set to 'auto' for automatic padding)</li>
  15989. * <li>int $style['hpadding'] horizontal padding in user units (set to 'auto' for automatic padding)</li>
  15990. * <li>int $style['vpadding'] vertical padding in user units (set to 'auto' for automatic padding)</li>
  15991. * <li>array $style['fgcolor'] color array for bars and text</li>
  15992. * <li>mixed $style['bgcolor'] color array for background (set to false for transparent)</li>
  15993. * <li>boolean $style['text'] if true prints text below the barcode</li>
  15994. * <li>string $style['label'] override default label</li>
  15995. * <li>string $style['font'] font name for text</li><li>int $style['fontsize'] font size for text</li>
  15996. * <li>int $style['stretchtext']: 0 = disabled; 1 = horizontal scaling only if necessary; 2 = forced horizontal scaling; 3 = character spacing only if necessary; 4 = forced character spacing.</li>
  15997. * <li>string $style['position'] horizontal position of the containing barcode cell on the page: L = left margin; C = center; R = right margin.</li>
  15998. * <li>string $style['align'] horizontal position of the barcode on the containing rectangle: L = left; C = center; R = right.</li>
  15999. * <li>string $style['stretch'] if true stretch the barcode to best fit the available width, otherwise uses $xres resolution for a single bar.</li>
  16000. * <li>string $style['fitwidth'] if true reduce the width to fit the barcode width + padding. When this option is enabled the 'stretch' option is automatically disabled.</li>
  16001. * <li>string $style['cellfitalign'] this option works only when 'fitwidth' is true and 'position' is unset or empty. Set the horizontal position of the containing barcode cell inside the specified rectangle: L = left; C = center; R = right.</li></ul>
  16002. * @param string $align Indicates the alignment of the pointer next to barcode insertion relative to barcode height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
  16003. * @author Nicola Asuni
  16004. * @since 3.1.000 (2008-06-09)
  16005. * @access public
  16006. */
  16007. public function write1DBarcode($code, $type, $x='', $y='', $w='', $h='', $xres='', $style='', $align='') {
  16008. if ($this->empty_string(trim($code))) {
  16009. return;
  16010. }
  16011. require_once(dirname(__FILE__).'/barcodes.php');
  16012. // save current graphic settings
  16013. $gvars = $this->getGraphicVars();
  16014. // create new barcode object
  16015. $barcodeobj = new TCPDFBarcode($code, $type);
  16016. $arrcode = $barcodeobj->getBarcodeArray();
  16017. if ($arrcode === false) {
  16018. $this->Error('Error in 1D barcode string');
  16019. }
  16020. // set default values
  16021. if (!isset($style['position'])) {
  16022. $style['position'] = '';
  16023. } elseif ($style['position'] == 'S') {
  16024. // keep this for backward compatibility
  16025. $style['position'] = '';
  16026. $style['stretch'] = true;
  16027. }
  16028. if (!isset($style['fitwidth'])) {
  16029. if (!isset($style['stretch'])) {
  16030. $style['fitwidth'] = true;
  16031. } else {
  16032. $style['fitwidth'] = false;
  16033. }
  16034. }
  16035. if ($style['fitwidth']) {
  16036. // disable stretch
  16037. $style['stretch'] = false;
  16038. }
  16039. if (!isset($style['stretch'])) {
  16040. if (($w === '') OR ($w <= 0)) {
  16041. $style['stretch'] = false;
  16042. } else {
  16043. $style['stretch'] = true;
  16044. }
  16045. }
  16046. if (!isset($style['fgcolor'])) {
  16047. $style['fgcolor'] = array(0,0,0); // default black
  16048. }
  16049. if (!isset($style['bgcolor'])) {
  16050. $style['bgcolor'] = false; // default transparent
  16051. }
  16052. if (!isset($style['border'])) {
  16053. $style['border'] = false;
  16054. }
  16055. $fontsize = 0;
  16056. if (!isset($style['text'])) {
  16057. $style['text'] = false;
  16058. }
  16059. if ($style['text'] AND isset($style['font'])) {
  16060. if (isset($style['fontsize'])) {
  16061. $fontsize = $style['fontsize'];
  16062. }
  16063. $this->SetFont($style['font'], '', $fontsize);
  16064. }
  16065. if (!isset($style['stretchtext'])) {
  16066. $style['stretchtext'] = 4;
  16067. }
  16068. if ($x === '') {
  16069. $x = $this->x;
  16070. }
  16071. if ($y === '') {
  16072. $y = $this->y;
  16073. }
  16074. // check page for no-write regions and adapt page margins if necessary
  16075. $this->checkPageRegions($h, $x, $y);
  16076. if (($w === '') OR ($w <= 0)) {
  16077. if ($this->rtl) {
  16078. $w = $x - $this->lMargin;
  16079. } else {
  16080. $w = $this->w - $this->rMargin - $x;
  16081. }
  16082. }
  16083. // padding
  16084. if (!isset($style['padding'])) {
  16085. $padding = 0;
  16086. } elseif ($style['padding'] === 'auto') {
  16087. $padding = 10 * ($w / ($arrcode['maxw'] + 20));
  16088. } else {
  16089. $padding = floatval($style['padding']);
  16090. }
  16091. // horizontal padding
  16092. if (!isset($style['hpadding'])) {
  16093. $hpadding = $padding;
  16094. } elseif ($style['hpadding'] === 'auto') {
  16095. $hpadding = 10 * ($w / ($arrcode['maxw'] + 20));
  16096. } else {
  16097. $hpadding = floatval($style['hpadding']);
  16098. }
  16099. // vertical padding
  16100. if (!isset($style['vpadding'])) {
  16101. $vpadding = $padding;
  16102. } elseif ($style['vpadding'] === 'auto') {
  16103. $vpadding = ($hpadding / 2);
  16104. } else {
  16105. $vpadding = floatval($style['vpadding']);
  16106. }
  16107. // calculate xres (single bar width)
  16108. $max_xres = ($w - (2 * $hpadding)) / $arrcode['maxw'];
  16109. if ($style['stretch']) {
  16110. $xres = $max_xres;
  16111. } else {
  16112. if ($this->empty_string($xres)) {
  16113. $xres = (0.141 * $this->k); // default bar width = 0.4 mm
  16114. }
  16115. if ($xres > $max_xres) {
  16116. // correct xres to fit on $w
  16117. $xres = $max_xres;
  16118. }
  16119. if ((isset($style['padding']) AND ($style['padding'] === 'auto'))
  16120. OR (isset($style['hpadding']) AND ($style['hpadding'] === 'auto'))) {
  16121. $hpadding = 10 * $xres;
  16122. if (isset($style['vpadding']) AND ($style['vpadding'] === 'auto')) {
  16123. $vpadding = ($hpadding / 2);
  16124. }
  16125. }
  16126. }
  16127. if ($style['fitwidth']) {
  16128. $wold = $w;
  16129. $w = (($arrcode['maxw'] * $xres) + (2 * $hpadding));
  16130. if (isset($style['cellfitalign'])) {
  16131. switch ($style['cellfitalign']) {
  16132. case 'L': {
  16133. if ($this->rtl) {
  16134. $x -= ($wold - $w);
  16135. }
  16136. break;
  16137. }
  16138. case 'R': {
  16139. if (!$this->rtl) {
  16140. $x += ($wold - $w);
  16141. }
  16142. break;
  16143. }
  16144. case 'C': {
  16145. if ($this->rtl) {
  16146. $x -= (($wold - $w) / 2);
  16147. } else {
  16148. $x += (($wold - $w) / 2);
  16149. }
  16150. break;
  16151. }
  16152. default : {
  16153. break;
  16154. }
  16155. }
  16156. }
  16157. }
  16158. $text_height = ($this->cell_height_ratio * $fontsize / $this->k);
  16159. // height
  16160. if (($h === '') OR ($h <= 0)) {
  16161. // set default height
  16162. $h = (($arrcode['maxw'] * $xres) / 3) + (2 * $vpadding) + $text_height;
  16163. }
  16164. $barh = $h - $text_height - (2 * $vpadding);
  16165. if ($barh <=0) {
  16166. // try to reduce font or padding to fit barcode on available height
  16167. if ($text_height > $h) {
  16168. $fontsize = (($h * $this->k) / (4 * $this->cell_height_ratio));
  16169. $text_height = ($this->cell_height_ratio * $fontsize / $this->k);
  16170. $this->SetFont($style['font'], '', $fontsize);
  16171. }
  16172. if ($vpadding > 0) {
  16173. $vpadding = (($h - $text_height) / 4);
  16174. }
  16175. $barh = $h - $text_height - (2 * $vpadding);
  16176. }
  16177. // fit the barcode on available space
  16178. $this->fitBlock($w, $h, $x, $y, false);
  16179. // set alignment
  16180. $this->img_rb_y = $y + $h;
  16181. // set alignment
  16182. if ($this->rtl) {
  16183. if ($style['position'] == 'L') {
  16184. $xpos = $this->lMargin;
  16185. } elseif ($style['position'] == 'C') {
  16186. $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
  16187. } elseif ($style['position'] == 'R') {
  16188. $xpos = $this->w - $this->rMargin - $w;
  16189. } else {
  16190. $xpos = $x - $w;
  16191. }
  16192. $this->img_rb_x = $xpos;
  16193. } else {
  16194. if ($style['position'] == 'L') {
  16195. $xpos = $this->lMargin;
  16196. } elseif ($style['position'] == 'C') {
  16197. $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
  16198. } elseif ($style['position'] == 'R') {
  16199. $xpos = $this->w - $this->rMargin - $w;
  16200. } else {
  16201. $xpos = $x;
  16202. }
  16203. $this->img_rb_x = $xpos + $w;
  16204. }
  16205. $xpos_rect = $xpos;
  16206. if (!isset($style['align'])) {
  16207. $style['align'] = 'C';
  16208. }
  16209. switch ($style['align']) {
  16210. case 'L': {
  16211. $xpos = $xpos_rect + $hpadding;
  16212. break;
  16213. }
  16214. case 'R': {
  16215. $xpos = $xpos_rect + ($w - ($arrcode['maxw'] * $xres)) - $hpadding;
  16216. break;
  16217. }
  16218. case 'C':
  16219. default : {
  16220. $xpos = $xpos_rect + (($w - ($arrcode['maxw'] * $xres)) / 2);
  16221. break;
  16222. }
  16223. }
  16224. $xpos_text = $xpos;
  16225. // barcode is always printed in LTR direction
  16226. $tempRTL = $this->rtl;
  16227. $this->rtl = false;
  16228. // print background color
  16229. if ($style['bgcolor']) {
  16230. $this->Rect($xpos_rect, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
  16231. } elseif ($style['border']) {
  16232. $this->Rect($xpos_rect, $y, $w, $h, 'D');
  16233. }
  16234. // set foreground color
  16235. $this->SetDrawColorArray($style['fgcolor']);
  16236. $this->SetTextColorArray($style['fgcolor']);
  16237. // print bars
  16238. foreach ($arrcode['bcode'] as $k => $v) {
  16239. $bw = ($v['w'] * $xres);
  16240. if ($v['t']) {
  16241. // draw a vertical bar
  16242. $ypos = $y + $vpadding + ($v['p'] * $barh / $arrcode['maxh']);
  16243. $this->Rect($xpos, $ypos, $bw, ($v['h'] * $barh / $arrcode['maxh']), 'F', array(), $style['fgcolor']);
  16244. }
  16245. $xpos += $bw;
  16246. }
  16247. // print text
  16248. if ($style['text']) {
  16249. if (isset($style['label']) AND !$this->empty_string($style['label'])) {
  16250. $label = $style['label'];
  16251. } else {
  16252. $label = $code;
  16253. }
  16254. $txtwidth = ($arrcode['maxw'] * $xres);
  16255. if ($this->GetStringWidth($label) > $txtwidth) {
  16256. $style['stretchtext'] = 2;
  16257. }
  16258. // print text
  16259. $this->x = $xpos_text;
  16260. $this->y = $y + $vpadding + $barh;
  16261. $cellpadding = $this->cell_padding;
  16262. $this->SetCellPadding(0);
  16263. $this->Cell($txtwidth, '', $label, 0, 0, 'C', 0, '', $style['stretchtext'], false, 'T', 'T');
  16264. $this->cell_padding = $cellpadding;
  16265. }
  16266. // restore original direction
  16267. $this->rtl = $tempRTL;
  16268. // restore previous settings
  16269. $this->setGraphicVars($gvars);
  16270. // set pointer to align the next text/objects
  16271. switch($align) {
  16272. case 'T':{
  16273. $this->y = $y;
  16274. $this->x = $this->img_rb_x;
  16275. break;
  16276. }
  16277. case 'M':{
  16278. $this->y = $y + round($h / 2);
  16279. $this->x = $this->img_rb_x;
  16280. break;
  16281. }
  16282. case 'B':{
  16283. $this->y = $this->img_rb_y;
  16284. $this->x = $this->img_rb_x;
  16285. break;
  16286. }
  16287. case 'N':{
  16288. $this->SetY($this->img_rb_y);
  16289. break;
  16290. }
  16291. default:{
  16292. break;
  16293. }
  16294. }
  16295. $this->endlinex = $this->img_rb_x;
  16296. }
  16297. /**
  16298. * This function is DEPRECATED, please use the new write1DBarcode() function.
  16299. * @param int $x x position in user units
  16300. * @param int $y y position in user units
  16301. * @param int $w width in user units
  16302. * @param int $h height position in user units
  16303. * @param string $type type of barcode
  16304. * @param string $style barcode style
  16305. * @param string $font font for text
  16306. * @param int $xres x resolution
  16307. * @param string $code code to print
  16308. * @deprecated deprecated since version 3.1.000 (2008-06-10)
  16309. * @access public
  16310. * @see write1DBarcode()
  16311. */
  16312. public function writeBarcode($x, $y, $w, $h, $type, $style, $font, $xres, $code) {
  16313. // convert old settings for the new write1DBarcode() function.
  16314. $xres = 1 / $xres;
  16315. $newstyle = array(
  16316. 'position' => '',
  16317. 'align' => '',
  16318. 'stretch' => false,
  16319. 'fitwidth' => false,
  16320. 'cellfitalign' => '',
  16321. 'border' => false,
  16322. 'padding' => 0,
  16323. 'fgcolor' => array(0,0,0),
  16324. 'bgcolor' => false,
  16325. 'text' => true,
  16326. 'font' => $font,
  16327. 'fontsize' => 8,
  16328. 'stretchtext' => 4
  16329. );
  16330. if ($style & 1) {
  16331. $newstyle['border'] = true;
  16332. }
  16333. if ($style & 2) {
  16334. $newstyle['bgcolor'] = false;
  16335. }
  16336. if ($style & 4) {
  16337. $newstyle['position'] = 'C';
  16338. } elseif ($style & 8) {
  16339. $newstyle['position'] = 'L';
  16340. } elseif ($style & 16) {
  16341. $newstyle['position'] = 'R';
  16342. }
  16343. if ($style & 128) {
  16344. $newstyle['text'] = true;
  16345. }
  16346. if ($style & 256) {
  16347. $newstyle['stretchtext'] = 4;
  16348. }
  16349. $this->write1DBarcode($code, $type, $x, $y, $w, $h, $xres, $newstyle, '');
  16350. }
  16351. /**
  16352. * Print 2D Barcode.
  16353. * @param string $code code to print
  16354. * @param string $type type of barcode (see 2dbarcodes.php for supported formats).
  16355. * @param int $x x position in user units
  16356. * @param int $y y position in user units
  16357. * @param int $w width in user units
  16358. * @param int $h height in user units
  16359. * @param array $style array of options:<ul>
  16360. * <li>boolean $style['border'] if true prints a border around the barcode</li>
  16361. * <li>int $style['padding'] padding to leave around the barcode in barcode units (set to 'auto' for automatic padding)</li>
  16362. * <li>int $style['hpadding'] horizontal padding in barcode units (set to 'auto' for automatic padding)</li>
  16363. * <li>int $style['vpadding'] vertical padding in barcode units (set to 'auto' for automatic padding)</li>
  16364. * <li>int $style['module_width'] width of a single module in points</li>
  16365. * <li>int $style['module_height'] height of a single module in points</li>
  16366. * <li>array $style['fgcolor'] color array for bars and text</li>
  16367. * <li>mixed $style['bgcolor'] color array for background or false for transparent</li>
  16368. * <li>string $style['position'] barcode position on the page: L = left margin; C = center; R = right margin; S = stretch</li><li>$style['module_width'] width of a single module in points</li>
  16369. * <li>$style['module_height'] height of a single module in points</li></ul>
  16370. * @param string $align Indicates the alignment of the pointer next to barcode insertion relative to barcode height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
  16371. * @param boolean $distort if true distort the barcode to fit width and height, otherwise preserve aspect ratio
  16372. * @author Nicola Asuni
  16373. * @since 4.5.037 (2009-04-07)
  16374. * @access public
  16375. */
  16376. public function write2DBarcode($code, $type, $x='', $y='', $w='', $h='', $style='', $align='', $distort=false) {
  16377. if ($this->empty_string(trim($code))) {
  16378. return;
  16379. }
  16380. require_once(dirname(__FILE__).'/2dbarcodes.php');
  16381. // save current graphic settings
  16382. $gvars = $this->getGraphicVars();
  16383. // create new barcode object
  16384. $barcodeobj = new TCPDF2DBarcode($code, $type);
  16385. $arrcode = $barcodeobj->getBarcodeArray();
  16386. if (($arrcode === false) OR empty($arrcode)) {
  16387. $this->Error('Error in 2D barcode string');
  16388. }
  16389. // set default values
  16390. if (!isset($style['position'])) {
  16391. $style['position'] = '';
  16392. }
  16393. if (!isset($style['fgcolor'])) {
  16394. $style['fgcolor'] = array(0,0,0); // default black
  16395. }
  16396. if (!isset($style['bgcolor'])) {
  16397. $style['bgcolor'] = false; // default transparent
  16398. }
  16399. if (!isset($style['border'])) {
  16400. $style['border'] = false;
  16401. }
  16402. // padding
  16403. if (!isset($style['padding'])) {
  16404. $style['padding'] = 0;
  16405. } elseif ($style['padding'] === 'auto') {
  16406. $style['padding'] = 4;
  16407. }
  16408. if (!isset($style['hpadding'])) {
  16409. $style['hpadding'] = $style['padding'];
  16410. } elseif ($style['hpadding'] === 'auto') {
  16411. $style['hpadding'] = 4;
  16412. }
  16413. if (!isset($style['vpadding'])) {
  16414. $style['vpadding'] = $style['padding'];
  16415. } elseif ($style['vpadding'] === 'auto') {
  16416. $style['vpadding'] = 4;
  16417. }
  16418. // cell (module) dimension
  16419. if (!isset($style['module_width'])) {
  16420. $style['module_width'] = 1; // width of a single module in points
  16421. }
  16422. if (!isset($style['module_height'])) {
  16423. $style['module_height'] = 1; // height of a single module in points
  16424. }
  16425. if ($x === '') {
  16426. $x = $this->x;
  16427. }
  16428. if ($y === '') {
  16429. $y = $this->y;
  16430. }
  16431. // check page for no-write regions and adapt page margins if necessary
  16432. $this->checkPageRegions($h, $x, $y);
  16433. // number of barcode columns and rows
  16434. $rows = $arrcode['num_rows'];
  16435. $cols = $arrcode['num_cols'];
  16436. // module width and height
  16437. $mw = $style['module_width'];
  16438. $mh = $style['module_height'];
  16439. // get max dimensions
  16440. if ($this->rtl) {
  16441. $maxw = $x - $this->lMargin;
  16442. } else {
  16443. $maxw = $this->w - $this->rMargin - $x;
  16444. }
  16445. $maxh = ($this->h - $this->tMargin - $this->bMargin);
  16446. $ratioHW = ($rows * $mh) / ($cols * $mw);
  16447. $ratioWH = ($cols * $mw) / ($rows * $mh);
  16448. if (!$distort) {
  16449. if (($maxw * $ratioHW) > $maxh) {
  16450. $maxw = $maxh * $ratioWH;
  16451. }
  16452. if (($maxh * $ratioWH) > $maxw) {
  16453. $maxh = $maxw * $ratioHW;
  16454. }
  16455. }
  16456. // set maximum dimesions
  16457. if ($w > $maxw) {
  16458. $w = $maxw;
  16459. }
  16460. if ($h > $maxh) {
  16461. $h = $maxh;
  16462. }
  16463. $hpad = (2 * $style['hpadding']);
  16464. $vpad = (2 * $style['vpadding']);
  16465. // set dimensions
  16466. if ((($w === '') OR ($w <= 0)) AND (($h === '') OR ($h <= 0))) {
  16467. $w = ($cols + $hpad) * ($mw / $this->k);
  16468. $h = ($rows + $vpad) * ($mh / $this->k);
  16469. } elseif (($w === '') OR ($w <= 0)) {
  16470. $w = $h * $ratioWH;
  16471. } elseif (($h === '') OR ($h <= 0)) {
  16472. $h = $w * $ratioHW;
  16473. }
  16474. // barcode size (excluding padding)
  16475. $bw = ($w * $cols) / ($cols + $hpad);
  16476. $bh = ($h * $rows) / ($rows + $vpad);
  16477. // dimension of single barcode cell unit
  16478. $cw = $bw / $cols;
  16479. $ch = $bh / $rows;
  16480. if (!$distort) {
  16481. if (($cw / $ch) > ($mw / $mh)) {
  16482. // correct horizontal distortion
  16483. $cw = $ch * $mw / $mh;
  16484. $bw = $cw * $cols;
  16485. $style['hpadding'] = ($w - $bw) / (2 * $cw);
  16486. } else {
  16487. // correct vertical distortion
  16488. $ch = $cw * $mh / $mw;
  16489. $bh = $ch * $rows;
  16490. $style['vpadding'] = ($h - $bh) / (2 * $ch);
  16491. }
  16492. }
  16493. // fit the barcode on available space
  16494. $this->fitBlock($w, $h, $x, $y, false);
  16495. // set alignment
  16496. $this->img_rb_y = $y + $h;
  16497. // set alignment
  16498. if ($this->rtl) {
  16499. if ($style['position'] == 'L') {
  16500. $xpos = $this->lMargin;
  16501. } elseif ($style['position'] == 'C') {
  16502. $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
  16503. } elseif ($style['position'] == 'R') {
  16504. $xpos = $this->w - $this->rMargin - $w;
  16505. } else {
  16506. $xpos = $x - $w;
  16507. }
  16508. $this->img_rb_x = $xpos;
  16509. } else {
  16510. if ($style['position'] == 'L') {
  16511. $xpos = $this->lMargin;
  16512. } elseif ($style['position'] == 'C') {
  16513. $xpos = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
  16514. } elseif ($style['position'] == 'R') {
  16515. $xpos = $this->w - $this->rMargin - $w;
  16516. } else {
  16517. $xpos = $x;
  16518. }
  16519. $this->img_rb_x = $xpos + $w;
  16520. }
  16521. $xstart = $xpos + ($style['hpadding'] * $cw);
  16522. $ystart = $y + ($style['vpadding'] * $ch);
  16523. // barcode is always printed in LTR direction
  16524. $tempRTL = $this->rtl;
  16525. $this->rtl = false;
  16526. // print background color
  16527. if ($style['bgcolor']) {
  16528. $this->Rect($xpos, $y, $w, $h, $style['border'] ? 'DF' : 'F', '', $style['bgcolor']);
  16529. } elseif ($style['border']) {
  16530. $this->Rect($xpos, $y, $w, $h, 'D');
  16531. }
  16532. // set foreground color
  16533. $this->SetDrawColorArray($style['fgcolor']);
  16534. // print barcode cells
  16535. // for each row
  16536. for ($r = 0; $r < $rows; ++$r) {
  16537. $xr = $xstart;
  16538. // for each column
  16539. for ($c = 0; $c < $cols; ++$c) {
  16540. if ($arrcode['bcode'][$r][$c] == 1) {
  16541. // draw a single barcode cell
  16542. $this->Rect($xr, $ystart, $cw, $ch, 'F', array(), $style['fgcolor']);
  16543. }
  16544. $xr += $cw;
  16545. }
  16546. $ystart += $ch;
  16547. }
  16548. // restore original direction
  16549. $this->rtl = $tempRTL;
  16550. // restore previous settings
  16551. $this->setGraphicVars($gvars);
  16552. // set pointer to align the next text/objects
  16553. switch($align) {
  16554. case 'T':{
  16555. $this->y = $y;
  16556. $this->x = $this->img_rb_x;
  16557. break;
  16558. }
  16559. case 'M':{
  16560. $this->y = $y + round($h/2);
  16561. $this->x = $this->img_rb_x;
  16562. break;
  16563. }
  16564. case 'B':{
  16565. $this->y = $this->img_rb_y;
  16566. $this->x = $this->img_rb_x;
  16567. break;
  16568. }
  16569. case 'N':{
  16570. $this->SetY($this->img_rb_y);
  16571. break;
  16572. }
  16573. default:{
  16574. break;
  16575. }
  16576. }
  16577. $this->endlinex = $this->img_rb_x;
  16578. }
  16579. /**
  16580. * Returns an array containing current margins:
  16581. * <ul>
  16582. <li>$ret['left'] = left margin</li>
  16583. <li>$ret['right'] = right margin</li>
  16584. <li>$ret['top'] = top margin</li>
  16585. <li>$ret['bottom'] = bottom margin</li>
  16586. <li>$ret['header'] = header margin</li>
  16587. <li>$ret['footer'] = footer margin</li>
  16588. <li>$ret['cell'] = cell padding array</li>
  16589. <li>$ret['padding_left'] = cell left padding</li>
  16590. <li>$ret['padding_top'] = cell top padding</li>
  16591. <li>$ret['padding_right'] = cell right padding</li>
  16592. <li>$ret['padding_bottom'] = cell bottom padding</li>
  16593. * </ul>
  16594. * @return array containing all margins measures
  16595. * @access public
  16596. * @since 3.2.000 (2008-06-23)
  16597. */
  16598. public function getMargins() {
  16599. $ret = array(
  16600. 'left' => $this->lMargin,
  16601. 'right' => $this->rMargin,
  16602. 'top' => $this->tMargin,
  16603. 'bottom' => $this->bMargin,
  16604. 'header' => $this->header_margin,
  16605. 'footer' => $this->footer_margin,
  16606. 'cell' => $this->cell_padding,
  16607. 'padding_left' => $this->cell_padding['L'],
  16608. 'padding_top' => $this->cell_padding['T'],
  16609. 'padding_right' => $this->cell_padding['R'],
  16610. 'padding_bottom' => $this->cell_padding['B']
  16611. );
  16612. return $ret;
  16613. }
  16614. /**
  16615. * Returns an array containing original margins:
  16616. * <ul>
  16617. <li>$ret['left'] = left margin</li>
  16618. <li>$ret['right'] = right margin</li>
  16619. * </ul>
  16620. * @return array containing all margins measures
  16621. * @access public
  16622. * @since 4.0.012 (2008-07-24)
  16623. */
  16624. public function getOriginalMargins() {
  16625. $ret = array(
  16626. 'left' => $this->original_lMargin,
  16627. 'right' => $this->original_rMargin
  16628. );
  16629. return $ret;
  16630. }
  16631. /**
  16632. * Returns the current font size.
  16633. * @return current font size
  16634. * @access public
  16635. * @since 3.2.000 (2008-06-23)
  16636. */
  16637. public function getFontSize() {
  16638. return $this->FontSize;
  16639. }
  16640. /**
  16641. * Returns the current font size in points unit.
  16642. * @return current font size in points unit
  16643. * @access public
  16644. * @since 3.2.000 (2008-06-23)
  16645. */
  16646. public function getFontSizePt() {
  16647. return $this->FontSizePt;
  16648. }
  16649. /**
  16650. * Returns the current font family name.
  16651. * @return string current font family name
  16652. * @access public
  16653. * @since 4.3.008 (2008-12-05)
  16654. */
  16655. public function getFontFamily() {
  16656. return $this->FontFamily;
  16657. }
  16658. /**
  16659. * Returns the current font style.
  16660. * @return string current font style
  16661. * @access public
  16662. * @since 4.3.008 (2008-12-05)
  16663. */
  16664. public function getFontStyle() {
  16665. return $this->FontStyle;
  16666. }
  16667. /**
  16668. * Extracts the CSS properties from a CSS string.
  16669. * @param string $cssdata string containing CSS definitions.
  16670. * @return An array where the keys are the CSS selectors and the values are the CSS properties.
  16671. * @author Nicola Asuni
  16672. * @since 5.1.000 (2010-05-25)
  16673. * @access protected
  16674. */
  16675. protected function extractCSSproperties($cssdata) {
  16676. if (empty($cssdata)) {
  16677. return array();
  16678. }
  16679. // remove comments
  16680. $cssdata = preg_replace('/\/\*[^\*]*\*\//', '', $cssdata);
  16681. // remove newlines and multiple spaces
  16682. $cssdata = preg_replace('/[\s]+/', ' ', $cssdata);
  16683. // remove some spaces
  16684. $cssdata = preg_replace('/[\s]*([;:\{\}]{1})[\s]*/', '\\1', $cssdata);
  16685. // remove empty blocks
  16686. $cssdata = preg_replace('/([^\}\{]+)\{\}/', '', $cssdata);
  16687. // replace media type parenthesis
  16688. $cssdata = preg_replace('/@media[\s]+([^\{]*)\{/i', '@media \\1§', $cssdata);
  16689. $cssdata = preg_replace('/\}\}/si', '}§', $cssdata);
  16690. // trim string
  16691. $cssdata = trim($cssdata);
  16692. // find media blocks (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
  16693. $cssblocks = array();
  16694. $matches = array();
  16695. if (preg_match_all('/@media[\s]+([^\§]*)§([^§]*)§/i', $cssdata, $matches) > 0) {
  16696. foreach ($matches[1] as $key => $type) {
  16697. $cssblocks[$type] = $matches[2][$key];
  16698. }
  16699. // remove media blocks
  16700. $cssdata = preg_replace('/@media[\s]+([^\§]*)§([^§]*)§/i', '', $cssdata);
  16701. }
  16702. // keep 'all' and 'print' media, other media types are discarded
  16703. if (isset($cssblocks['all']) AND !empty($cssblocks['all'])) {
  16704. $cssdata .= $cssblocks['all'];
  16705. }
  16706. if (isset($cssblocks['print']) AND !empty($cssblocks['print'])) {
  16707. $cssdata .= $cssblocks['print'];
  16708. }
  16709. // reset css blocks array
  16710. $cssblocks = array();
  16711. $matches = array();
  16712. // explode css data string into array
  16713. if (substr($cssdata, -1) == '}') {
  16714. // remove last parethesis
  16715. $cssdata = substr($cssdata, 0, -1);
  16716. }
  16717. $matches = explode('}', $cssdata);
  16718. foreach ($matches as $key => $block) {
  16719. // index 0 contains the CSS selector, index 1 contains CSS properties
  16720. $cssblocks[$key] = explode('{', $block);
  16721. if (!isset($cssblocks[$key][1])) {
  16722. // remove empty definitions
  16723. unset($cssblocks[$key]);
  16724. }
  16725. }
  16726. // split groups of selectors (comma-separated list of selectors)
  16727. foreach ($cssblocks as $key => $block) {
  16728. if (strpos($block[0], ',') > 0) {
  16729. $selectors = explode(',', $block[0]);
  16730. foreach ($selectors as $sel) {
  16731. $cssblocks[] = array(0 => trim($sel), 1 => $block[1]);
  16732. }
  16733. unset($cssblocks[$key]);
  16734. }
  16735. }
  16736. // covert array to selector => properties
  16737. $cssdata = array();
  16738. foreach ($cssblocks as $block) {
  16739. $selector = $block[0];
  16740. // calculate selector's specificity
  16741. $matches = array();
  16742. $a = 0; // the declaration is not from is a 'style' attribute
  16743. $b = intval(preg_match_all('/[\#]/', $selector, $matches)); // number of ID attributes
  16744. $c = intval(preg_match_all('/[\[\.]/', $selector, $matches)); // number of other attributes
  16745. $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
  16746. $d = intval(preg_match_all('/[\>\+\~\s]{1}[a-zA-Z0-9\*]+/', ' '.$selector, $matches)); // number of element names
  16747. $d += intval(preg_match_all('/[\:][\:]/', $selector, $matches)); // number of pseudo-elements
  16748. $specificity = $a.$b.$c.$d;
  16749. // add specificity to the beginning of the selector
  16750. $cssdata[$specificity.' '.$selector] = $block[1];
  16751. }
  16752. // sort selectors alphabetically to account for specificity
  16753. ksort($cssdata, SORT_STRING);
  16754. // return array
  16755. return $cssdata;
  16756. }
  16757. /**
  16758. * Returns true if the CSS selector is valid for the selected HTML tag
  16759. * @param array $dom array of HTML tags and properties
  16760. * @param int $key key of the current HTML tag
  16761. * @param string $selector CSS selector string
  16762. * @return true if the selector is valid, false otherwise
  16763. * @access protected
  16764. * @since 5.1.000 (2010-05-25)
  16765. */
  16766. protected function isValidCSSSelectorForTag($dom, $key, $selector) {
  16767. $valid = false; // value to be returned
  16768. $tag = $dom[$key]['value'];
  16769. $class = array();
  16770. if (isset($dom[$key]['attribute']['class']) AND !empty($dom[$key]['attribute']['class'])) {
  16771. $class = explode(' ', strtolower($dom[$key]['attribute']['class']));
  16772. }
  16773. $id = '';
  16774. if (isset($dom[$key]['attribute']['id']) AND !empty($dom[$key]['attribute']['id'])) {
  16775. $id = strtolower($dom[$key]['attribute']['id']);
  16776. }
  16777. $selector = preg_replace('/([\>\+\~\s]{1})([\.]{1})([^\>\+\~\s]*)/si', '\\1*.\\3', $selector);
  16778. $matches = array();
  16779. if (preg_match_all('/([\>\+\~\s]{1})([a-zA-Z0-9\*]+)([^\>\+\~\s]*)/si', $selector, $matches, PREG_PATTERN_ORDER | PREG_OFFSET_CAPTURE) > 0) {
  16780. $parentop = array_pop($matches[1]);
  16781. $operator = $parentop[0];
  16782. $offset = $parentop[1];
  16783. $lasttag = array_pop($matches[2]);
  16784. $lasttag = strtolower(trim($lasttag[0]));
  16785. if (($lasttag == '*') OR ($lasttag == $tag)) {
  16786. // the last element on selector is our tag or 'any tag'
  16787. $attrib = array_pop($matches[3]);
  16788. $attrib = strtolower(trim($attrib[0]));
  16789. if (!empty($attrib)) {
  16790. // check if matches class, id, attribute, pseudo-class or pseudo-element
  16791. switch ($attrib{0}) {
  16792. case '.': { // class
  16793. if (in_array(substr($attrib, 1), $class)) {
  16794. $valid = true;
  16795. }
  16796. break;
  16797. }
  16798. case '#': { // ID
  16799. if (substr($attrib, 1) == $id) {
  16800. $valid = true;
  16801. }
  16802. break;
  16803. }
  16804. case '[': { // attribute
  16805. $attrmatch = array();
  16806. if (preg_match('/\[([a-zA-Z0-9]*)[\s]*([\~\^\$\*\|\=]*)[\s]*["]?([^"\]]*)["]?\]/i', $attrib, $attrmatch) > 0) {
  16807. $att = strtolower($attrmatch[1]);
  16808. $val = $attrmatch[3];
  16809. if (isset($dom[$key]['attribute'][$att])) {
  16810. switch ($attrmatch[2]) {
  16811. case '=': {
  16812. if ($dom[$key]['attribute'][$att] == $val) {
  16813. $valid = true;
  16814. }
  16815. break;
  16816. }
  16817. case '~=': {
  16818. if (in_array($val, explode(' ', $dom[$key]['attribute'][$att]))) {
  16819. $valid = true;
  16820. }
  16821. break;
  16822. }
  16823. case '^=': {
  16824. if ($val == substr($dom[$key]['attribute'][$att], 0, strlen($val))) {
  16825. $valid = true;
  16826. }
  16827. break;
  16828. }
  16829. case '$=': {
  16830. if ($val == substr($dom[$key]['attribute'][$att], -strlen($val))) {
  16831. $valid = true;
  16832. }
  16833. break;
  16834. }
  16835. case '*=': {
  16836. if (strpos($dom[$key]['attribute'][$att], $val) !== false) {
  16837. $valid = true;
  16838. }
  16839. break;
  16840. }
  16841. case '|=': {
  16842. if ($dom[$key]['attribute'][$att] == $val) {
  16843. $valid = true;
  16844. } elseif (preg_match('/'.$val.'[\-]{1}/i', $dom[$key]['attribute'][$att]) > 0) {
  16845. $valid = true;
  16846. }
  16847. break;
  16848. }
  16849. default: {
  16850. $valid = true;
  16851. }
  16852. }
  16853. }
  16854. }
  16855. break;
  16856. }
  16857. case ':': { // pseudo-class or pseudo-element
  16858. if ($attrib{1} == ':') { // pseudo-element
  16859. // pseudo-elements are not supported!
  16860. // (::first-line, ::first-letter, ::before, ::after)
  16861. } else { // pseudo-class
  16862. // pseudo-classes are not supported!
  16863. // (: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)
  16864. }
  16865. break;
  16866. }
  16867. } // end of switch
  16868. } else {
  16869. $valid = true;
  16870. }
  16871. if ($valid AND ($offset > 0)) {
  16872. $valid = false;
  16873. // check remaining selector part
  16874. $selector = substr($selector, 0, $offset);
  16875. switch ($operator) {
  16876. case ' ': { // descendant of an element
  16877. while ($dom[$key]['parent'] > 0) {
  16878. if ($this->isValidCSSSelectorForTag($dom, $dom[$key]['parent'], $selector)) {
  16879. $valid = true;
  16880. break;
  16881. } else {
  16882. $key = $dom[$key]['parent'];
  16883. }
  16884. }
  16885. break;
  16886. }
  16887. case '>': { // child of an element
  16888. $valid = $this->isValidCSSSelectorForTag($dom, $dom[$key]['parent'], $selector);
  16889. break;
  16890. }
  16891. case '+': { // immediately preceded by an element
  16892. for ($i = ($key - 1); $i > $dom[$key]['parent']; --$i) {
  16893. if ($dom[$i]['tag'] AND $dom[$i]['opening']) {
  16894. $valid = $this->isValidCSSSelectorForTag($dom, $i, $selector);
  16895. break;
  16896. }
  16897. }
  16898. break;
  16899. }
  16900. case '~': { // preceded by an element
  16901. for ($i = ($key - 1); $i > $dom[$key]['parent']; --$i) {
  16902. if ($dom[$i]['tag'] AND $dom[$i]['opening']) {
  16903. if ($this->isValidCSSSelectorForTag($dom, $i, $selector)) {
  16904. break;
  16905. }
  16906. }
  16907. }
  16908. break;
  16909. }
  16910. }
  16911. }
  16912. }
  16913. }
  16914. return $valid;
  16915. }
  16916. /**
  16917. * Returns the styles that apply for the selected HTML tag.
  16918. * @param array $dom array of HTML tags and properties
  16919. * @param int $key key of the current HTML tag
  16920. * @param array $css array of CSS properties
  16921. * @return string containing CSS properties
  16922. * @access protected
  16923. * @since 5.1.000 (2010-05-25)
  16924. */
  16925. protected function getTagStyleFromCSS($dom, $key, $css) {
  16926. $tagstyle = ''; // style to be returned
  16927. // get all styles that apply
  16928. foreach($css as $selector => $style) {
  16929. // remove specificity
  16930. $selector = substr($selector, strpos($selector, ' '));
  16931. // check if this selector apply to current tag
  16932. if ($this->isValidCSSSelectorForTag($dom, $key, $selector)) {
  16933. // apply style
  16934. $tagstyle .= ';'.$style;
  16935. }
  16936. }
  16937. if (isset($dom[$key]['attribute']['style'])) {
  16938. // attach inline style (latest properties have high priority)
  16939. $tagstyle .= ';'.$dom[$key]['attribute']['style'];
  16940. }
  16941. // remove multiple semicolons
  16942. $tagstyle = preg_replace('/[;]+/', ';', $tagstyle);
  16943. return $tagstyle;
  16944. }
  16945. /**
  16946. * Returns the border width from CSS property
  16947. * @param string $width border width
  16948. * @return int with in user units
  16949. * @access protected
  16950. * @since 5.7.000 (2010-08-02)
  16951. */
  16952. protected function getCSSBorderWidth($width) {
  16953. if ($width == 'thin') {
  16954. $width = (2 / $this->k);
  16955. } elseif ($width == 'medium') {
  16956. $width = (4 / $this->k);
  16957. } elseif ($width == 'thick') {
  16958. $width = (6 / $this->k);
  16959. } else {
  16960. $width = $this->getHTMLUnitToUnits($width, 1, 'px', false);
  16961. }
  16962. return $width;
  16963. }
  16964. /**
  16965. * Returns the border dash style from CSS property
  16966. * @param string $style border style to convert
  16967. * @return int sash style (return -1 in case of none or hidden border)
  16968. * @access protected
  16969. * @since 5.7.000 (2010-08-02)
  16970. */
  16971. protected function getCSSBorderDashStyle($style) {
  16972. switch (strtolower($style)) {
  16973. case 'none':
  16974. case 'hidden': {
  16975. $dash = -1;
  16976. break;
  16977. }
  16978. case 'dotted': {
  16979. $dash = 1;
  16980. break;
  16981. }
  16982. case 'dashed': {
  16983. $dash = 3;
  16984. break;
  16985. }
  16986. case 'double':
  16987. case 'groove':
  16988. case 'ridge':
  16989. case 'inset':
  16990. case 'outset':
  16991. case 'solid':
  16992. default: {
  16993. $dash = 0;
  16994. break;
  16995. }
  16996. }
  16997. return $dash;
  16998. }
  16999. /**
  17000. * Returns the border style array from CSS border properties
  17001. * @param string $cssborder border properties
  17002. * @return array containing border properties
  17003. * @access protected
  17004. * @since 5.7.000 (2010-08-02)
  17005. */
  17006. protected function getCSSBorderStyle($cssborder) {
  17007. $bprop = preg_split('/[\s]+/', trim($cssborder));
  17008. $border = array(); // value to be returned
  17009. switch (count($bprop)) {
  17010. case 3: {
  17011. $width = $bprop[0];
  17012. $style = $bprop[1];
  17013. $color = $bprop[2];
  17014. break;
  17015. }
  17016. case 2: {
  17017. $width = 'medium';
  17018. $style = $bprop[0];
  17019. $color = $bprop[1];
  17020. break;
  17021. }
  17022. case 1: {
  17023. $width = 'medium';
  17024. $style = $bprop[0];
  17025. $color = 'black';
  17026. break;
  17027. }
  17028. default: {
  17029. $width = 'medium';
  17030. $style = 'solid';
  17031. $color = 'black';
  17032. break;
  17033. }
  17034. }
  17035. if ($style == 'none') {
  17036. return array();
  17037. }
  17038. $border['cap'] = 'square';
  17039. $border['join'] = 'miter';
  17040. $border['dash'] = $this->getCSSBorderDashStyle($style);
  17041. if ($border['dash'] < 0) {
  17042. return array();
  17043. }
  17044. $border['width'] = $this->getCSSBorderWidth($width);
  17045. $border['color'] = $this->convertHTMLColorToDec($color);
  17046. return $border;
  17047. }
  17048. /**
  17049. * Get the internal Cell padding from CSS attribute.
  17050. * @param string $csspadding padding properties
  17051. * @param float $width width of the containing element
  17052. * @access public
  17053. * @since 5.9.000 (2010-10-04)
  17054. */
  17055. public function getCSSPadding($csspadding, $width=0) {
  17056. $padding = preg_split('/[\s]+/', trim($csspadding));
  17057. $cell_padding = array(); // value to be returned
  17058. switch (count($padding)) {
  17059. case 4: {
  17060. $cell_padding['T'] = $padding[0];
  17061. $cell_padding['R'] = $padding[1];
  17062. $cell_padding['B'] = $padding[2];
  17063. $cell_padding['L'] = $padding[3];
  17064. break;
  17065. }
  17066. case 3: {
  17067. $cell_padding['T'] = $padding[0];
  17068. $cell_padding['R'] = $padding[1];
  17069. $cell_padding['B'] = $padding[2];
  17070. $cell_padding['L'] = $padding[1];
  17071. break;
  17072. }
  17073. case 2: {
  17074. $cell_padding['T'] = $padding[0];
  17075. $cell_padding['R'] = $padding[1];
  17076. $cell_padding['B'] = $padding[0];
  17077. $cell_padding['L'] = $padding[1];
  17078. break;
  17079. }
  17080. case 1: {
  17081. $cell_padding['T'] = $padding[0];
  17082. $cell_padding['R'] = $padding[0];
  17083. $cell_padding['B'] = $padding[0];
  17084. $cell_padding['L'] = $padding[0];
  17085. break;
  17086. }
  17087. default: {
  17088. return $this->cell_padding;
  17089. }
  17090. }
  17091. if ($width == 0) {
  17092. $width = $this->w - $this->lMargin - $this->rMargin;
  17093. }
  17094. $cell_padding['T'] = $this->getHTMLUnitToUnits($cell_padding['T'], $width, 'px', false);
  17095. $cell_padding['R'] = $this->getHTMLUnitToUnits($cell_padding['R'], $width, 'px', false);
  17096. $cell_padding['B'] = $this->getHTMLUnitToUnits($cell_padding['B'], $width, 'px', false);
  17097. $cell_padding['L'] = $this->getHTMLUnitToUnits($cell_padding['L'], $width, 'px', false);
  17098. return $cell_padding;
  17099. }
  17100. /**
  17101. * Get the internal Cell margin from CSS attribute.
  17102. * @param string $cssmargin margin properties
  17103. * @param float $width width of the containing element
  17104. * @access public
  17105. * @since 5.9.000 (2010-10-04)
  17106. */
  17107. public function getCSSMargin($cssmargin, $width=0) {
  17108. $margin = preg_split('/[\s]+/', trim($cssmargin));
  17109. $cell_margin = array(); // value to be returned
  17110. switch (count($margin)) {
  17111. case 4: {
  17112. $cell_margin['T'] = $margin[0];
  17113. $cell_margin['R'] = $margin[1];
  17114. $cell_margin['B'] = $margin[2];
  17115. $cell_margin['L'] = $margin[3];
  17116. break;
  17117. }
  17118. case 3: {
  17119. $cell_margin['T'] = $margin[0];
  17120. $cell_margin['R'] = $margin[1];
  17121. $cell_margin['B'] = $margin[2];
  17122. $cell_margin['L'] = $margin[1];
  17123. break;
  17124. }
  17125. case 2: {
  17126. $cell_margin['T'] = $margin[0];
  17127. $cell_margin['R'] = $margin[1];
  17128. $cell_margin['B'] = $margin[0];
  17129. $cell_margin['L'] = $margin[1];
  17130. break;
  17131. }
  17132. case 1: {
  17133. $cell_margin['T'] = $margin[0];
  17134. $cell_margin['R'] = $margin[0];
  17135. $cell_margin['B'] = $margin[0];
  17136. $cell_margin['L'] = $margin[0];
  17137. break;
  17138. }
  17139. default: {
  17140. return $this->cell_margin;
  17141. }
  17142. }
  17143. if ($width == 0) {
  17144. $width = $this->w - $this->lMargin - $this->rMargin;
  17145. }
  17146. $cell_margin['T'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['T']), $width, 'px', false);
  17147. $cell_margin['R'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['R']), $width, 'px', false);
  17148. $cell_margin['B'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['B']), $width, 'px', false);
  17149. $cell_margin['L'] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $cell_margin['L']), $width, 'px', false);
  17150. return $cell_margin;
  17151. }
  17152. /**
  17153. * Returns the letter-spacing value from CSS value
  17154. * @param string $spacing letter-spacing value
  17155. * @param float $parent font spacing (tracking/kerning) value of the parent element
  17156. * @return float quantity to increases or decreases the space between characters in a text.
  17157. * @access protected
  17158. * @since 5.9.000 (2010-10-02)
  17159. */
  17160. protected function getCSSFontSpacing($spacing, $parent=0) {
  17161. $val = 0; // value to be returned
  17162. $spacing = trim($spacing);
  17163. switch ($spacing) {
  17164. case 'normal': {
  17165. $val = 0;
  17166. break;
  17167. }
  17168. case 'inherit': {
  17169. if ($parent == 'normal') {
  17170. $val = 0;
  17171. } else {
  17172. $val = $parent;
  17173. }
  17174. break;
  17175. }
  17176. default: {
  17177. $val = $this->getHTMLUnitToUnits($spacing, 0, 'px', false);
  17178. }
  17179. }
  17180. return $val;
  17181. }
  17182. /**
  17183. * Returns the percentage of font stretching from CSS value
  17184. * @param string $stretch stretch mode
  17185. * @param float $parent stretch value of the parent element
  17186. * @return float font stretching percentage
  17187. * @access protected
  17188. * @since 5.9.000 (2010-10-02)
  17189. */
  17190. protected function getCSSFontStretching($stretch, $parent=100) {
  17191. $val = 100; // value to be returned
  17192. $stretch = trim($stretch);
  17193. switch ($stretch) {
  17194. case 'ultra-condensed': {
  17195. $val = 40;
  17196. break;
  17197. }
  17198. case 'extra-condensed': {
  17199. $val = 55;
  17200. break;
  17201. }
  17202. case 'condensed': {
  17203. $val = 70;
  17204. break;
  17205. }
  17206. case 'semi-condensed': {
  17207. $val = 85;
  17208. break;
  17209. }
  17210. case 'normal': {
  17211. $val = 100;
  17212. break;
  17213. }
  17214. case 'semi-expanded': {
  17215. $val = 115;
  17216. break;
  17217. }
  17218. case 'expanded': {
  17219. $val = 130;
  17220. break;
  17221. }
  17222. case 'extra-expanded': {
  17223. $val = 145;
  17224. break;
  17225. }
  17226. case 'ultra-expanded': {
  17227. $val = 160;
  17228. break;
  17229. }
  17230. case 'wider': {
  17231. $val = $parent + 10;
  17232. break;
  17233. }
  17234. case 'narrower': {
  17235. $val = $parent - 10;
  17236. break;
  17237. }
  17238. case 'inherit': {
  17239. if ($parent == 'normal') {
  17240. $val = 100;
  17241. } else {
  17242. $val = $parent;
  17243. }
  17244. break;
  17245. }
  17246. default: {
  17247. $val = $this->getHTMLUnitToUnits($stretch, 100, '%', false);
  17248. }
  17249. }
  17250. return $val;
  17251. }
  17252. /**
  17253. * Returns the HTML DOM array.
  17254. * @param string $html html code
  17255. * @return array
  17256. * @access protected
  17257. * @since 3.2.000 (2008-06-20)
  17258. */
  17259. protected function getHtmlDomArray($html) {
  17260. // array of CSS styles ( selector => properties).
  17261. $css = array();
  17262. // get CSS array defined at previous call
  17263. $matches = array();
  17264. if (preg_match_all('/<cssarray>([^\<]*)<\/cssarray>/isU', $html, $matches) > 0) {
  17265. if (isset($matches[1][0])) {
  17266. $css = array_merge($css, unserialize($this->unhtmlentities($matches[1][0])));
  17267. }
  17268. $html = preg_replace('/<cssarray>(.*?)<\/cssarray>/isU', '', $html);
  17269. }
  17270. // extract external CSS files
  17271. $matches = array();
  17272. if (preg_match_all('/<link([^\>]*)>/isU', $html, $matches) > 0) {
  17273. foreach ($matches[1] as $key => $link) {
  17274. $type = array();
  17275. if (preg_match('/type[\s]*=[\s]*"text\/css"/', $link, $type)) {
  17276. $type = array();
  17277. preg_match('/media[\s]*=[\s]*"([^"]*)"/', $link, $type);
  17278. // get 'all' and 'print' media, other media types are discarded
  17279. // (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
  17280. if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
  17281. $type = array();
  17282. if (preg_match('/href[\s]*=[\s]*"([^"]*)"/', $link, $type) > 0) {
  17283. // read CSS data file
  17284. $cssdata = file_get_contents(trim($type[1]));
  17285. $css = array_merge($css, $this->extractCSSproperties($cssdata));
  17286. }
  17287. }
  17288. }
  17289. }
  17290. }
  17291. // extract style tags
  17292. $matches = array();
  17293. if (preg_match_all('/<style([^\>]*)>([^\<]*)<\/style>/isU', $html, $matches) > 0) {
  17294. foreach ($matches[1] as $key => $media) {
  17295. $type = array();
  17296. preg_match('/media[\s]*=[\s]*"([^"]*)"/', $media, $type);
  17297. // get 'all' and 'print' media, other media types are discarded
  17298. // (all, braille, embossed, handheld, print, projection, screen, speech, tty, tv)
  17299. if (empty($type) OR (isset($type[1]) AND (($type[1] == 'all') OR ($type[1] == 'print')))) {
  17300. $cssdata = $matches[2][$key];
  17301. $css = array_merge($css, $this->extractCSSproperties($cssdata));
  17302. }
  17303. }
  17304. }
  17305. // create a special tag to contain the CSS array (used for table content)
  17306. $csstagarray = '<cssarray>'.htmlentities(serialize($css)).'</cssarray>';
  17307. // remove head and style blocks
  17308. $html = preg_replace('/<head([^\>]*)>(.*?)<\/head>/siU', '', $html);
  17309. $html = preg_replace('/<style([^\>]*)>([^\<]*)<\/style>/isU', '', $html);
  17310. // define block tags
  17311. $blocktags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table','tr','td');
  17312. // define self-closing tags
  17313. $selfclosingtags = array('area','base','basefont','br','hr','input','img','link','meta');
  17314. // remove all unsupported tags (the line below lists all supported tags)
  17315. $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>');
  17316. //replace some blank characters
  17317. $html = preg_replace('/<pre/', '<xre', $html); // preserve pre tag
  17318. $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);
  17319. $html = preg_replace('@(\r\n|\r)@', "\n", $html);
  17320. $repTable = array("\t" => ' ', "\0" => ' ', "\x0B" => ' ', "\\" => "\\\\");
  17321. $html = strtr($html, $repTable);
  17322. $offset = 0;
  17323. while (($offset < strlen($html)) AND ($pos = strpos($html, '</pre>', $offset)) !== false) {
  17324. $html_a = substr($html, 0, $offset);
  17325. $html_b = substr($html, $offset, ($pos - $offset + 6));
  17326. while (preg_match("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", $html_b)) {
  17327. // preserve newlines on <pre> tag
  17328. $html_b = preg_replace("'<xre([^\>]*)>(.*?)\n(.*?)</pre>'si", "<xre\\1>\\2<br />\\3</pre>", $html_b);
  17329. }
  17330. while (preg_match("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], $html_b)) {
  17331. // preserve spaces on <pre> tag
  17332. $html_b = preg_replace("'<xre([^\>]*)>(.*?)".$this->re_space['p']."(.*?)</pre>'".$this->re_space['m'], "<xre\\1>\\2&nbsp;\\3</pre>", $html_b);
  17333. }
  17334. $html = $html_a.$html_b.substr($html, $pos + 6);
  17335. $offset = strlen($html_a.$html_b);
  17336. }
  17337. $offset = 0;
  17338. while (($offset < strlen($html)) AND ($pos = strpos($html, '</textarea>', $offset)) !== false) {
  17339. $html_a = substr($html, 0, $offset);
  17340. $html_b = substr($html, $offset, ($pos - $offset + 11));
  17341. while (preg_match("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", $html_b)) {
  17342. // preserve newlines on <textarea> tag
  17343. $html_b = preg_replace("'<textarea([^\>]*)>(.*?)\n(.*?)</textarea>'si", "<textarea\\1>\\2<TBR>\\3</textarea>", $html_b);
  17344. $html_b = preg_replace("'<textarea([^\>]*)>(.*?)[\"](.*?)</textarea>'si", "<textarea\\1>\\2''\\3</textarea>", $html_b);
  17345. }
  17346. $html = $html_a.$html_b.substr($html, $pos + 11);
  17347. $offset = strlen($html_a.$html_b);
  17348. }
  17349. $html = preg_replace('/([\s]*)<option/si', '<option', $html);
  17350. $html = preg_replace('/<\/option>([\s]*)/si', '</option>', $html);
  17351. $offset = 0;
  17352. while (($offset < strlen($html)) AND ($pos = strpos($html, '</option>', $offset)) !== false) {
  17353. $html_a = substr($html, 0, $offset);
  17354. $html_b = substr($html, $offset, ($pos - $offset + 9));
  17355. while (preg_match("'<option([^\>]*)>(.*?)</option>'si", $html_b)) {
  17356. $html_b = preg_replace("'<option([\s]+)value=\"([^\"]*)\"([^\>]*)>(.*?)</option>'si", "\\2#!TaB!#\\4#!NwL!#", $html_b);
  17357. $html_b = preg_replace("'<option([^\>]*)>(.*?)</option>'si", "\\2#!NwL!#", $html_b);
  17358. }
  17359. $html = $html_a.$html_b.substr($html, $pos + 9);
  17360. $offset = strlen($html_a.$html_b);
  17361. }
  17362. if (preg_match("'</select'si", $html)) {
  17363. $html = preg_replace("'<select([^\>]*)>'si", "<select\\1 opt=\"", $html);
  17364. $html = preg_replace("'#!NwL!#</select>'si", "\" />", $html);
  17365. }
  17366. $html = str_replace("\n", ' ', $html);
  17367. // restore textarea newlines
  17368. $html = str_replace('<TBR>', "\n", $html);
  17369. // remove extra spaces from code
  17370. $html = preg_replace('/[\s]+<\/(table|tr|ul|ol|dl)>/', '</\\1>', $html);
  17371. $html = preg_replace('/'.$this->re_space['p'].'+<\/(td|th|li|dt|dd)>/'.$this->re_space['m'], '</\\1>', $html);
  17372. $html = preg_replace('/[\s]+<(tr|td|th|li|dt|dd)/', '<\\1', $html);
  17373. $html = preg_replace('/'.$this->re_space['p'].'+<(ul|ol|dl|br)/'.$this->re_space['m'], '<\\1', $html);
  17374. $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);
  17375. $html = preg_replace('/<\/(td|th)>/', '<marker style="font-size:0"/></\\1>', $html);
  17376. $html = preg_replace('/<\/table>([\s]*)<marker style="font-size:0"\/>/', '</table>', $html);
  17377. $html = preg_replace('/'.$this->re_space['p'].'+<img/'.$this->re_space['m'], chr(32).'<img', $html);
  17378. $html = preg_replace('/<img([^\>]*)>/xi', '<img\\1><span><marker style="font-size:0"/></span>', $html);
  17379. $html = preg_replace('/<xre/', '<pre', $html); // restore pre tag
  17380. $html = preg_replace('/<textarea([^\>]*)>([^\<]*)<\/textarea>/xi', '<textarea\\1 value="\\2" />', $html);
  17381. $html = preg_replace('/<li([^\>]*)><\/li>/', '<li\\1>&nbsp;</li>', $html);
  17382. $html = preg_replace('/<li([^\>]*)>'.$this->re_space['p'].'*<img/'.$this->re_space['m'], '<li\\1><font size="1">&nbsp;</font><img', $html);
  17383. $html = preg_replace('/<([^\>\/]*)>[\s]/', '<\\1>&nbsp;', $html); // preserve some spaces
  17384. $html = preg_replace('/[\s]<\/([^\>]*)>/', '&nbsp;</\\1>', $html); // preserve some spaces
  17385. $html = preg_replace('/'.$this->re_space['p'].'+/'.$this->re_space['m'], chr(32), $html); // replace multiple spaces with a single space
  17386. // trim string
  17387. $html = $this->stringTrim($html);
  17388. // pattern for generic tag
  17389. $tagpattern = '/(<[^>]+>)/';
  17390. // explodes the string
  17391. $a = preg_split($tagpattern, $html, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
  17392. // count elements
  17393. $maxel = count($a);
  17394. $elkey = 0;
  17395. $key = 0;
  17396. // create an array of elements
  17397. $dom = array();
  17398. $dom[$key] = array();
  17399. // set inheritable properties fot the first void element
  17400. // 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
  17401. $dom[$key]['tag'] = false;
  17402. $dom[$key]['block'] = false;
  17403. $dom[$key]['value'] = '';
  17404. $dom[$key]['parent'] = 0;
  17405. $dom[$key]['fontname'] = $this->FontFamily;
  17406. $dom[$key]['fontstyle'] = $this->FontStyle;
  17407. $dom[$key]['fontsize'] = $this->FontSizePt;
  17408. $dom[$key]['font-stretch'] = 100;
  17409. $dom[$key]['letter-spacing'] = 0;
  17410. $dom[$key]['stroke'] = $this->textstrokewidth;
  17411. $dom[$key]['fill'] = (($this->textrendermode % 2) == 0);
  17412. $dom[$key]['clip'] = ($this->textrendermode > 3);
  17413. $dom[$key]['line-height'] = $this->cell_height_ratio;
  17414. $dom[$key]['bgcolor'] = false;
  17415. $dom[$key]['fgcolor'] = $this->fgcolor; // color
  17416. $dom[$key]['strokecolor'] = $this->strokecolor;
  17417. $dom[$key]['align'] = '';
  17418. $dom[$key]['listtype'] = '';
  17419. $dom[$key]['text-indent'] = 0;
  17420. $dom[$key]['border'] = array();
  17421. $dom[$key]['dir'] = $this->rtl?'rtl':'ltr';
  17422. $thead = false; // true when we are inside the THEAD tag
  17423. ++$key;
  17424. $level = array();
  17425. array_push($level, 0); // root
  17426. while ($elkey < $maxel) {
  17427. $dom[$key] = array();
  17428. $element = $a[$elkey];
  17429. $dom[$key]['elkey'] = $elkey;
  17430. if (preg_match($tagpattern, $element)) {
  17431. // html tag
  17432. $element = substr($element, 1, -1);
  17433. // get tag name
  17434. preg_match('/[\/]?([a-zA-Z0-9]*)/', $element, $tag);
  17435. $tagname = strtolower($tag[1]);
  17436. // check if we are inside a table header
  17437. if ($tagname == 'thead') {
  17438. if ($element{0} == '/') {
  17439. $thead = false;
  17440. } else {
  17441. $thead = true;
  17442. }
  17443. ++$elkey;
  17444. continue;
  17445. }
  17446. $dom[$key]['tag'] = true;
  17447. $dom[$key]['value'] = $tagname;
  17448. if (in_array($dom[$key]['value'], $blocktags)) {
  17449. $dom[$key]['block'] = true;
  17450. } else {
  17451. $dom[$key]['block'] = false;
  17452. }
  17453. if ($element{0} == '/') {
  17454. // *** closing html tag
  17455. $dom[$key]['opening'] = false;
  17456. $dom[$key]['parent'] = end($level);
  17457. array_pop($level);
  17458. $dom[$key]['fontname'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontname'];
  17459. $dom[$key]['fontstyle'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontstyle'];
  17460. $dom[$key]['fontsize'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fontsize'];
  17461. $dom[$key]['font-stretch'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['font-stretch'];
  17462. $dom[$key]['letter-spacing'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['letter-spacing'];
  17463. $dom[$key]['stroke'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['stroke'];
  17464. $dom[$key]['fill'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fill'];
  17465. $dom[$key]['clip'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['clip'];
  17466. $dom[$key]['line-height'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['line-height'];
  17467. $dom[$key]['bgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['bgcolor'];
  17468. $dom[$key]['fgcolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['fgcolor'];
  17469. $dom[$key]['strokecolor'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['strokecolor'];
  17470. $dom[$key]['align'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['align'];
  17471. $dom[$key]['dir'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['dir'];
  17472. if (isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'])) {
  17473. $dom[$key]['listtype'] = $dom[($dom[($dom[$key]['parent'])]['parent'])]['listtype'];
  17474. }
  17475. // set the number of columns in table tag
  17476. if (($dom[$key]['value'] == 'tr') AND (!isset($dom[($dom[($dom[$key]['parent'])]['parent'])]['cols']))) {
  17477. $dom[($dom[($dom[$key]['parent'])]['parent'])]['cols'] = $dom[($dom[$key]['parent'])]['cols'];
  17478. }
  17479. if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
  17480. $dom[($dom[$key]['parent'])]['content'] = $csstagarray;
  17481. for ($i = ($dom[$key]['parent'] + 1); $i < $key; ++$i) {
  17482. $dom[($dom[$key]['parent'])]['content'] .= $a[$dom[$i]['elkey']];
  17483. }
  17484. $key = $i;
  17485. $parent_table = $dom[$dom[$dom[($dom[$key]['parent'])]['parent']]['parent']];
  17486. $parent_padding = 0;
  17487. $parent_spacing = 0;
  17488. if (isset($parent_table['attribute']['cellpadding'])) {
  17489. $parent_padding = $this->getHTMLUnitToUnits($parent_table['attribute']['cellpadding'], 1, 'px');
  17490. }
  17491. if (isset($parent_table['attribute']['cellspacing'])) {
  17492. $parent_spacing = $this->getHTMLUnitToUnits($parent_table['attribute']['cellspacing'], 1, 'px');
  17493. }
  17494. // mark nested tables
  17495. $dom[($dom[$key]['parent'])]['content'] = str_replace('<table', '<table nested="true" pcellpadding="'.$parent_padding.'" pcellspacing="'.$parent_spacing.'"', $dom[($dom[$key]['parent'])]['content']);
  17496. // remove thead sections from nested tables
  17497. $dom[($dom[$key]['parent'])]['content'] = str_replace('<thead>', '', $dom[($dom[$key]['parent'])]['content']);
  17498. $dom[($dom[$key]['parent'])]['content'] = str_replace('</thead>', '', $dom[($dom[$key]['parent'])]['content']);
  17499. }
  17500. // store header rows on a new table
  17501. if (($dom[$key]['value'] == 'tr') AND ($dom[($dom[$key]['parent'])]['thead'] === true)) {
  17502. if ($this->empty_string($dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'])) {
  17503. $dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] = $a[$dom[($dom[($dom[$key]['parent'])]['parent'])]['elkey']];
  17504. }
  17505. for ($i = $dom[$key]['parent']; $i <= $key; ++$i) {
  17506. $dom[($dom[($dom[$key]['parent'])]['parent'])]['thead'] .= $a[$dom[$i]['elkey']];
  17507. }
  17508. if (!isset($dom[($dom[$key]['parent'])]['attribute'])) {
  17509. $dom[($dom[$key]['parent'])]['attribute'] = array();
  17510. }
  17511. // header elements must be always contained in a single page
  17512. $dom[($dom[$key]['parent'])]['attribute']['nobr'] = 'true';
  17513. }
  17514. if (($dom[$key]['value'] == 'table') AND (!$this->empty_string($dom[($dom[$key]['parent'])]['thead']))) {
  17515. // remove the nobr attributes from the table header
  17516. $dom[($dom[$key]['parent'])]['thead'] = str_replace(' nobr="true"', '', $dom[($dom[$key]['parent'])]['thead']);
  17517. $dom[($dom[$key]['parent'])]['thead'] .= '</tablehead>';
  17518. }
  17519. } else {
  17520. // *** opening or self-closing html tag
  17521. $dom[$key]['opening'] = true;
  17522. $dom[$key]['parent'] = end($level);
  17523. if ((substr($element, -1, 1) == '/') OR (in_array($dom[$key]['value'], $selfclosingtags))) {
  17524. // self-closing tag
  17525. $dom[$key]['self'] = true;
  17526. } else {
  17527. // opening tag
  17528. array_push($level, $key);
  17529. $dom[$key]['self'] = false;
  17530. }
  17531. // copy some values from parent
  17532. $parentkey = 0;
  17533. if ($key > 0) {
  17534. $parentkey = $dom[$key]['parent'];
  17535. $dom[$key]['fontname'] = $dom[$parentkey]['fontname'];
  17536. $dom[$key]['fontstyle'] = $dom[$parentkey]['fontstyle'];
  17537. $dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'];
  17538. $dom[$key]['font-stretch'] = $dom[$parentkey]['font-stretch'];
  17539. $dom[$key]['letter-spacing'] = $dom[$parentkey]['letter-spacing'];
  17540. $dom[$key]['stroke'] = $dom[$parentkey]['stroke'];
  17541. $dom[$key]['fill'] = $dom[$parentkey]['fill'];
  17542. $dom[$key]['clip'] = $dom[$parentkey]['clip'];
  17543. $dom[$key]['line-height'] = $dom[$parentkey]['line-height'];
  17544. $dom[$key]['bgcolor'] = $dom[$parentkey]['bgcolor'];
  17545. $dom[$key]['fgcolor'] = $dom[$parentkey]['fgcolor'];
  17546. $dom[$key]['strokecolor'] = $dom[$parentkey]['strokecolor'];
  17547. $dom[$key]['align'] = $dom[$parentkey]['align'];
  17548. $dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
  17549. $dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
  17550. $dom[$key]['border'] = array();
  17551. $dom[$key]['dir'] = $dom[$parentkey]['dir'];
  17552. }
  17553. // get attributes
  17554. preg_match_all('/([^=\s]*)[\s]*=[\s]*"([^"]*)"/', $element, $attr_array, PREG_PATTERN_ORDER);
  17555. $dom[$key]['attribute'] = array(); // reset attribute array
  17556. while (list($id, $name) = each($attr_array[1])) {
  17557. $dom[$key]['attribute'][strtolower($name)] = $attr_array[2][$id];
  17558. }
  17559. if (!empty($css)) {
  17560. // merge eternal CSS style to current style
  17561. $dom[$key]['attribute']['style'] = $this->getTagStyleFromCSS($dom, $key, $css);
  17562. }
  17563. // split style attributes
  17564. if (isset($dom[$key]['attribute']['style']) AND !empty($dom[$key]['attribute']['style'])) {
  17565. // get style attributes
  17566. preg_match_all('/([^;:\s]*):([^;]*)/', $dom[$key]['attribute']['style'], $style_array, PREG_PATTERN_ORDER);
  17567. $dom[$key]['style'] = array(); // reset style attribute array
  17568. while (list($id, $name) = each($style_array[1])) {
  17569. // in case of duplicate attribute the last replace the previous
  17570. $dom[$key]['style'][strtolower($name)] = trim($style_array[2][$id]);
  17571. }
  17572. // --- get some style attributes ---
  17573. // text direction
  17574. if (isset($dom[$key]['style']['direction'])) {
  17575. $dom[$key]['dir'] = $dom[$key]['style']['direction'];
  17576. }
  17577. // font family
  17578. if (isset($dom[$key]['style']['font-family'])) {
  17579. $dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['style']['font-family']);
  17580. }
  17581. // list-style-type
  17582. if (isset($dom[$key]['style']['list-style-type'])) {
  17583. $dom[$key]['listtype'] = trim(strtolower($dom[$key]['style']['list-style-type']));
  17584. if ($dom[$key]['listtype'] == 'inherit') {
  17585. $dom[$key]['listtype'] = $dom[$parentkey]['listtype'];
  17586. }
  17587. }
  17588. // text-indent
  17589. if (isset($dom[$key]['style']['text-indent'])) {
  17590. $dom[$key]['text-indent'] = $this->getHTMLUnitToUnits($dom[$key]['style']['text-indent']);
  17591. if ($dom[$key]['text-indent'] == 'inherit') {
  17592. $dom[$key]['text-indent'] = $dom[$parentkey]['text-indent'];
  17593. }
  17594. }
  17595. // font size
  17596. if (isset($dom[$key]['style']['font-size'])) {
  17597. $fsize = trim($dom[$key]['style']['font-size']);
  17598. switch ($fsize) {
  17599. // absolute-size
  17600. case 'xx-small': {
  17601. $dom[$key]['fontsize'] = $dom[0]['fontsize'] - 4;
  17602. break;
  17603. }
  17604. case 'x-small': {
  17605. $dom[$key]['fontsize'] = $dom[0]['fontsize'] - 3;
  17606. break;
  17607. }
  17608. case 'small': {
  17609. $dom[$key]['fontsize'] = $dom[0]['fontsize'] - 2;
  17610. break;
  17611. }
  17612. case 'medium': {
  17613. $dom[$key]['fontsize'] = $dom[0]['fontsize'];
  17614. break;
  17615. }
  17616. case 'large': {
  17617. $dom[$key]['fontsize'] = $dom[0]['fontsize'] + 2;
  17618. break;
  17619. }
  17620. case 'x-large': {
  17621. $dom[$key]['fontsize'] = $dom[0]['fontsize'] + 4;
  17622. break;
  17623. }
  17624. case 'xx-large': {
  17625. $dom[$key]['fontsize'] = $dom[0]['fontsize'] + 6;
  17626. break;
  17627. }
  17628. // relative-size
  17629. case 'smaller': {
  17630. $dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'] - 3;
  17631. break;
  17632. }
  17633. case 'larger': {
  17634. $dom[$key]['fontsize'] = $dom[$parentkey]['fontsize'] + 3;
  17635. break;
  17636. }
  17637. default: {
  17638. $dom[$key]['fontsize'] = $this->getHTMLUnitToUnits($fsize, $dom[$parentkey]['fontsize'], 'pt', true);
  17639. }
  17640. }
  17641. }
  17642. // font-stretch
  17643. if (isset($dom[$key]['style']['font-stretch'])) {
  17644. $dom[$key]['font-stretch'] = $this->getCSSFontStretching($dom[$key]['style']['font-stretch'], $dom[$parentkey]['font-stretch']);
  17645. }
  17646. // letter-spacing
  17647. if (isset($dom[$key]['style']['letter-spacing'])) {
  17648. $dom[$key]['letter-spacing'] = $this->getCSSFontSpacing($dom[$key]['style']['letter-spacing'], $dom[$parentkey]['letter-spacing']);
  17649. }
  17650. // line-height
  17651. if (isset($dom[$key]['style']['line-height'])) {
  17652. $lineheight = trim($dom[$key]['style']['line-height']);
  17653. switch ($lineheight) {
  17654. // A normal line height. This is default
  17655. case 'normal': {
  17656. $dom[$key]['line-height'] = $dom[0]['line-height'];
  17657. break;
  17658. }
  17659. default: {
  17660. if (is_numeric($lineheight)) {
  17661. $lineheight = $lineheight * 100;
  17662. }
  17663. $dom[$key]['line-height'] = $this->getHTMLUnitToUnits($lineheight, 1, '%', true);
  17664. }
  17665. }
  17666. }
  17667. // font style
  17668. if (isset($dom[$key]['style']['font-weight']) AND (strtolower($dom[$key]['style']['font-weight']{0}) == 'b')) {
  17669. $dom[$key]['fontstyle'] .= 'B';
  17670. }
  17671. if (isset($dom[$key]['style']['font-style']) AND (strtolower($dom[$key]['style']['font-style']{0}) == 'i')) {
  17672. $dom[$key]['fontstyle'] .= 'I';
  17673. }
  17674. // font color
  17675. if (isset($dom[$key]['style']['color']) AND (!$this->empty_string($dom[$key]['style']['color']))) {
  17676. $dom[$key]['fgcolor'] = $this->convertHTMLColorToDec($dom[$key]['style']['color']);
  17677. } elseif ($dom[$key]['value'] == 'a') {
  17678. $dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
  17679. }
  17680. // background color
  17681. if (isset($dom[$key]['style']['background-color']) AND (!$this->empty_string($dom[$key]['style']['background-color']))) {
  17682. $dom[$key]['bgcolor'] = $this->convertHTMLColorToDec($dom[$key]['style']['background-color']);
  17683. }
  17684. // text-decoration
  17685. if (isset($dom[$key]['style']['text-decoration'])) {
  17686. $decors = explode(' ', strtolower($dom[$key]['style']['text-decoration']));
  17687. foreach ($decors as $dec) {
  17688. $dec = trim($dec);
  17689. if (!$this->empty_string($dec)) {
  17690. if ($dec{0} == 'u') {
  17691. // underline
  17692. $dom[$key]['fontstyle'] .= 'U';
  17693. } elseif ($dec{0} == 'l') {
  17694. // line-trough
  17695. $dom[$key]['fontstyle'] .= 'D';
  17696. } elseif ($dec{0} == 'o') {
  17697. // overline
  17698. $dom[$key]['fontstyle'] .= 'O';
  17699. }
  17700. }
  17701. }
  17702. } elseif ($dom[$key]['value'] == 'a') {
  17703. $dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
  17704. }
  17705. // check for width attribute
  17706. if (isset($dom[$key]['style']['width'])) {
  17707. $dom[$key]['width'] = $dom[$key]['style']['width'];
  17708. }
  17709. // check for height attribute
  17710. if (isset($dom[$key]['style']['height'])) {
  17711. $dom[$key]['height'] = $dom[$key]['style']['height'];
  17712. }
  17713. // check for text alignment
  17714. if (isset($dom[$key]['style']['text-align'])) {
  17715. $dom[$key]['align'] = strtoupper($dom[$key]['style']['text-align']{0});
  17716. }
  17717. // check for CSS border properties
  17718. if (isset($dom[$key]['style']['border'])) {
  17719. $borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border']);
  17720. if (!empty($borderstyle)) {
  17721. $dom[$key]['border']['LTRB'] = $borderstyle;
  17722. }
  17723. }
  17724. if (isset($dom[$key]['style']['border-color'])) {
  17725. $brd_colors = preg_split('/[\s]+/', trim($dom[$key]['style']['border-color']));
  17726. if (isset($brd_colors[3])) {
  17727. $dom[$key]['border']['L']['color'] = $this->convertHTMLColorToDec($brd_colors[3]);
  17728. }
  17729. if (isset($brd_colors[1])) {
  17730. $dom[$key]['border']['R']['color'] = $this->convertHTMLColorToDec($brd_colors[1]);
  17731. }
  17732. if (isset($brd_colors[0])) {
  17733. $dom[$key]['border']['T']['color'] = $this->convertHTMLColorToDec($brd_colors[0]);
  17734. }
  17735. if (isset($brd_colors[2])) {
  17736. $dom[$key]['border']['B']['color'] = $this->convertHTMLColorToDec($brd_colors[2]);
  17737. }
  17738. }
  17739. if (isset($dom[$key]['style']['border-width'])) {
  17740. $brd_widths = preg_split('/[\s]+/', trim($dom[$key]['style']['border-width']));
  17741. if (isset($brd_widths[3])) {
  17742. $dom[$key]['border']['L']['width'] = $this->getCSSBorderWidth($brd_widths[3]);
  17743. }
  17744. if (isset($brd_widths[1])) {
  17745. $dom[$key]['border']['R']['width'] = $this->getCSSBorderWidth($brd_widths[1]);
  17746. }
  17747. if (isset($brd_widths[0])) {
  17748. $dom[$key]['border']['T']['width'] = $this->getCSSBorderWidth($brd_widths[0]);
  17749. }
  17750. if (isset($brd_widths[2])) {
  17751. $dom[$key]['border']['B']['width'] = $this->getCSSBorderWidth($brd_widths[2]);
  17752. }
  17753. }
  17754. if (isset($dom[$key]['style']['border-style'])) {
  17755. $brd_styles = preg_split('/[\s]+/', trim($dom[$key]['style']['border-style']));
  17756. if (isset($brd_styles[3])) {
  17757. $dom[$key]['border']['L']['cap'] = 'square';
  17758. $dom[$key]['border']['L']['join'] = 'miter';
  17759. $dom[$key]['border']['L']['dash'] = $this->getCSSBorderDashStyle($brd_styles[3]);
  17760. if ($dom[$key]['border']['L']['dash'] < 0) {
  17761. $dom[$key]['border']['L'] = array();
  17762. }
  17763. }
  17764. if (isset($brd_styles[1])) {
  17765. $dom[$key]['border']['R']['cap'] = 'square';
  17766. $dom[$key]['border']['R']['join'] = 'miter';
  17767. $dom[$key]['border']['R']['dash'] = $this->getCSSBorderDashStyle($brd_styles[1]);
  17768. if ($dom[$key]['border']['R']['dash'] < 0) {
  17769. $dom[$key]['border']['R'] = array();
  17770. }
  17771. }
  17772. if (isset($brd_styles[0])) {
  17773. $dom[$key]['border']['T']['cap'] = 'square';
  17774. $dom[$key]['border']['T']['join'] = 'miter';
  17775. $dom[$key]['border']['T']['dash'] = $this->getCSSBorderDashStyle($brd_styles[0]);
  17776. if ($dom[$key]['border']['T']['dash'] < 0) {
  17777. $dom[$key]['border']['T'] = array();
  17778. }
  17779. }
  17780. if (isset($brd_styles[2])) {
  17781. $dom[$key]['border']['B']['cap'] = 'square';
  17782. $dom[$key]['border']['B']['join'] = 'miter';
  17783. $dom[$key]['border']['B']['dash'] = $this->getCSSBorderDashStyle($brd_styles[2]);
  17784. if ($dom[$key]['border']['B']['dash'] < 0) {
  17785. $dom[$key]['border']['B'] = array();
  17786. }
  17787. }
  17788. }
  17789. $cellside = array('L' => 'left', 'R' => 'right', 'T' => 'top', 'B' => 'bottom');
  17790. foreach ($cellside as $bsk => $bsv) {
  17791. if (isset($dom[$key]['style']['border-'.$bsv])) {
  17792. $borderstyle = $this->getCSSBorderStyle($dom[$key]['style']['border-'.$bsv]);
  17793. if (!empty($borderstyle)) {
  17794. $dom[$key]['border'][$bsk] = $borderstyle;
  17795. }
  17796. }
  17797. if (isset($dom[$key]['style']['border-'.$bsv.'-color'])) {
  17798. $dom[$key]['border'][$bsk]['color'] = $this->convertHTMLColorToDec($dom[$key]['style']['border-'.$bsv.'-color']);
  17799. }
  17800. if (isset($dom[$key]['style']['border-'.$bsv.'-width'])) {
  17801. $dom[$key]['border'][$bsk]['width'] = $this->getCSSBorderWidth($dom[$key]['style']['border-'.$bsv.'-width']);
  17802. }
  17803. if (isset($dom[$key]['style']['border-'.$bsv.'-style'])) {
  17804. $dom[$key]['border'][$bsk]['dash'] = $this->getCSSBorderDashStyle($dom[$key]['style']['border-'.$bsv.'-style']);
  17805. if ($dom[$key]['border'][$bsk]['dash'] < 0) {
  17806. $dom[$key]['border'][$bsk] = array();
  17807. }
  17808. }
  17809. }
  17810. // check for CSS padding properties
  17811. if (isset($dom[$key]['style']['padding'])) {
  17812. $dom[$key]['padding'] = $this->getCSSPadding($dom[$key]['style']['padding']);
  17813. } else {
  17814. $dom[$key]['padding'] = $this->cell_padding;
  17815. }
  17816. foreach ($cellside as $psk => $psv) {
  17817. if (isset($dom[$key]['style']['padding-'.$psv])) {
  17818. $dom[$key]['padding'][$psk] = $this->getHTMLUnitToUnits($dom[$key]['style']['padding-'.$psv], 0, 'px', false);
  17819. }
  17820. }
  17821. // check for CSS margin properties
  17822. if (isset($dom[$key]['style']['margin'])) {
  17823. $dom[$key]['margin'] = $this->getCSSMargin($dom[$key]['style']['margin']);
  17824. } else {
  17825. $dom[$key]['margin'] = $this->cell_margin;
  17826. }
  17827. foreach ($cellside as $psk => $psv) {
  17828. if (isset($dom[$key]['style']['margin-'.$psv])) {
  17829. $dom[$key]['margin'][$psk] = $this->getHTMLUnitToUnits(str_replace('auto', '0', $dom[$key]['style']['margin-'.$psv]), 0, 'px', false);
  17830. }
  17831. }
  17832. // page-break-inside
  17833. if (isset($dom[$key]['style']['page-break-inside']) AND ($dom[$key]['style']['page-break-inside'] == 'avoid')) {
  17834. $dom[$key]['attribute']['nobr'] = 'true';
  17835. }
  17836. // page-break-before
  17837. if (isset($dom[$key]['style']['page-break-before'])) {
  17838. if ($dom[$key]['style']['page-break-before'] == 'always') {
  17839. $dom[$key]['attribute']['pagebreak'] = 'true';
  17840. } elseif ($dom[$key]['style']['page-break-before'] == 'left') {
  17841. $dom[$key]['attribute']['pagebreak'] = 'left';
  17842. } elseif ($dom[$key]['style']['page-break-before'] == 'right') {
  17843. $dom[$key]['attribute']['pagebreak'] = 'right';
  17844. }
  17845. }
  17846. // page-break-after
  17847. if (isset($dom[$key]['style']['page-break-after'])) {
  17848. if ($dom[$key]['style']['page-break-after'] == 'always') {
  17849. $dom[$key]['attribute']['pagebreakafter'] = 'true';
  17850. } elseif ($dom[$key]['style']['page-break-after'] == 'left') {
  17851. $dom[$key]['attribute']['pagebreakafter'] = 'left';
  17852. } elseif ($dom[$key]['style']['page-break-after'] == 'right') {
  17853. $dom[$key]['attribute']['pagebreakafter'] = 'right';
  17854. }
  17855. }
  17856. }
  17857. if (isset($dom[$key]['attribute']['border']) AND ($dom[$key]['attribute']['border'] != 0)) {
  17858. $borderstyle = $this->getCSSBorderStyle($dom[$key]['attribute']['border'].' solid black');
  17859. if (!empty($borderstyle)) {
  17860. $dom[$key]['border']['LTRB'] = $borderstyle;
  17861. }
  17862. }
  17863. // check for font tag
  17864. if ($dom[$key]['value'] == 'font') {
  17865. // font family
  17866. if (isset($dom[$key]['attribute']['face'])) {
  17867. $dom[$key]['fontname'] = $this->getFontFamilyName($dom[$key]['attribute']['face']);
  17868. }
  17869. // font size
  17870. if (isset($dom[$key]['attribute']['size'])) {
  17871. if ($key > 0) {
  17872. if ($dom[$key]['attribute']['size']{0} == '+') {
  17873. $dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] + intval(substr($dom[$key]['attribute']['size'], 1));
  17874. } elseif ($dom[$key]['attribute']['size']{0} == '-') {
  17875. $dom[$key]['fontsize'] = $dom[($dom[$key]['parent'])]['fontsize'] - intval(substr($dom[$key]['attribute']['size'], 1));
  17876. } else {
  17877. $dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
  17878. }
  17879. } else {
  17880. $dom[$key]['fontsize'] = intval($dom[$key]['attribute']['size']);
  17881. }
  17882. }
  17883. }
  17884. // force natural alignment for lists
  17885. if ((($dom[$key]['value'] == 'ul') OR ($dom[$key]['value'] == 'ol') OR ($dom[$key]['value'] == 'dl'))
  17886. AND (!isset($dom[$key]['align']) OR $this->empty_string($dom[$key]['align']) OR ($dom[$key]['align'] != 'J'))) {
  17887. if ($this->rtl) {
  17888. $dom[$key]['align'] = 'R';
  17889. } else {
  17890. $dom[$key]['align'] = 'L';
  17891. }
  17892. }
  17893. if (($dom[$key]['value'] == 'small') OR ($dom[$key]['value'] == 'sup') OR ($dom[$key]['value'] == 'sub')) {
  17894. if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
  17895. $dom[$key]['fontsize'] = $dom[$key]['fontsize'] * K_SMALL_RATIO;
  17896. }
  17897. }
  17898. if (($dom[$key]['value'] == 'strong') OR ($dom[$key]['value'] == 'b')) {
  17899. $dom[$key]['fontstyle'] .= 'B';
  17900. }
  17901. if (($dom[$key]['value'] == 'em') OR ($dom[$key]['value'] == 'i')) {
  17902. $dom[$key]['fontstyle'] .= 'I';
  17903. }
  17904. if ($dom[$key]['value'] == 'u') {
  17905. $dom[$key]['fontstyle'] .= 'U';
  17906. }
  17907. if (($dom[$key]['value'] == 'del') OR ($dom[$key]['value'] == 's') OR ($dom[$key]['value'] == 'strike')) {
  17908. $dom[$key]['fontstyle'] .= 'D';
  17909. }
  17910. if (!isset($dom[$key]['style']['text-decoration']) AND ($dom[$key]['value'] == 'a')) {
  17911. $dom[$key]['fontstyle'] = $this->htmlLinkFontStyle;
  17912. }
  17913. if (($dom[$key]['value'] == 'pre') OR ($dom[$key]['value'] == 'tt')) {
  17914. $dom[$key]['fontname'] = $this->default_monospaced_font;
  17915. }
  17916. if (($dom[$key]['value']{0} == 'h') AND (intval($dom[$key]['value']{1}) > 0) AND (intval($dom[$key]['value']{1}) < 7)) {
  17917. // headings h1, h2, h3, h4, h5, h6
  17918. if (!isset($dom[$key]['attribute']['size']) AND !isset($dom[$key]['style']['font-size'])) {
  17919. $headsize = (4 - intval($dom[$key]['value']{1})) * 2;
  17920. $dom[$key]['fontsize'] = $dom[0]['fontsize'] + $headsize;
  17921. }
  17922. if (!isset($dom[$key]['style']['font-weight'])) {
  17923. $dom[$key]['fontstyle'] .= 'B';
  17924. }
  17925. }
  17926. if (($dom[$key]['value'] == 'table')) {
  17927. $dom[$key]['rows'] = 0; // number of rows
  17928. $dom[$key]['trids'] = array(); // IDs of TR elements
  17929. $dom[$key]['thead'] = ''; // table header rows
  17930. }
  17931. if (($dom[$key]['value'] == 'tr')) {
  17932. $dom[$key]['cols'] = 0;
  17933. if ($thead) {
  17934. $dom[$key]['thead'] = true;
  17935. // rows on thead block are printed as a separate table
  17936. } else {
  17937. $dom[$key]['thead'] = false;
  17938. // store the number of rows on table element
  17939. ++$dom[($dom[$key]['parent'])]['rows'];
  17940. // store the TR elements IDs on table element
  17941. array_push($dom[($dom[$key]['parent'])]['trids'], $key);
  17942. }
  17943. }
  17944. if (($dom[$key]['value'] == 'th') OR ($dom[$key]['value'] == 'td')) {
  17945. if (isset($dom[$key]['attribute']['colspan'])) {
  17946. $colspan = intval($dom[$key]['attribute']['colspan']);
  17947. } else {
  17948. $colspan = 1;
  17949. }
  17950. $dom[$key]['attribute']['colspan'] = $colspan;
  17951. $dom[($dom[$key]['parent'])]['cols'] += $colspan;
  17952. }
  17953. // text direction
  17954. if (isset($dom[$key]['attribute']['dir'])) {
  17955. $dom[$key]['dir'] = $dom[$key]['attribute']['dir'];
  17956. }
  17957. // set foreground color attribute
  17958. if (isset($dom[$key]['attribute']['color']) AND (!$this->empty_string($dom[$key]['attribute']['color']))) {
  17959. $dom[$key]['fgcolor'] = $this->convertHTMLColorToDec($dom[$key]['attribute']['color']);
  17960. } elseif (!isset($dom[$key]['style']['color']) AND ($dom[$key]['value'] == 'a')) {
  17961. $dom[$key]['fgcolor'] = $this->htmlLinkColorArray;
  17962. }
  17963. // set background color attribute
  17964. if (isset($dom[$key]['attribute']['bgcolor']) AND (!$this->empty_string($dom[$key]['attribute']['bgcolor']))) {
  17965. $dom[$key]['bgcolor'] = $this->convertHTMLColorToDec($dom[$key]['attribute']['bgcolor']);
  17966. }
  17967. // set stroke color attribute
  17968. if (isset($dom[$key]['attribute']['strokecolor']) AND (!$this->empty_string($dom[$key]['attribute']['strokecolor']))) {
  17969. $dom[$key]['strokecolor'] = $this->convertHTMLColorToDec($dom[$key]['attribute']['strokecolor']);
  17970. }
  17971. // check for width attribute
  17972. if (isset($dom[$key]['attribute']['width'])) {
  17973. $dom[$key]['width'] = $dom[$key]['attribute']['width'];
  17974. }
  17975. // check for height attribute
  17976. if (isset($dom[$key]['attribute']['height'])) {
  17977. $dom[$key]['height'] = $dom[$key]['attribute']['height'];
  17978. }
  17979. // check for text alignment
  17980. if (isset($dom[$key]['attribute']['align']) AND (!$this->empty_string($dom[$key]['attribute']['align'])) AND ($dom[$key]['value'] !== 'img')) {
  17981. $dom[$key]['align'] = strtoupper($dom[$key]['attribute']['align']{0});
  17982. }
  17983. // check for text rendering mode (the following attributes do not exist in HTML)
  17984. if (isset($dom[$key]['attribute']['stroke'])) {
  17985. // font stroke width
  17986. $dom[$key]['stroke'] = $this->getHTMLUnitToUnits($dom[$key]['attribute']['stroke'], $dom[$key]['fontsize'], 'pt', true);
  17987. }
  17988. if (isset($dom[$key]['attribute']['fill'])) {
  17989. // font fill
  17990. if ($dom[$key]['attribute']['fill'] == 'true') {
  17991. $dom[$key]['fill'] = true;
  17992. } else {
  17993. $dom[$key]['fill'] = false;
  17994. }
  17995. }
  17996. if (isset($dom[$key]['attribute']['clip'])) {
  17997. // clipping mode
  17998. if ($dom[$key]['attribute']['clip'] == 'true') {
  17999. $dom[$key]['clip'] = true;
  18000. } else {
  18001. $dom[$key]['clip'] = false;
  18002. }
  18003. }
  18004. } // end opening tag
  18005. } else {
  18006. // text
  18007. $dom[$key]['tag'] = false;
  18008. $dom[$key]['block'] = false;
  18009. $element = str_replace('$nbsp;', $this->unichr(160), $element);
  18010. $dom[$key]['value'] = stripslashes($this->unhtmlentities($element));
  18011. $dom[$key]['parent'] = end($level);
  18012. }
  18013. ++$elkey;
  18014. ++$key;
  18015. }
  18016. return $dom;
  18017. }
  18018. /**
  18019. * Returns the string used to find spaces
  18020. * @return string
  18021. * @access protected
  18022. * @author Nicola Asuni
  18023. * @since 4.8.024 (2010-01-15)
  18024. */
  18025. protected function getSpaceString() {
  18026. $spacestr = chr(32);
  18027. if ($this->isUnicodeFont()) {
  18028. $spacestr = chr(0).chr(32);
  18029. }
  18030. return $spacestr;
  18031. }
  18032. /**
  18033. * Prints a cell (rectangular area) with optional borders, background color and html text string.
  18034. * The upper-left corner of the cell corresponds to the current position. After the call, the current position moves to the right or to the next line.<br />
  18035. * If automatic page breaking is enabled and the cell goes beyond the limit, a page break is done before outputting.
  18036. * @param float $w Cell width. If 0, the cell extends up to the right margin.
  18037. * @param float $h Cell minimum height. The cell extends automatically if needed.
  18038. * @param float $x upper-left corner X coordinate
  18039. * @param float $y upper-left corner Y coordinate
  18040. * @param string $html html text to print. Default value: empty string.
  18041. * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
  18042. * @param int $ln Indicates where the current position should go after the call. Possible values are:<ul><li>0: to the right (or left for RTL language)</li><li>1: to the beginning of the next line</li><li>2: below</li></ul>
  18043. Putting 1 is equivalent to putting 0 and calling Ln() just after. Default value: 0.
  18044. * @param boolean $fill Indicates if the cell background must be painted (true) or transparent (false).
  18045. * @param boolean $reseth if true reset the last cell height (default true).
  18046. * @param string $align Allows to center or align the text. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
  18047. * @param boolean $autopadding if true, uses internal padding and automatically adjust it to account for line width.
  18048. * @access public
  18049. * @uses MultiCell()
  18050. * @see Multicell(), writeHTML()
  18051. */
  18052. public function writeHTMLCell($w, $h, $x, $y, $html='', $border=0, $ln=0, $fill=false, $reseth=true, $align='', $autopadding=true) {
  18053. return $this->MultiCell($w, $h, $html, $border, $align, $fill, $ln, $x, $y, $reseth, 0, true, $autopadding, 0);
  18054. }
  18055. /**
  18056. * Allows to preserve some HTML formatting (limited support).<br />
  18057. * IMPORTANT: The HTML must be well formatted - try to clean-up it using an application like HTML-Tidy before submitting.
  18058. * Supported tags are: a, b, blockquote, br, dd, del, div, dl, dt, em, font, h1, h2, h3, h4, h5, h6, hr, i, img, li, ol, p, pre, small, span, strong, sub, sup, table, tcpdf, td, th, thead, tr, tt, u, ul
  18059. * @param string $html text to display
  18060. * @param boolean $ln if true add a new line after text (default = true)
  18061. * @param boolean $fill Indicates if the background must be painted (true) or transparent (false).
  18062. * @param boolean $reseth if true reset the last cell height (default false).
  18063. * @param boolean $cell if true add the current left (or right for RTL) padding to each Write (default false).
  18064. * @param string $align Allows to center or align the text. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
  18065. * @access public
  18066. */
  18067. public function writeHTML($html, $ln=true, $fill=false, $reseth=false, $cell=false, $align='') {
  18068. $gvars = $this->getGraphicVars();
  18069. // store current values
  18070. $prev_cell_margin = $this->cell_margin;
  18071. $prev_cell_padding = $this->cell_padding;
  18072. $prevPage = $this->page;
  18073. $prevlMargin = $this->lMargin;
  18074. $prevrMargin = $this->rMargin;
  18075. $curfontname = $this->FontFamily;
  18076. $curfontstyle = $this->FontStyle;
  18077. $curfontsize = $this->FontSizePt;
  18078. $curfontascent = $this->getFontAscent($curfontname, $curfontstyle, $curfontsize);
  18079. $curfontdescent = $this->getFontDescent($curfontname, $curfontstyle, $curfontsize);
  18080. $curfontstretcing = $this->font_stretching;
  18081. $curfontkerning = $this->font_spacing;
  18082. $this->newline = true;
  18083. $newline = true;
  18084. $startlinepage = $this->page;
  18085. $minstartliney = $this->y;
  18086. $maxbottomliney = 0;
  18087. $startlinex = $this->x;
  18088. $startliney = $this->y;
  18089. $yshift = 0;
  18090. $loop = 0;
  18091. $curpos = 0;
  18092. $this_method_vars = array();
  18093. $undo = false;
  18094. $fontaligned = false;
  18095. $reverse_dir = false; // true when the text direction is reversed
  18096. $this->premode = false;
  18097. if ($this->inxobj) {
  18098. // we are inside an XObject template
  18099. $pask = count($this->xobjects[$this->xobjid]['annotations']);
  18100. } elseif (isset($this->PageAnnots[$this->page])) {
  18101. $pask = count($this->PageAnnots[$this->page]);
  18102. } else {
  18103. $pask = 0;
  18104. }
  18105. if ($this->inxobj) {
  18106. // we are inside an XObject template
  18107. $startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
  18108. } elseif (!$this->InFooter) {
  18109. if (isset($this->footerlen[$this->page])) {
  18110. $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
  18111. } else {
  18112. $this->footerpos[$this->page] = $this->pagelen[$this->page];
  18113. }
  18114. $startlinepos = $this->footerpos[$this->page];
  18115. } else {
  18116. // we are inside the footer
  18117. $startlinepos = $this->pagelen[$this->page];
  18118. }
  18119. $lalign = $align;
  18120. $plalign = $align;
  18121. if ($this->rtl) {
  18122. $w = $this->x - $this->lMargin;
  18123. } else {
  18124. $w = $this->w - $this->rMargin - $this->x;
  18125. }
  18126. $w -= ($this->cell_padding['L'] + $this->cell_padding['R']);
  18127. if ($cell) {
  18128. if ($this->rtl) {
  18129. $this->x -= $this->cell_padding['R'];
  18130. $this->lMargin += $this->cell_padding['R'];
  18131. } else {
  18132. $this->x += $this->cell_padding['L'];
  18133. $this->rMargin += $this->cell_padding['L'];
  18134. }
  18135. }
  18136. if ($this->customlistindent >= 0) {
  18137. $this->listindent = $this->customlistindent;
  18138. } else {
  18139. $this->listindent = $this->GetStringWidth('0000');
  18140. }
  18141. $this->listindentlevel = 0;
  18142. // save previous states
  18143. $prev_cell_height_ratio = $this->cell_height_ratio;
  18144. $prev_listnum = $this->listnum;
  18145. $prev_listordered = $this->listordered;
  18146. $prev_listcount = $this->listcount;
  18147. $prev_lispacer = $this->lispacer;
  18148. $this->listnum = 0;
  18149. $this->listordered = array();
  18150. $this->listcount = array();
  18151. $this->lispacer = '';
  18152. if (($this->empty_string($this->lasth)) OR ($reseth)) {
  18153. // reset row height
  18154. $this->resetLastH();
  18155. }
  18156. $dom = $this->getHtmlDomArray($html);
  18157. $maxel = count($dom);
  18158. $key = 0;
  18159. while ($key < $maxel) {
  18160. if ($dom[$key]['tag'] AND isset($dom[$key]['attribute']['pagebreak'])) {
  18161. // check for pagebreak
  18162. if (($dom[$key]['attribute']['pagebreak'] == 'true') OR ($dom[$key]['attribute']['pagebreak'] == 'left') OR ($dom[$key]['attribute']['pagebreak'] == 'right')) {
  18163. // add a page (or trig AcceptPageBreak() for multicolumn mode)
  18164. $this->checkPageBreak($this->PageBreakTrigger + 1);
  18165. }
  18166. if ((($dom[$key]['attribute']['pagebreak'] == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
  18167. OR (($dom[$key]['attribute']['pagebreak'] == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
  18168. // add a page (or trig AcceptPageBreak() for multicolumn mode)
  18169. $this->checkPageBreak($this->PageBreakTrigger + 1);
  18170. }
  18171. }
  18172. if ($dom[$key]['tag'] AND $dom[$key]['opening'] AND isset($dom[$key]['attribute']['nobr']) AND ($dom[$key]['attribute']['nobr'] == 'true')) {
  18173. if (isset($dom[($dom[$key]['parent'])]['attribute']['nobr']) AND ($dom[($dom[$key]['parent'])]['attribute']['nobr'] == 'true')) {
  18174. $dom[$key]['attribute']['nobr'] = false;
  18175. } else {
  18176. // store current object
  18177. $this->startTransaction();
  18178. // save this method vars
  18179. $this_method_vars['html'] = $html;
  18180. $this_method_vars['ln'] = $ln;
  18181. $this_method_vars['fill'] = $fill;
  18182. $this_method_vars['reseth'] = $reseth;
  18183. $this_method_vars['cell'] = $cell;
  18184. $this_method_vars['align'] = $align;
  18185. $this_method_vars['gvars'] = $gvars;
  18186. $this_method_vars['prevPage'] = $prevPage;
  18187. $this_method_vars['prev_cell_margin'] = $prev_cell_margin;
  18188. $this_method_vars['prev_cell_padding'] = $prev_cell_padding;
  18189. $this_method_vars['prevlMargin'] = $prevlMargin;
  18190. $this_method_vars['prevrMargin'] = $prevrMargin;
  18191. $this_method_vars['curfontname'] = $curfontname;
  18192. $this_method_vars['curfontstyle'] = $curfontstyle;
  18193. $this_method_vars['curfontsize'] = $curfontsize;
  18194. $this_method_vars['curfontascent'] = $curfontascent;
  18195. $this_method_vars['curfontdescent'] = $curfontdescent;
  18196. $this_method_vars['curfontstretcing'] = $curfontstretcing;
  18197. $this_method_vars['curfontkerning'] = $curfontkerning;
  18198. $this_method_vars['minstartliney'] = $minstartliney;
  18199. $this_method_vars['maxbottomliney'] = $maxbottomliney;
  18200. $this_method_vars['yshift'] = $yshift;
  18201. $this_method_vars['startlinepage'] = $startlinepage;
  18202. $this_method_vars['startlinepos'] = $startlinepos;
  18203. $this_method_vars['startlinex'] = $startlinex;
  18204. $this_method_vars['startliney'] = $startliney;
  18205. $this_method_vars['newline'] = $newline;
  18206. $this_method_vars['loop'] = $loop;
  18207. $this_method_vars['curpos'] = $curpos;
  18208. $this_method_vars['pask'] = $pask;
  18209. $this_method_vars['lalign'] = $lalign;
  18210. $this_method_vars['plalign'] = $plalign;
  18211. $this_method_vars['w'] = $w;
  18212. $this_method_vars['prev_cell_height_ratio'] = $prev_cell_height_ratio;
  18213. $this_method_vars['prev_listnum'] = $prev_listnum;
  18214. $this_method_vars['prev_listordered'] = $prev_listordered;
  18215. $this_method_vars['prev_listcount'] = $prev_listcount;
  18216. $this_method_vars['prev_lispacer'] = $prev_lispacer;
  18217. $this_method_vars['fontaligned'] = $fontaligned;
  18218. $this_method_vars['key'] = $key;
  18219. $this_method_vars['dom'] = $dom;
  18220. }
  18221. }
  18222. // print THEAD block
  18223. if (($dom[$key]['value'] == 'tr') AND isset($dom[$key]['thead']) AND $dom[$key]['thead']) {
  18224. if (isset($dom[$key]['parent']) AND isset($dom[$dom[$key]['parent']]['thead']) AND !$this->empty_string($dom[$dom[$key]['parent']]['thead'])) {
  18225. $this->inthead = true;
  18226. // print table header (thead)
  18227. $this->writeHTML($this->thead, false, false, false, false, '');
  18228. // check if we are on a new page or on a new column
  18229. if (($this->y < $this->start_transaction_y) OR ($this->checkPageBreak($this->lasth, '', false))) {
  18230. // we are on a new page or on a new column and the total object height is less than the available vertical space.
  18231. // restore previous object
  18232. $this->rollbackTransaction(true);
  18233. // restore previous values
  18234. foreach ($this_method_vars as $vkey => $vval) {
  18235. $$vkey = $vval;
  18236. }
  18237. // disable table header
  18238. $tmp_thead = $this->thead;
  18239. $this->thead = '';
  18240. // add a page (or trig AcceptPageBreak() for multicolumn mode)
  18241. $pre_y = $this->y;
  18242. if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
  18243. // fix for multicolumn mode
  18244. $startliney = $this->y;
  18245. }
  18246. $this->start_transaction_page = $this->page;
  18247. $this->start_transaction_y = $this->y;
  18248. // restore table header
  18249. $this->thead = $tmp_thead;
  18250. // fix table border properties
  18251. if (isset($dom[$dom[$key]['parent']]['attribute']['cellspacing'])) {
  18252. $tmp_cellspacing = $this->getHTMLUnitToUnits($dom[$dom[$key]['parent']]['attribute']['cellspacing'], 1, 'px');
  18253. } else {
  18254. $tmp_cellspacing = 0;
  18255. }
  18256. $dom[$dom[$key]['parent']]['borderposition']['page'] = $this->page;
  18257. $dom[$dom[$key]['parent']]['borderposition']['column'] = $this->current_column;
  18258. $dom[$dom[$key]['parent']]['borderposition']['y'] = $this->y + $tmp_cellspacing;
  18259. $xoffset = ($this->x - $dom[$dom[$key]['parent']]['borderposition']['x']);
  18260. $dom[$dom[$key]['parent']]['borderposition']['x'] += $xoffset;
  18261. $dom[$dom[$key]['parent']]['borderposition']['xmax'] += $xoffset;
  18262. // print table header (thead)
  18263. $this->writeHTML($this->thead, false, false, false, false, '');
  18264. }
  18265. }
  18266. // move $key index forward to skip THEAD block
  18267. while ( ($key < $maxel) AND (!(
  18268. ($dom[$key]['tag'] AND $dom[$key]['opening'] AND ($dom[$key]['value'] == 'tr') AND (!isset($dom[$key]['thead']) OR !$dom[$key]['thead']))
  18269. OR ($dom[$key]['tag'] AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == 'table'))) )) {
  18270. ++$key;
  18271. }
  18272. }
  18273. if ($dom[$key]['tag'] OR ($key == 0)) {
  18274. if ((($dom[$key]['value'] == 'table') OR ($dom[$key]['value'] == 'tr')) AND (isset($dom[$key]['align']))) {
  18275. $dom[$key]['align'] = ($this->rtl) ? 'R' : 'L';
  18276. }
  18277. // vertically align image in line
  18278. if ((!$this->newline) AND ($dom[$key]['value'] == 'img') AND (isset($dom[$key]['height'])) AND ($dom[$key]['height'] > 0)) {
  18279. // get image height
  18280. $imgh = $this->getHTMLUnitToUnits($dom[$key]['height'], $this->lasth, 'px');
  18281. // check for automatic line break
  18282. $autolinebreak = false;
  18283. if (isset($dom[$key]['width']) AND ($dom[$key]['width'] > 0)) {
  18284. $imgw = $this->getHTMLUnitToUnits($dom[$key]['width'], 1, 'px', false);
  18285. if (($this->rtl AND (($this->x - $imgw) < ($this->lMargin + $this->cell_padding['L'])))
  18286. OR (!$this->rtl AND (($this->x + $imgw) > ($this->w - $this->rMargin - $this->cell_padding['R'])))) {
  18287. // add automatic line break
  18288. $autolinebreak = true;
  18289. $this->Ln('', $cell);
  18290. // go back to evaluate this line break
  18291. --$key;
  18292. }
  18293. }
  18294. if (!$autolinebreak) {
  18295. if (!$this->InFooter) {
  18296. $pre_y = $this->y;
  18297. // check for page break
  18298. if ((!$this->checkPageBreak($imgh)) AND ($this->y < $pre_y)) {
  18299. // fix for multicolumn mode
  18300. $startliney = $this->y;
  18301. }
  18302. }
  18303. if ($this->page > $startlinepage) {
  18304. // fix line splitted over two pages
  18305. if (isset($this->footerlen[$startlinepage])) {
  18306. $curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
  18307. }
  18308. // line to be moved one page forward
  18309. $pagebuff = $this->getPageBuffer($startlinepage);
  18310. $linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
  18311. $tstart = substr($pagebuff, 0, $startlinepos);
  18312. $tend = substr($this->getPageBuffer($startlinepage), $curpos);
  18313. // remove line from previous page
  18314. $this->setPageBuffer($startlinepage, $tstart.''.$tend);
  18315. $pagebuff = $this->getPageBuffer($this->page);
  18316. $tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
  18317. $tend = substr($pagebuff, $this->cntmrk[$this->page]);
  18318. // add line start to current page
  18319. $yshift = $minstartliney - $this->y;
  18320. if ($fontaligned) {
  18321. $yshift += ($curfontsize / $this->k);
  18322. }
  18323. $try = sprintf('1 0 0 1 0 %.3F cm', ($yshift * $this->k));
  18324. $this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
  18325. // shift the annotations and links
  18326. if (isset($this->PageAnnots[$this->page])) {
  18327. $next_pask = count($this->PageAnnots[$this->page]);
  18328. } else {
  18329. $next_pask = 0;
  18330. }
  18331. if (isset($this->PageAnnots[$startlinepage])) {
  18332. foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
  18333. if ($pak >= $pask) {
  18334. $this->PageAnnots[$this->page][] = $pac;
  18335. unset($this->PageAnnots[$startlinepage][$pak]);
  18336. $npak = count($this->PageAnnots[$this->page]) - 1;
  18337. $this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
  18338. }
  18339. }
  18340. }
  18341. $pask = $next_pask;
  18342. $startlinepos = $this->cntmrk[$this->page];
  18343. $startlinepage = $this->page;
  18344. $startliney = $this->y;
  18345. $this->newline = false;
  18346. }
  18347. $this->y += ((($curfontsize * $this->cell_height_ratio / $this->k) + $curfontascent - $curfontdescent) / 2) - $imgh;
  18348. $minstartliney = min($this->y, $minstartliney);
  18349. $maxbottomliney = ($startliney + ($this->FontSize * $this->cell_height_ratio));
  18350. }
  18351. } elseif (isset($dom[$key]['fontname']) OR isset($dom[$key]['fontstyle']) OR isset($dom[$key]['fontsize']) OR isset($dom[$key]['line-height'])) {
  18352. // account for different font size
  18353. $pfontname = $curfontname;
  18354. $pfontstyle = $curfontstyle;
  18355. $pfontsize = $curfontsize;
  18356. $fontname = isset($dom[$key]['fontname']) ? $dom[$key]['fontname'] : $curfontname;
  18357. $fontstyle = isset($dom[$key]['fontstyle']) ? $dom[$key]['fontstyle'] : $curfontstyle;
  18358. $fontsize = isset($dom[$key]['fontsize']) ? $dom[$key]['fontsize'] : $curfontsize;
  18359. $fontascent = $this->getFontAscent($fontname, $fontstyle, $fontsize);
  18360. $fontdescent = $this->getFontDescent($fontname, $fontstyle, $fontsize);
  18361. if (($fontname != $curfontname) OR ($fontstyle != $curfontstyle) OR ($fontsize != $curfontsize) OR ($this->cell_height_ratio != $dom[$key]['line-height'])) {
  18362. if ((!$this->newline) AND ($key < ($maxel - 1))
  18363. AND ((is_numeric($fontsize) AND ($fontsize >= 0) AND is_numeric($curfontsize) AND ($curfontsize >= 0) AND ($fontsize != $curfontsize))
  18364. OR ($this->cell_height_ratio != $dom[$key]['line-height']))) {
  18365. if ($this->page > $startlinepage) {
  18366. // fix lines splitted over two pages
  18367. if (isset($this->footerlen[$startlinepage])) {
  18368. $curpos = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
  18369. }
  18370. // line to be moved one page forward
  18371. $pagebuff = $this->getPageBuffer($startlinepage);
  18372. $linebeg = substr($pagebuff, $startlinepos, ($curpos - $startlinepos));
  18373. $tstart = substr($pagebuff, 0, $startlinepos);
  18374. $tend = substr($this->getPageBuffer($startlinepage), $curpos);
  18375. // remove line start from previous page
  18376. $this->setPageBuffer($startlinepage, $tstart.''.$tend);
  18377. $pagebuff = $this->getPageBuffer($this->page);
  18378. $tstart = substr($pagebuff, 0, $this->cntmrk[$this->page]);
  18379. $tend = substr($pagebuff, $this->cntmrk[$this->page]);
  18380. // add line start to current page
  18381. $yshift = $minstartliney - $this->y;
  18382. $try = sprintf('1 0 0 1 0 %.3F cm', ($yshift * $this->k));
  18383. $this->setPageBuffer($this->page, $tstart."\nq\n".$try."\n".$linebeg."\nQ\n".$tend);
  18384. // shift the annotations and links
  18385. if (isset($this->PageAnnots[$this->page])) {
  18386. $next_pask = count($this->PageAnnots[$this->page]);
  18387. } else {
  18388. $next_pask = 0;
  18389. }
  18390. if (isset($this->PageAnnots[$startlinepage])) {
  18391. foreach ($this->PageAnnots[$startlinepage] as $pak => $pac) {
  18392. if ($pak >= $pask) {
  18393. $this->PageAnnots[$this->page][] = $pac;
  18394. unset($this->PageAnnots[$startlinepage][$pak]);
  18395. $npak = count($this->PageAnnots[$this->page]) - 1;
  18396. $this->PageAnnots[$this->page][$npak]['y'] -= $yshift;
  18397. }
  18398. }
  18399. }
  18400. $pask = $next_pask;
  18401. $startlinepos = $this->cntmrk[$this->page];
  18402. $startlinepage = $this->page;
  18403. $startliney = $this->y;
  18404. }
  18405. if (!isset($dom[$key]['line-height'])) {
  18406. $dom[$key]['line-height'] = $this->cell_height_ratio;
  18407. }
  18408. if (!$dom[$key]['block']) {
  18409. $this->y += (((($curfontsize * $this->cell_height_ratio ) - ($fontsize * $dom[$key]['line-height'])) / $this->k) + $curfontascent - $fontascent - $curfontdescent + $fontdescent) / 2;
  18410. if (($dom[$key]['value'] != 'sup') AND ($dom[$key]['value'] != 'sub')) {
  18411. $minstartliney = min($this->y, $minstartliney);
  18412. $maxbottomliney = max(($this->y + (($fontsize * $this->cell_height_ratio) / $this->k)), $maxbottomliney);
  18413. }
  18414. }
  18415. $this->cell_height_ratio = $dom[$key]['line-height'];
  18416. $fontaligned = true;
  18417. }
  18418. $this->SetFont($fontname, $fontstyle, $fontsize);
  18419. // reset row height
  18420. $this->resetLastH();
  18421. $curfontname = $fontname;
  18422. $curfontstyle = $fontstyle;
  18423. $curfontsize = $fontsize;
  18424. $curfontascent = $fontascent;
  18425. $curfontdescent = $fontdescent;
  18426. }
  18427. }
  18428. // set text rendering mode
  18429. $textstroke = isset($dom[$key]['stroke']) ? $dom[$key]['stroke'] : $this->textstrokewidth;
  18430. $textfill = isset($dom[$key]['fill']) ? $dom[$key]['fill'] : (($this->textrendermode % 2) == 0);
  18431. $textclip = isset($dom[$key]['clip']) ? $dom[$key]['clip'] : ($this->textrendermode > 3);
  18432. $this->setTextRenderingMode($textstroke, $textfill, $textclip);
  18433. if (isset($dom[$key]['font-stretch']) AND ($dom[$key]['font-stretch'] !== false)) {
  18434. $this->setFontStretching($dom[$key]['font-stretch']);
  18435. }
  18436. if (isset($dom[$key]['letter-spacing']) AND ($dom[$key]['letter-spacing'] !== false)) {
  18437. $this->setFontSpacing($dom[$key]['letter-spacing']);
  18438. }
  18439. if (($plalign == 'J') AND $dom[$key]['block']) {
  18440. $plalign = '';
  18441. }
  18442. // get current position on page buffer
  18443. $curpos = $this->pagelen[$startlinepage];
  18444. if (isset($dom[$key]['bgcolor']) AND ($dom[$key]['bgcolor'] !== false)) {
  18445. $this->SetFillColorArray($dom[$key]['bgcolor']);
  18446. $wfill = true;
  18447. } else {
  18448. $wfill = $fill | false;
  18449. }
  18450. if (isset($dom[$key]['fgcolor']) AND ($dom[$key]['fgcolor'] !== false)) {
  18451. $this->SetTextColorArray($dom[$key]['fgcolor']);
  18452. }
  18453. if (isset($dom[$key]['strokecolor']) AND ($dom[$key]['strokecolor'] !== false)) {
  18454. $this->SetDrawColorArray($dom[$key]['strokecolor']);
  18455. }
  18456. if (isset($dom[$key]['align'])) {
  18457. $lalign = $dom[$key]['align'];
  18458. }
  18459. if ($this->empty_string($lalign)) {
  18460. $lalign = $align;
  18461. }
  18462. }
  18463. // align lines
  18464. if ($this->newline AND (strlen($dom[$key]['value']) > 0) AND ($dom[$key]['value'] != 'td') AND ($dom[$key]['value'] != 'th')) {
  18465. $newline = true;
  18466. $fontaligned = false;
  18467. // we are at the beginning of a new line
  18468. if (isset($startlinex)) {
  18469. $yshift = $minstartliney - $startliney;
  18470. if (($yshift > 0) OR ($this->page > $startlinepage)) {
  18471. $yshift = 0;
  18472. }
  18473. $t_x = 0;
  18474. // the last line must be shifted to be aligned as requested
  18475. $linew = abs($this->endlinex - $startlinex);
  18476. if ($this->inxobj) {
  18477. // we are inside an XObject template
  18478. $pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
  18479. if (isset($opentagpos)) {
  18480. $midpos = $opentagpos;
  18481. } else {
  18482. $midpos = 0;
  18483. }
  18484. if ($midpos > 0) {
  18485. $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
  18486. $pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
  18487. } else {
  18488. $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
  18489. $pend = '';
  18490. }
  18491. } else {
  18492. $pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
  18493. if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
  18494. $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
  18495. $midpos = min($opentagpos, $this->footerpos[$startlinepage]);
  18496. } elseif (isset($opentagpos)) {
  18497. $midpos = $opentagpos;
  18498. } elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
  18499. $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
  18500. $midpos = $this->footerpos[$startlinepage];
  18501. } else {
  18502. $midpos = 0;
  18503. }
  18504. if ($midpos > 0) {
  18505. $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
  18506. $pend = substr($this->getPageBuffer($startlinepage), $midpos);
  18507. } else {
  18508. $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
  18509. $pend = '';
  18510. }
  18511. }
  18512. if ((isset($plalign) AND ((($plalign == 'C') OR ($plalign == 'J') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
  18513. // calculate shifting amount
  18514. $tw = $w;
  18515. if (($plalign == 'J') AND $this->isRTLTextDir() AND ($this->num_columns > 1)) {
  18516. $tw += $this->cell_padding['R'];
  18517. }
  18518. if ($this->lMargin != $prevlMargin) {
  18519. $tw += ($prevlMargin - $this->lMargin);
  18520. }
  18521. if ($this->rMargin != $prevrMargin) {
  18522. $tw += ($prevrMargin - $this->rMargin);
  18523. }
  18524. $one_space_width = $this->GetStringWidth(chr(32));
  18525. $no = 0; // number of spaces on a line contained on a single block
  18526. if ($this->isRTLTextDir()) { // RTL
  18527. // remove left space if exist
  18528. $pos1 = $this->revstrpos($pmid, '[(');
  18529. if ($pos1 > 0) {
  18530. $pos1 = intval($pos1);
  18531. if ($this->isUnicodeFont()) {
  18532. $pos2 = intval($this->revstrpos($pmid, '[('.chr(0).chr(32)));
  18533. $spacelen = 2;
  18534. } else {
  18535. $pos2 = intval($this->revstrpos($pmid, '[('.chr(32)));
  18536. $spacelen = 1;
  18537. }
  18538. if ($pos1 == $pos2) {
  18539. $pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
  18540. if (substr($pmid, $pos1, 4) == '[()]') {
  18541. $linew -= $one_space_width;
  18542. } elseif ($pos1 == strpos($pmid, '[(')) {
  18543. $no = 1;
  18544. }
  18545. }
  18546. }
  18547. } else { // LTR
  18548. // remove right space if exist
  18549. $pos1 = $this->revstrpos($pmid, ')]');
  18550. if ($pos1 > 0) {
  18551. $pos1 = intval($pos1);
  18552. if ($this->isUnicodeFont()) {
  18553. $pos2 = intval($this->revstrpos($pmid, chr(0).chr(32).')]')) + 2;
  18554. $spacelen = 2;
  18555. } else {
  18556. $pos2 = intval($this->revstrpos($pmid, chr(32).')]')) + 1;
  18557. $spacelen = 1;
  18558. }
  18559. if ($pos1 == $pos2) {
  18560. $pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
  18561. $linew -= $one_space_width;
  18562. }
  18563. }
  18564. }
  18565. $mdiff = ($tw - $linew);
  18566. if ($plalign == 'C') {
  18567. if ($this->rtl) {
  18568. $t_x = -($mdiff / 2);
  18569. } else {
  18570. $t_x = ($mdiff / 2);
  18571. }
  18572. } elseif ($plalign == 'R') {
  18573. // right alignment on LTR document
  18574. $t_x = $mdiff;
  18575. } elseif ($plalign == 'L') {
  18576. // left alignment on RTL document
  18577. $t_x = -$mdiff;
  18578. } elseif (($plalign == 'J') AND ($plalign == $lalign)) {
  18579. // Justification
  18580. if ($this->isRTLTextDir()) {
  18581. // align text on the left
  18582. $t_x = -$mdiff;
  18583. }
  18584. $ns = 0; // number of spaces
  18585. $pmidtemp = $pmid;
  18586. // escape special characters
  18587. $pmidtemp = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmidtemp);
  18588. $pmidtemp = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmidtemp);
  18589. // search spaces
  18590. if (preg_match_all('/\[\(([^\)]*)\)\]/x', $pmidtemp, $lnstring, PREG_PATTERN_ORDER)) {
  18591. $spacestr = $this->getSpaceString();
  18592. $maxkk = count($lnstring[1]) - 1;
  18593. for ($kk=0; $kk <= $maxkk; ++$kk) {
  18594. // restore special characters
  18595. $lnstring[1][$kk] = str_replace('#!#OP#!#', '(', $lnstring[1][$kk]);
  18596. $lnstring[1][$kk] = str_replace('#!#CP#!#', ')', $lnstring[1][$kk]);
  18597. // store number of spaces on the strings
  18598. $lnstring[2][$kk] = substr_count($lnstring[1][$kk], $spacestr);
  18599. // count total spaces on line
  18600. $ns += $lnstring[2][$kk];
  18601. $lnstring[3][$kk] = $ns;
  18602. }
  18603. if ($ns == 0) {
  18604. $ns = 1;
  18605. }
  18606. // calculate additional space to add to each existing space
  18607. $spacewidth = ($mdiff / ($ns - $no)) * $this->k;
  18608. $spacewidthu = -1000 * ($mdiff + (($ns + $no) * $one_space_width)) / $ns / $this->FontSize;
  18609. if ($this->font_spacing != 0) {
  18610. // fixed spacing mode
  18611. $osw = -1000 * $this->font_spacing / $this->FontSize;
  18612. $spacewidthu += $osw;
  18613. }
  18614. $nsmax = $ns;
  18615. $ns = 0;
  18616. reset($lnstring);
  18617. $offset = 0;
  18618. $strcount = 0;
  18619. $prev_epsposbeg = 0;
  18620. $textpos = 0;
  18621. if ($this->isRTLTextDir()) {
  18622. $textpos = $this->wPt;
  18623. }
  18624. global $spacew;
  18625. while (preg_match('/([0-9\.\+\-]*)[\s](Td|cm|m|l|c|re)[\s]/x', $pmid, $strpiece, PREG_OFFSET_CAPTURE, $offset) == 1) {
  18626. // check if we are inside a string section '[( ... )]'
  18627. $stroffset = strpos($pmid, '[(', $offset);
  18628. if (($stroffset !== false) AND ($stroffset <= $strpiece[2][1])) {
  18629. // set offset to the end of string section
  18630. $offset = strpos($pmid, ')]', $stroffset);
  18631. while (($offset !== false) AND ($pmid{($offset - 1)} == '\\')) {
  18632. $offset = strpos($pmid, ')]', ($offset + 1));
  18633. }
  18634. if ($offset === false) {
  18635. $this->Error('HTML Justification: malformed PDF code.');
  18636. }
  18637. continue;
  18638. }
  18639. if ($this->isRTLTextDir()) {
  18640. $spacew = ($spacewidth * ($nsmax - $ns));
  18641. } else {
  18642. $spacew = ($spacewidth * $ns);
  18643. }
  18644. $offset = $strpiece[2][1] + strlen($strpiece[2][0]);
  18645. $epsposbeg = strpos($pmid, 'q'.$this->epsmarker, $offset);
  18646. $epsposend = strpos($pmid, $this->epsmarker.'Q', $offset) + strlen($this->epsmarker.'Q');
  18647. if ((($epsposbeg > 0) AND ($epsposend > 0) AND ($offset > $epsposbeg) AND ($offset < $epsposend))
  18648. OR (($epsposbeg === false) AND ($epsposend > 0) AND ($offset < $epsposend))) {
  18649. // shift EPS images
  18650. $trx = sprintf('1 0 0 1 %.3F 0 cm', $spacew);
  18651. $epsposbeg = strpos($pmid, 'q'.$this->epsmarker, ($prev_epsposbeg - 6));
  18652. $pmid_b = substr($pmid, 0, $epsposbeg);
  18653. $pmid_m = substr($pmid, $epsposbeg, ($epsposend - $epsposbeg));
  18654. $pmid_e = substr($pmid, $epsposend);
  18655. $pmid = $pmid_b."\nq\n".$trx."\n".$pmid_m."\nQ\n".$pmid_e;
  18656. $offset = $epsposend;
  18657. continue;
  18658. }
  18659. $prev_epsposbeg = $epsposbeg;
  18660. $currentxpos = 0;
  18661. // shift blocks of code
  18662. switch ($strpiece[2][0]) {
  18663. case 'Td':
  18664. case 'cm':
  18665. case 'm':
  18666. case 'l': {
  18667. // get current X position
  18668. preg_match('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x', $pmid, $xmatches);
  18669. $currentxpos = $xmatches[1];
  18670. $textpos = $currentxpos;
  18671. if (($strcount <= $maxkk) AND ($strpiece[2][0] == 'Td')) {
  18672. $ns = $lnstring[3][$strcount];
  18673. if ($this->isRTLTextDir()) {
  18674. $spacew = ($spacewidth * ($nsmax - $ns));
  18675. }
  18676. ++$strcount;
  18677. }
  18678. // justify block
  18679. $pmid = preg_replace_callback('/([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s]('.$strpiece[2][0].')([\s]*)/x',
  18680. create_function('$matches', 'global $spacew;
  18681. $newx = sprintf("%.2F",(floatval($matches[1]) + $spacew));
  18682. return "".$newx." ".$matches[2]." x*#!#*x".$matches[3].$matches[4];'), $pmid, 1);
  18683. break;
  18684. }
  18685. case 're': {
  18686. // justify block
  18687. if (!$this->empty_string($this->lispacer)) {
  18688. $this->lispacer = '';
  18689. continue;
  18690. }
  18691. preg_match('/([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]([0-9\.\+\-]*)[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x', $pmid, $xmatches);
  18692. $currentxpos = $xmatches[1];
  18693. global $x_diff, $w_diff;
  18694. $x_diff = 0;
  18695. $w_diff = 0;
  18696. if ($this->isRTLTextDir()) { // RTL
  18697. if ($currentxpos < $textpos) {
  18698. $x_diff = ($spacewidth * ($nsmax - $lnstring[3][$strcount]));
  18699. $w_diff = ($spacewidth * $lnstring[2][$strcount]);
  18700. } else {
  18701. if ($strcount > 0) {
  18702. $x_diff = ($spacewidth * ($nsmax - $lnstring[3][($strcount - 1)]));
  18703. $w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
  18704. }
  18705. }
  18706. } else { // LTR
  18707. if ($currentxpos > $textpos) {
  18708. if ($strcount > 0) {
  18709. $x_diff = ($spacewidth * $lnstring[3][($strcount - 1)]);
  18710. }
  18711. $w_diff = ($spacewidth * $lnstring[2][$strcount]);
  18712. } else {
  18713. if ($strcount > 1) {
  18714. $x_diff = ($spacewidth * $lnstring[3][($strcount - 2)]);
  18715. }
  18716. if ($strcount > 0) {
  18717. $w_diff = ($spacewidth * $lnstring[2][($strcount - 1)]);
  18718. }
  18719. }
  18720. }
  18721. $pmid = preg_replace_callback('/('.$xmatches[1].')[\s]('.$xmatches[2].')[\s]('.$xmatches[3].')[\s]('.$strpiece[1][0].')[\s](re)([\s]*)/x',
  18722. create_function('$matches', 'global $x_diff, $w_diff;
  18723. $newx = sprintf("%.2F",(floatval($matches[1]) + $x_diff));
  18724. $neww = sprintf("%.2F",(floatval($matches[3]) + $w_diff));
  18725. return "".$newx." ".$matches[2]." ".$neww." ".$matches[4]." x*#!#*x".$matches[5].$matches[6];'), $pmid, 1);
  18726. break;
  18727. }
  18728. case 'c': {
  18729. // get current X position
  18730. 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);
  18731. $currentxpos = $xmatches[1];
  18732. // justify block
  18733. $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',
  18734. create_function('$matches', 'global $spacew;
  18735. $newx1 = sprintf("%.3F",(floatval($matches[1]) + $spacew));
  18736. $newx2 = sprintf("%.3F",(floatval($matches[3]) + $spacew));
  18737. $newx3 = sprintf("%.3F",(floatval($matches[5]) + $spacew));
  18738. return "".$newx1." ".$matches[2]." ".$newx2." ".$matches[4]." ".$newx3." ".$matches[6]." x*#!#*x".$matches[7].$matches[8];'), $pmid, 1);
  18739. break;
  18740. }
  18741. }
  18742. // shift the annotations and links
  18743. $cxpos = ($currentxpos / $this->k);
  18744. $lmpos = ($this->lMargin + $this->cell_padding['L'] + $this->feps);
  18745. if ($this->inxobj) {
  18746. // we are inside an XObject template
  18747. foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
  18748. if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
  18749. if ($cxpos > $lmpos) {
  18750. $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += ($spacew / $this->k);
  18751. $this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
  18752. } else {
  18753. $this->xobjects[$this->xobjid]['annotations'][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
  18754. }
  18755. break;
  18756. }
  18757. }
  18758. } elseif (isset($this->PageAnnots[$this->page])) {
  18759. foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
  18760. if (($pac['y'] >= $minstartliney) AND (($pac['x'] * $this->k) >= ($currentxpos - $this->feps)) AND (($pac['x'] * $this->k) <= ($currentxpos + $this->feps))) {
  18761. if ($cxpos > $lmpos) {
  18762. $this->PageAnnots[$this->page][$pak]['x'] += ($spacew / $this->k);
  18763. $this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
  18764. } else {
  18765. $this->PageAnnots[$this->page][$pak]['w'] += (($spacewidth * $pac['numspaces']) / $this->k);
  18766. }
  18767. break;
  18768. }
  18769. }
  18770. }
  18771. } // end of while
  18772. // remove markers
  18773. $pmid = str_replace('x*#!#*x', '', $pmid);
  18774. if ($this->isUnicodeFont()) {
  18775. // multibyte characters
  18776. $spacew = $spacewidthu;
  18777. if ($this->font_stretching != 100) {
  18778. // word spacing is affected by stretching
  18779. $spacew /= ($this->font_stretching / 100);
  18780. }
  18781. $pmidtemp = $pmid;
  18782. // escape special characters
  18783. $pmidtemp = preg_replace('/[\\\][\(]/x', '\\#!#OP#!#', $pmidtemp);
  18784. $pmidtemp = preg_replace('/[\\\][\)]/x', '\\#!#CP#!#', $pmidtemp);
  18785. $pmid = preg_replace_callback("/\[\(([^\)]*)\)\]/x",
  18786. create_function('$matches', 'global $spacew;
  18787. $matches[1] = str_replace("#!#OP#!#", "(", $matches[1]);
  18788. $matches[1] = str_replace("#!#CP#!#", ")", $matches[1]);
  18789. return "[(".str_replace(chr(0).chr(32), ") ".sprintf("%.3F", $spacew)." (", $matches[1]).")]";'), $pmidtemp);
  18790. if ($this->inxobj) {
  18791. // we are inside an XObject template
  18792. $this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\n".$pend;
  18793. } else {
  18794. $this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\n".$pend);
  18795. }
  18796. $endlinepos = strlen($pstart."\n".$pmid."\n");
  18797. } else {
  18798. // non-unicode (single-byte characters)
  18799. if ($this->font_stretching != 100) {
  18800. // word spacing (Tw) is affected by stretching
  18801. $spacewidth /= ($this->font_stretching / 100);
  18802. }
  18803. $rs = sprintf('%.3F Tw', $spacewidth);
  18804. $pmid = preg_replace("/\[\(/x", $rs.' [(', $pmid);
  18805. if ($this->inxobj) {
  18806. // we are inside an XObject template
  18807. $this->xobjects[$this->xobjid]['outdata'] = $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend;
  18808. } else {
  18809. $this->setPageBuffer($startlinepage, $pstart."\n".$pmid."\nBT 0 Tw ET\n".$pend);
  18810. }
  18811. $endlinepos = strlen($pstart."\n".$pmid."\nBT 0 Tw ET\n");
  18812. }
  18813. }
  18814. } // end of J
  18815. } // end if $startlinex
  18816. if (($t_x != 0) OR ($yshift < 0)) {
  18817. // shift the line
  18818. $trx = sprintf('1 0 0 1 %.3F %.3F cm', ($t_x * $this->k), ($yshift * $this->k));
  18819. $pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
  18820. $endlinepos = strlen($pstart);
  18821. if ($this->inxobj) {
  18822. // we are inside an XObject template
  18823. $this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
  18824. foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
  18825. if ($pak >= $pask) {
  18826. $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
  18827. $this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
  18828. }
  18829. }
  18830. } else {
  18831. $this->setPageBuffer($startlinepage, $pstart.$pend);
  18832. // shift the annotations and links
  18833. if (isset($this->PageAnnots[$this->page])) {
  18834. foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
  18835. if ($pak >= $pask) {
  18836. $this->PageAnnots[$this->page][$pak]['x'] += $t_x;
  18837. $this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
  18838. }
  18839. }
  18840. }
  18841. }
  18842. $this->y -= $yshift;
  18843. }
  18844. }
  18845. $pbrk = $this->checkPageBreak($this->lasth);
  18846. $this->newline = false;
  18847. $startlinex = $this->x;
  18848. $startliney = $this->y;
  18849. if ($dom[$dom[$key]['parent']]['value'] == 'sup') {
  18850. $startliney -= ((0.3 * $this->FontSizePt) / $this->k);
  18851. } elseif ($dom[$dom[$key]['parent']]['value'] == 'sub') {
  18852. $startliney -= (($this->FontSizePt / 0.7) / $this->k);
  18853. } else {
  18854. $minstartliney = $startliney;
  18855. $maxbottomliney = ($this->y + (($fontsize * $this->cell_height_ratio) / $this->k));
  18856. }
  18857. $startlinepage = $this->page;
  18858. if (isset($endlinepos) AND (!$pbrk)) {
  18859. $startlinepos = $endlinepos;
  18860. } else {
  18861. if ($this->inxobj) {
  18862. // we are inside an XObject template
  18863. $startlinepos = strlen($this->xobjects[$this->xobjid]['outdata']);
  18864. } elseif (!$this->InFooter) {
  18865. if (isset($this->footerlen[$this->page])) {
  18866. $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
  18867. } else {
  18868. $this->footerpos[$this->page] = $this->pagelen[$this->page];
  18869. }
  18870. $startlinepos = $this->footerpos[$this->page];
  18871. } else {
  18872. $startlinepos = $this->pagelen[$this->page];
  18873. }
  18874. }
  18875. unset($endlinepos);
  18876. $plalign = $lalign;
  18877. if (isset($this->PageAnnots[$this->page])) {
  18878. $pask = count($this->PageAnnots[$this->page]);
  18879. } else {
  18880. $pask = 0;
  18881. }
  18882. if (!($dom[$key]['tag'] AND !$dom[$key]['opening'] AND ($dom[$key]['value'] == 'table') AND ($this->emptypagemrk[$this->page] == $this->pagelen[$this->page]))) {
  18883. $this->SetFont($fontname, $fontstyle, $fontsize);
  18884. if ($wfill) {
  18885. $this->SetFillColorArray($this->bgcolor);
  18886. }
  18887. }
  18888. } // end newline
  18889. if (isset($opentagpos)) {
  18890. unset($opentagpos);
  18891. }
  18892. if ($dom[$key]['tag']) {
  18893. if ($dom[$key]['opening']) {
  18894. // get text indentation (if any)
  18895. if (isset($dom[$key]['text-indent']) AND $dom[$key]['block']) {
  18896. $this->textindent = $dom[$key]['text-indent'];
  18897. $this->newline = true;
  18898. }
  18899. // table
  18900. if ($dom[$key]['value'] == 'table') {
  18901. // available page width
  18902. if ($this->rtl) {
  18903. $wtmp = $this->x - $this->lMargin;
  18904. } else {
  18905. $wtmp = $this->w - $this->rMargin - $this->x;
  18906. }
  18907. if (isset($dom[$key]['attribute']['cellspacing'])) {
  18908. $cellspacing = $this->getHTMLUnitToUnits($dom[$key]['attribute']['cellspacing'], 1, 'px');
  18909. } else {
  18910. $cellspacing = 0;
  18911. }
  18912. // table width
  18913. if (isset($dom[$key]['width'])) {
  18914. $table_width = $this->getHTMLUnitToUnits($dom[$key]['width'], $wtmp, 'px');
  18915. } else {
  18916. $table_width = $wtmp;
  18917. }
  18918. $table_width -= (2 * $cellspacing);
  18919. if (!$this->inthead) {
  18920. $this->y += $cellspacing;
  18921. }
  18922. if ($this->rtl) {
  18923. $cellspacingx = -$cellspacing;
  18924. } else {
  18925. $cellspacingx = $cellspacing;
  18926. }
  18927. // total table width without cellspaces
  18928. $table_columns_width = ($table_width - ($cellspacing * ($dom[$key]['cols'] - 1)));
  18929. // minimum column width
  18930. $table_min_column_width = ($table_columns_width / $dom[$key]['cols']);
  18931. // array of custom column widths
  18932. $table_colwidths = array_fill(0, $dom[$key]['cols'], $table_min_column_width);
  18933. }
  18934. // table row
  18935. if ($dom[$key]['value'] == 'tr') {
  18936. // reset column counter
  18937. $colid = 0;
  18938. }
  18939. // table cell
  18940. if (($dom[$key]['value'] == 'td') OR ($dom[$key]['value'] == 'th')) {
  18941. $trid = $dom[$key]['parent'];
  18942. $table_el = $dom[$trid]['parent'];
  18943. if (!isset($dom[$table_el]['cols'])) {
  18944. $dom[$table_el]['cols'] = $dom[$trid]['cols'];
  18945. }
  18946. // store border info
  18947. $tdborder = 0;
  18948. if (isset($dom[$key]['border']) AND !empty($dom[$key]['border'])) {
  18949. $tdborder = $dom[$key]['border'];
  18950. }
  18951. $colspan = $dom[$key]['attribute']['colspan'];
  18952. $old_cell_padding = $this->cell_padding;
  18953. if (isset($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'])) {
  18954. $current_cell_padding = $this->getHTMLUnitToUnits($dom[($dom[$trid]['parent'])]['attribute']['cellpadding'], 1, 'px');
  18955. } else {
  18956. $current_cell_padding = 0;
  18957. }
  18958. $this->SetCellPadding($current_cell_padding);
  18959. if (isset($dom[$key]['height'])) {
  18960. // minimum cell height
  18961. $cellh = $this->getHTMLUnitToUnits($dom[$key]['height'], 0, 'px');
  18962. } else {
  18963. $cellh = 0;
  18964. }
  18965. if (isset($dom[$key]['content'])) {
  18966. $cell_content = $dom[$key]['content'];
  18967. } else {
  18968. $cell_content = '&nbsp;';
  18969. }
  18970. $tagtype = $dom[$key]['value'];
  18971. $parentid = $key;
  18972. while (($key < $maxel) AND (!(($dom[$key]['tag']) AND (!$dom[$key]['opening']) AND ($dom[$key]['value'] == $tagtype) AND ($dom[$key]['parent'] == $parentid)))) {
  18973. // move $key index forward
  18974. ++$key;
  18975. }
  18976. if (!isset($dom[$trid]['startpage'])) {
  18977. $dom[$trid]['startpage'] = $this->page;
  18978. } else {
  18979. $this->setPage($dom[$trid]['startpage']);
  18980. }
  18981. if (!isset($dom[$trid]['startcolumn'])) {
  18982. $dom[$trid]['startcolumn'] = $this->current_column;
  18983. } elseif ($this->current_column != $dom[$trid]['startcolumn']) {
  18984. $tmpx = $this->x;
  18985. $this->selectColumn($dom[$trid]['startcolumn']);
  18986. $this->x = $tmpx;
  18987. }
  18988. if (!isset($dom[$trid]['starty'])) {
  18989. $dom[$trid]['starty'] = $this->y;
  18990. } else {
  18991. $this->y = $dom[$trid]['starty'];
  18992. }
  18993. if (!isset($dom[$trid]['startx'])) {
  18994. $dom[$trid]['startx'] = $this->x;
  18995. $this->x += $cellspacingx;
  18996. } else {
  18997. $this->x += ($cellspacingx / 2);
  18998. }
  18999. if (isset($dom[$parentid]['attribute']['rowspan'])) {
  19000. $rowspan = intval($dom[$parentid]['attribute']['rowspan']);
  19001. } else {
  19002. $rowspan = 1;
  19003. }
  19004. // skip row-spanned cells started on the previous rows
  19005. if (isset($dom[$table_el]['rowspans'])) {
  19006. $rsk = 0;
  19007. $rskmax = count($dom[$table_el]['rowspans']);
  19008. while ($rsk < $rskmax) {
  19009. $trwsp = $dom[$table_el]['rowspans'][$rsk];
  19010. $rsstartx = $trwsp['startx'];
  19011. $rsendx = $trwsp['endx'];
  19012. // account for margin changes
  19013. if ($trwsp['startpage'] < $this->page) {
  19014. if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$trwsp['startpage']]['orm'])) {
  19015. $dl = ($this->pagedim[$this->page]['orm'] - $this->pagedim[$trwsp['startpage']]['orm']);
  19016. $rsstartx -= $dl;
  19017. $rsendx -= $dl;
  19018. } elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$trwsp['startpage']]['olm'])) {
  19019. $dl = ($this->pagedim[$this->page]['olm'] - $this->pagedim[$trwsp['startpage']]['olm']);
  19020. $rsstartx += $dl;
  19021. $rsendx += $dl;
  19022. }
  19023. }
  19024. if (($trwsp['rowspan'] > 0)
  19025. AND ($rsstartx > ($this->x - $cellspacing - $current_cell_padding - $this->feps))
  19026. AND ($rsstartx < ($this->x + $cellspacing + $current_cell_padding + $this->feps))
  19027. AND (($trwsp['starty'] < ($this->y - $this->feps)) OR ($trwsp['startpage'] < $this->page) OR ($trwsp['startcolumn'] < $this->current_column))) {
  19028. // set the starting X position of the current cell
  19029. $this->x = $rsendx + $cellspacingx;
  19030. // increment column indicator
  19031. $colid += $trwsp['colspan'];
  19032. if (($trwsp['rowspan'] == 1)
  19033. AND (isset($dom[$trid]['endy']))
  19034. AND (isset($dom[$trid]['endpage']))
  19035. AND (isset($dom[$trid]['endcolumn']))
  19036. AND ($trwsp['endpage'] == $dom[$trid]['endpage'])
  19037. AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
  19038. // set ending Y position for row
  19039. $dom[$table_el]['rowspans'][$rsk]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
  19040. $dom[$trid]['endy'] = $dom[$table_el]['rowspans'][$rsk]['endy'];
  19041. }
  19042. $rsk = 0;
  19043. } else {
  19044. ++$rsk;
  19045. }
  19046. }
  19047. }
  19048. if (isset($dom[$parentid]['width'])) {
  19049. // user specified width
  19050. $cellw = $this->getHTMLUnitToUnits($dom[$parentid]['width'], $table_columns_width, 'px');
  19051. $tmpcw = ($cellw / $colspan);
  19052. for ($i = 0; $i < $colspan; ++$i) {
  19053. $table_colwidths[($colid + $i)] = $tmpcw;
  19054. }
  19055. } else {
  19056. // inherit column width
  19057. $cellw = 0;
  19058. for ($i = 0; $i < $colspan; ++$i) {
  19059. $cellw += $table_colwidths[($colid + $i)];
  19060. }
  19061. }
  19062. $cellw += (($colspan - 1) * $cellspacing);
  19063. // increment column indicator
  19064. $colid += $colspan;
  19065. // add rowspan information to table element
  19066. if ($rowspan > 1) {
  19067. $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));
  19068. }
  19069. $cellid = array_push($dom[$trid]['cellpos'], array('startx' => $this->x));
  19070. if ($rowspan > 1) {
  19071. $dom[$trid]['cellpos'][($cellid - 1)]['rowspanid'] = ($trsid - 1);
  19072. }
  19073. // push background colors
  19074. if (isset($dom[$parentid]['bgcolor']) AND ($dom[$parentid]['bgcolor'] !== false)) {
  19075. $dom[$trid]['cellpos'][($cellid - 1)]['bgcolor'] = $dom[$parentid]['bgcolor'];
  19076. }
  19077. // store border info
  19078. if (isset($tdborder) AND !empty($tdborder)) {
  19079. $dom[$trid]['cellpos'][($cellid - 1)]['border'] = $tdborder;
  19080. }
  19081. $prevLastH = $this->lasth;
  19082. // store some info for multicolumn mode
  19083. if ($this->rtl) {
  19084. $this->colxshift['x'] = $this->w - $this->x - $this->rMargin;
  19085. } else {
  19086. $this->colxshift['x'] = $this->x - $this->lMargin;
  19087. }
  19088. $this->colxshift['s'] = $cellspacing;
  19089. $this->colxshift['p'] = $current_cell_padding;
  19090. // ****** write the cell content ******
  19091. $this->MultiCell($cellw, $cellh, $cell_content, false, $lalign, false, 2, '', '', true, 0, true);
  19092. // restore some values
  19093. $this->colxshift = array('x' => 0, 's' => 0, 'p' => 0);
  19094. $this->lasth = $prevLastH;
  19095. $this->cell_padding = $old_cell_padding;
  19096. $dom[$trid]['cellpos'][($cellid - 1)]['endx'] = $this->x;
  19097. // update the end of row position
  19098. if ($rowspan <= 1) {
  19099. if (isset($dom[$trid]['endy'])) {
  19100. if (($this->page == $dom[$trid]['endpage']) AND ($this->current_column == $dom[$trid]['endcolumn'])) {
  19101. $dom[$trid]['endy'] = max($this->y, $dom[$trid]['endy']);
  19102. } elseif (($this->page > $dom[$trid]['endpage']) OR ($this->current_column > $dom[$trid]['endcolumn'])) {
  19103. $dom[$trid]['endy'] = $this->y;
  19104. }
  19105. } else {
  19106. $dom[$trid]['endy'] = $this->y;
  19107. }
  19108. if (isset($dom[$trid]['endpage'])) {
  19109. $dom[$trid]['endpage'] = max($this->page, $dom[$trid]['endpage']);
  19110. } else {
  19111. $dom[$trid]['endpage'] = $this->page;
  19112. }
  19113. if (isset($dom[$trid]['endcolumn'])) {
  19114. $dom[$trid]['endcolumn'] = max($this->current_column, $dom[$trid]['endcolumn']);
  19115. } else {
  19116. $dom[$trid]['endcolumn'] = $this->current_column;
  19117. }
  19118. } else {
  19119. // account for row-spanned cells
  19120. $dom[$table_el]['rowspans'][($trsid - 1)]['endx'] = $this->x;
  19121. $dom[$table_el]['rowspans'][($trsid - 1)]['endy'] = $this->y;
  19122. $dom[$table_el]['rowspans'][($trsid - 1)]['endpage'] = $this->page;
  19123. $dom[$table_el]['rowspans'][($trsid - 1)]['endcolumn'] = $this->current_column;
  19124. }
  19125. if (isset($dom[$table_el]['rowspans'])) {
  19126. // update endy and endpage on rowspanned cells
  19127. foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
  19128. if ($trwsp['rowspan'] > 0) {
  19129. if (isset($dom[$trid]['endpage'])) {
  19130. if (($trwsp['endpage'] == $dom[$trid]['endpage']) AND ($trwsp['endcolumn'] == $dom[$trid]['endcolumn'])) {
  19131. $dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$trid]['endy'], $trwsp['endy']);
  19132. } elseif (($trwsp['endpage'] < $dom[$trid]['endpage']) OR ($trwsp['endcolumn'] < $dom[$trid]['endcolumn'])) {
  19133. $dom[$table_el]['rowspans'][$k]['endy'] = $dom[$trid]['endy'];
  19134. $dom[$table_el]['rowspans'][$k]['endpage'] = $dom[$trid]['endpage'];
  19135. $dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[$trid]['endcolumn'];
  19136. } else {
  19137. $dom[$trid]['endy'] = $this->pagedim[$dom[$trid]['endpage']]['hk'] - $this->pagedim[$dom[$trid]['endpage']]['bm'];
  19138. }
  19139. }
  19140. }
  19141. }
  19142. }
  19143. $this->x += ($cellspacingx / 2);
  19144. } else {
  19145. // opening tag (or self-closing tag)
  19146. if (!isset($opentagpos)) {
  19147. if ($this->inxobj) {
  19148. // we are inside an XObject template
  19149. $opentagpos = strlen($this->xobjects[$this->xobjid]['outdata']);
  19150. } elseif (!$this->InFooter) {
  19151. if (isset($this->footerlen[$this->page])) {
  19152. $this->footerpos[$this->page] = $this->pagelen[$this->page] - $this->footerlen[$this->page];
  19153. } else {
  19154. $this->footerpos[$this->page] = $this->pagelen[$this->page];
  19155. }
  19156. $opentagpos = $this->footerpos[$this->page];
  19157. }
  19158. }
  19159. $this->openHTMLTagHandler($dom, $key, $cell);
  19160. }
  19161. } else { // closing tag
  19162. $prev_numpages = $this->numpages;
  19163. $old_bordermrk = $this->bordermrk[$this->page];
  19164. $this->closeHTMLTagHandler($dom, $key, $cell, $maxbottomliney);
  19165. if ($this->bordermrk[$this->page] > $old_bordermrk) {
  19166. $startlinepos += ($this->bordermrk[$this->page] - $old_bordermrk);
  19167. }
  19168. if ($prev_numpages > $this->numpages) {
  19169. $startlinepage = $this->page;
  19170. }
  19171. }
  19172. } elseif (strlen($dom[$key]['value']) > 0) {
  19173. // print list-item
  19174. if (!$this->empty_string($this->lispacer) AND ($this->lispacer != '^')) {
  19175. $this->SetFont($pfontname, $pfontstyle, $pfontsize);
  19176. $this->resetLastH();
  19177. $minstartliney = $this->y;
  19178. $maxbottomliney = ($startliney + ($this->FontSize * $this->cell_height_ratio));
  19179. $this->putHtmlListBullet($this->listnum, $this->lispacer, $pfontsize);
  19180. $this->SetFont($curfontname, $curfontstyle, $curfontsize);
  19181. $this->resetLastH();
  19182. if (is_numeric($pfontsize) AND ($pfontsize > 0) AND is_numeric($curfontsize) AND ($curfontsize > 0) AND ($pfontsize != $curfontsize)) {
  19183. $pfontascent = $this->getFontAscent($pfontname, $pfontstyle, $pfontsize);
  19184. $pfontdescent = $this->getFontDescent($pfontname, $pfontstyle, $pfontsize);
  19185. $this->y += ((($pfontsize - $curfontsize) * $this->cell_height_ratio / $this->k) + $pfontascent - $curfontascent - $pfontdescent + $curfontdescent) / 2;
  19186. $minstartliney = min($this->y, $minstartliney);
  19187. $maxbottomliney = max(($this->y + (($pfontsize * $this->cell_height_ratio) / $this->k)), $maxbottomliney);
  19188. }
  19189. }
  19190. // text
  19191. $this->htmlvspace = 0;
  19192. if ((!$this->premode) AND $this->isRTLTextDir()) {
  19193. // reverse spaces order
  19194. $lsp = ''; // left spaces
  19195. $rsp = ''; // right spaces
  19196. if (preg_match('/^('.$this->re_space['p'].'+)/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
  19197. $lsp = $matches[1];
  19198. }
  19199. if (preg_match('/('.$this->re_space['p'].'+)$/'.$this->re_space['m'], $dom[$key]['value'], $matches)) {
  19200. $rsp = $matches[1];
  19201. }
  19202. $dom[$key]['value'] = $rsp.$this->stringTrim($dom[$key]['value']).$lsp;
  19203. }
  19204. if ($newline) {
  19205. if (!$this->premode) {
  19206. $prelen = strlen($dom[$key]['value']);
  19207. if ($this->isRTLTextDir()) {
  19208. // right trim except non-breaking space
  19209. $dom[$key]['value'] = $this->stringRightTrim($dom[$key]['value']);
  19210. } else {
  19211. // left trim except non-breaking space
  19212. $dom[$key]['value'] = $this->stringLeftTrim($dom[$key]['value']);
  19213. }
  19214. $postlen = strlen($dom[$key]['value']);
  19215. if (($postlen == 0) AND ($prelen > 0)) {
  19216. $dom[$key]['trimmed_space'] = true;
  19217. }
  19218. }
  19219. $newline = false;
  19220. $firstblock = true;
  19221. } else {
  19222. $firstblock = false;
  19223. // replace empty multiple spaces string with a single space
  19224. $dom[$key]['value'] = preg_replace('/^'.$this->re_space['p'].'+$/'.$this->re_space['m'], chr(32), $dom[$key]['value']);
  19225. }
  19226. $strrest = '';
  19227. if ($this->rtl) {
  19228. $this->x -= $this->textindent;
  19229. } else {
  19230. $this->x += $this->textindent;
  19231. }
  19232. if (!isset($dom[$key]['trimmed_space']) OR !$dom[$key]['trimmed_space']) {
  19233. if (!empty($this->HREF) AND (isset($this->HREF['url']))) {
  19234. // HTML <a> Link
  19235. $hrefcolor = '';
  19236. if (isset($dom[($dom[$key]['parent'])]['fgcolor']) AND ($dom[($dom[$key]['parent'])]['fgcolor'] !== false)) {
  19237. $hrefcolor = $dom[($dom[$key]['parent'])]['fgcolor'];
  19238. }
  19239. $hrefstyle = -1;
  19240. if (isset($dom[($dom[$key]['parent'])]['fontstyle']) AND ($dom[($dom[$key]['parent'])]['fontstyle'] !== false)) {
  19241. $hrefstyle = $dom[($dom[$key]['parent'])]['fontstyle'];
  19242. }
  19243. $strrest = $this->addHtmlLink($this->HREF['url'], $dom[$key]['value'], $wfill, true, $hrefcolor, $hrefstyle, true);
  19244. } else {
  19245. $wadj = 0; // space to leave for block continuity
  19246. $adjblks = 0; // number of blocks
  19247. // check the next text blocks for continuity
  19248. $nkey = ($key + 1);
  19249. $write_block = true;
  19250. $tmp_fontname = $this->FontFamily;
  19251. $tmp_fontstyle = $this->FontStyle;
  19252. $tmp_fontsize = $this->FontSizePt;
  19253. while ($write_block AND isset($dom[$nkey])) {
  19254. if ($dom[$nkey]['tag']) {
  19255. if ($dom[$nkey]['block']) {
  19256. // end of block
  19257. $write_block = false;
  19258. }
  19259. $tmp_fontname = isset($dom[$nkey]['fontname']) ? $dom[$nkey]['fontname'] : $this->FontFamily;
  19260. $tmp_fontstyle = isset($dom[$nkey]['fontstyle']) ? $dom[$nkey]['fontstyle'] : $this->FontStyle;
  19261. $tmp_fontsize = isset($dom[$nkey]['fontsize']) ? $dom[$nkey]['fontsize'] : $this->FontSizePt;
  19262. } else {
  19263. $nextstr = preg_split('/'.$this->re_space['p'].'+/'.$this->re_space['m'], $dom[$nkey]['value']);
  19264. if (isset($nextstr[0])) {
  19265. $wadj += $this->GetStringWidth($nextstr[0], $tmp_fontname, $tmp_fontstyle, $tmp_fontsize);
  19266. ++$adjblks;
  19267. }
  19268. if (isset($nextstr[1])) {
  19269. $write_block = false;
  19270. }
  19271. }
  19272. ++$nkey;
  19273. }
  19274. // check for reversed text direction
  19275. if (($wadj > 0) AND (($this->rtl AND ($this->tmprtl == 'L')) OR (!$this->rtl AND ($this->tmprtl == 'R')))) {
  19276. // LTR text on RTL direction or RTL text on LTR direction
  19277. $reverse_dir = true;
  19278. $this->rtl = !$this->rtl;
  19279. $revshift = ($this->GetStringWidth($dom[$key]['value']) + $wadj) + 0.000001; // add little quantity for rounding problems
  19280. if ($this->rtl) {
  19281. $this->x += $revshift;
  19282. } else {
  19283. $this->x -= $revshift;
  19284. }
  19285. $xws = $this->x;
  19286. }
  19287. // ****** write only until the end of the line and get the rest ******
  19288. $strrest = $this->Write($this->lasth, $dom[$key]['value'], '', $wfill, '', false, 0, true, $firstblock, 0, $wadj);
  19289. // restore default direction
  19290. if ($reverse_dir AND ($wadj == 0)) {
  19291. $this->x = $xws;
  19292. $this->rtl = !$this->rtl;
  19293. $reverse_dir = false;
  19294. }
  19295. }
  19296. }
  19297. $this->textindent = 0;
  19298. if (strlen($strrest) > 0) {
  19299. // store the remaining string on the previous $key position
  19300. $this->newline = true;
  19301. if ($strrest == $dom[$key]['value']) {
  19302. // used to avoid infinite loop
  19303. ++$loop;
  19304. } else {
  19305. $loop = 0;
  19306. }
  19307. $dom[$key]['value'] = $strrest;
  19308. if ($cell) {
  19309. if ($this->rtl) {
  19310. $this->x -= $this->cell_padding['R'];
  19311. } else {
  19312. $this->x += $this->cell_padding['L'];
  19313. }
  19314. }
  19315. if ($loop < 3) {
  19316. --$key;
  19317. }
  19318. } else {
  19319. $loop = 0;
  19320. }
  19321. }
  19322. ++$key;
  19323. 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')) {
  19324. // check if we are on a new page or on a new column
  19325. if ((!$undo) AND ($this->y < $this->start_transaction_y)) {
  19326. // we are on a new page or on a new column and the total object height is less than the available vertical space.
  19327. // restore previous object
  19328. $this->rollbackTransaction(true);
  19329. // restore previous values
  19330. foreach ($this_method_vars as $vkey => $vval) {
  19331. $$vkey = $vval;
  19332. }
  19333. // add a page (or trig AcceptPageBreak() for multicolumn mode)
  19334. $pre_y = $this->y;
  19335. if ((!$this->checkPageBreak($this->PageBreakTrigger + 1)) AND ($this->y < $pre_y)) {
  19336. $startliney = $this->y;
  19337. }
  19338. $undo = true; // avoid infinite loop
  19339. } else {
  19340. $undo = false;
  19341. }
  19342. }
  19343. } // end for each $key
  19344. // align the last line
  19345. if (isset($startlinex)) {
  19346. $yshift = $minstartliney - $startliney;
  19347. if (($yshift > 0) OR ($this->page > $startlinepage)) {
  19348. $yshift = 0;
  19349. }
  19350. $t_x = 0;
  19351. // the last line must be shifted to be aligned as requested
  19352. $linew = abs($this->endlinex - $startlinex);
  19353. if ($this->inxobj) {
  19354. // we are inside an XObject template
  19355. $pstart = substr($this->xobjects[$this->xobjid]['outdata'], 0, $startlinepos);
  19356. if (isset($opentagpos)) {
  19357. $midpos = $opentagpos;
  19358. } else {
  19359. $midpos = 0;
  19360. }
  19361. if ($midpos > 0) {
  19362. $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos, ($midpos - $startlinepos));
  19363. $pend = substr($this->xobjects[$this->xobjid]['outdata'], $midpos);
  19364. } else {
  19365. $pmid = substr($this->xobjects[$this->xobjid]['outdata'], $startlinepos);
  19366. $pend = '';
  19367. }
  19368. } else {
  19369. $pstart = substr($this->getPageBuffer($startlinepage), 0, $startlinepos);
  19370. if (isset($opentagpos) AND isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
  19371. $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
  19372. $midpos = min($opentagpos, $this->footerpos[$startlinepage]);
  19373. } elseif (isset($opentagpos)) {
  19374. $midpos = $opentagpos;
  19375. } elseif (isset($this->footerlen[$startlinepage]) AND (!$this->InFooter)) {
  19376. $this->footerpos[$startlinepage] = $this->pagelen[$startlinepage] - $this->footerlen[$startlinepage];
  19377. $midpos = $this->footerpos[$startlinepage];
  19378. } else {
  19379. $midpos = 0;
  19380. }
  19381. if ($midpos > 0) {
  19382. $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos, ($midpos - $startlinepos));
  19383. $pend = substr($this->getPageBuffer($startlinepage), $midpos);
  19384. } else {
  19385. $pmid = substr($this->getPageBuffer($startlinepage), $startlinepos);
  19386. $pend = '';
  19387. }
  19388. }
  19389. if ((isset($plalign) AND ((($plalign == 'C') OR (($plalign == 'R') AND (!$this->rtl)) OR (($plalign == 'L') AND ($this->rtl)))))) {
  19390. // calculate shifting amount
  19391. $tw = $w;
  19392. if ($this->lMargin != $prevlMargin) {
  19393. $tw += ($prevlMargin - $this->lMargin);
  19394. }
  19395. if ($this->rMargin != $prevrMargin) {
  19396. $tw += ($prevrMargin - $this->rMargin);
  19397. }
  19398. $one_space_width = $this->GetStringWidth(chr(32));
  19399. $no = 0; // number of spaces on a line contained on a single block
  19400. if ($this->isRTLTextDir()) { // RTL
  19401. // remove left space if exist
  19402. $pos1 = $this->revstrpos($pmid, '[(');
  19403. if ($pos1 > 0) {
  19404. $pos1 = intval($pos1);
  19405. if ($this->isUnicodeFont()) {
  19406. $pos2 = intval($this->revstrpos($pmid, '[('.chr(0).chr(32)));
  19407. $spacelen = 2;
  19408. } else {
  19409. $pos2 = intval($this->revstrpos($pmid, '[('.chr(32)));
  19410. $spacelen = 1;
  19411. }
  19412. if ($pos1 == $pos2) {
  19413. $pmid = substr($pmid, 0, ($pos1 + 2)).substr($pmid, ($pos1 + 2 + $spacelen));
  19414. if (substr($pmid, $pos1, 4) == '[()]') {
  19415. $linew -= $one_space_width;
  19416. } elseif ($pos1 == strpos($pmid, '[(')) {
  19417. $no = 1;
  19418. }
  19419. }
  19420. }
  19421. } else { // LTR
  19422. // remove right space if exist
  19423. $pos1 = $this->revstrpos($pmid, ')]');
  19424. if ($pos1 > 0) {
  19425. $pos1 = intval($pos1);
  19426. if ($this->isUnicodeFont()) {
  19427. $pos2 = intval($this->revstrpos($pmid, chr(0).chr(32).')]')) + 2;
  19428. $spacelen = 2;
  19429. } else {
  19430. $pos2 = intval($this->revstrpos($pmid, chr(32).')]')) + 1;
  19431. $spacelen = 1;
  19432. }
  19433. if ($pos1 == $pos2) {
  19434. $pmid = substr($pmid, 0, ($pos1 - $spacelen)).substr($pmid, $pos1);
  19435. $linew -= $one_space_width;
  19436. }
  19437. }
  19438. }
  19439. $mdiff = ($tw - $linew);
  19440. if ($plalign == 'C') {
  19441. if ($this->rtl) {
  19442. $t_x = -($mdiff / 2);
  19443. } else {
  19444. $t_x = ($mdiff / 2);
  19445. }
  19446. } elseif ($plalign == 'R') {
  19447. // right alignment on LTR document
  19448. $t_x = $mdiff;
  19449. } elseif ($plalign == 'L') {
  19450. // left alignment on RTL document
  19451. $t_x = -$mdiff;
  19452. }
  19453. } // end if startlinex
  19454. if (($t_x != 0) OR ($yshift < 0)) {
  19455. // shift the line
  19456. $trx = sprintf('1 0 0 1 %.3F %.3F cm', ($t_x * $this->k), ($yshift * $this->k));
  19457. $pstart .= "\nq\n".$trx."\n".$pmid."\nQ\n";
  19458. $endlinepos = strlen($pstart);
  19459. if ($this->inxobj) {
  19460. // we are inside an XObject template
  19461. $this->xobjects[$this->xobjid]['outdata'] = $pstart.$pend;
  19462. foreach ($this->xobjects[$this->xobjid]['annotations'] as $pak => $pac) {
  19463. if ($pak >= $pask) {
  19464. $this->xobjects[$this->xobjid]['annotations'][$pak]['x'] += $t_x;
  19465. $this->xobjects[$this->xobjid]['annotations'][$pak]['y'] -= $yshift;
  19466. }
  19467. }
  19468. } else {
  19469. $this->setPageBuffer($startlinepage, $pstart.$pend);
  19470. // shift the annotations and links
  19471. if (isset($this->PageAnnots[$this->page])) {
  19472. foreach ($this->PageAnnots[$this->page] as $pak => $pac) {
  19473. if ($pak >= $pask) {
  19474. $this->PageAnnots[$this->page][$pak]['x'] += $t_x;
  19475. $this->PageAnnots[$this->page][$pak]['y'] -= $yshift;
  19476. }
  19477. }
  19478. }
  19479. }
  19480. $this->y -= $yshift;
  19481. }
  19482. }
  19483. // restore previous values
  19484. $this->setGraphicVars($gvars);
  19485. if ($this->num_columns > 1) {
  19486. $this->selectColumn();
  19487. } elseif ($this->page > $prevPage) {
  19488. $this->lMargin = $this->pagedim[$this->page]['olm'];
  19489. $this->rMargin = $this->pagedim[$this->page]['orm'];
  19490. }
  19491. // restore previous list state
  19492. $this->cell_height_ratio = $prev_cell_height_ratio;
  19493. $this->listnum = $prev_listnum;
  19494. $this->listordered = $prev_listordered;
  19495. $this->listcount = $prev_listcount;
  19496. $this->lispacer = $prev_lispacer;
  19497. if ($ln AND (!($cell AND ($dom[$key-1]['value'] == 'table')))) {
  19498. $this->Ln($this->lasth);
  19499. if ($this->y < $maxbottomliney) {
  19500. $this->y = $maxbottomliney;
  19501. }
  19502. }
  19503. unset($dom);
  19504. }
  19505. /**
  19506. * Process opening tags.
  19507. * @param array $dom html dom array
  19508. * @param int $key current element id
  19509. * @param boolean $cell if true add the default left (or right if RTL) padding to each new line (default false).
  19510. * @access protected
  19511. */
  19512. protected function openHTMLTagHandler(&$dom, $key, $cell) {
  19513. $tag = $dom[$key];
  19514. $parent = $dom[($dom[$key]['parent'])];
  19515. $firsttag = ($key == 1);
  19516. // check for text direction attribute
  19517. if (isset($tag['dir'])) {
  19518. $this->setTempRTL($tag['dir']);
  19519. } else {
  19520. $this->tmprtl = false;
  19521. }
  19522. if ($tag['block']) {
  19523. $hbz = 0; // distance from y to line bottom
  19524. $hb = 0; // vertical space between block tags
  19525. // calculate vertical space for block tags
  19526. if (isset($this->tagvspaces[$tag['value']][0]['h']) AND ($this->tagvspaces[$tag['value']][0]['h'] >= 0)) {
  19527. $cur_h = $this->tagvspaces[$tag['value']][0]['h'];
  19528. } elseif (isset($tag['fontsize'])) {
  19529. $cur_h = ($tag['fontsize'] / $this->k) * $this->cell_height_ratio;
  19530. } else {
  19531. $cur_h = $this->FontSize * $this->cell_height_ratio;
  19532. }
  19533. if (isset($this->tagvspaces[$tag['value']][0]['n'])) {
  19534. $n = $this->tagvspaces[$tag['value']][0]['n'];
  19535. } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
  19536. $n = 0.6;
  19537. } else {
  19538. $n = 1;
  19539. }
  19540. $hb = ($n * $cur_h);
  19541. if (($this->htmlvspace <= 0) AND ($n > 0)) {
  19542. if (isset($parent['fontsize'])) {
  19543. $hbz = (($parent['fontsize'] / $this->k) * $this->cell_height_ratio);
  19544. } else {
  19545. $hbz = $this->FontSize * $this->cell_height_ratio;
  19546. }
  19547. }
  19548. }
  19549. // Opening tag
  19550. switch($tag['value']) {
  19551. case 'table': {
  19552. $cp = 0;
  19553. $cs = 0;
  19554. $dom[$key]['rowspans'] = array();
  19555. if (!isset($dom[$key]['attribute']['nested']) OR ($dom[$key]['attribute']['nested'] != 'true')) {
  19556. // set table header
  19557. if (!$this->empty_string($dom[$key]['thead'])) {
  19558. // set table header
  19559. $this->thead = $dom[$key]['thead'];
  19560. if (!isset($this->theadMargins) OR (empty($this->theadMargins))) {
  19561. $this->theadMargins = array();
  19562. $this->theadMargins['cell_padding'] = $this->cell_padding;
  19563. $this->theadMargins['lmargin'] = $this->lMargin;
  19564. $this->theadMargins['rmargin'] = $this->rMargin;
  19565. $this->theadMargins['page'] = $this->page;
  19566. }
  19567. }
  19568. }
  19569. // store current margins and page
  19570. $dom[$key]['old_cell_padding'] = $this->cell_padding;
  19571. if (isset($tag['attribute']['cellpadding'])) {
  19572. $pad = $this->getHTMLUnitToUnits($tag['attribute']['cellpadding'], 1, 'px');
  19573. $this->SetCellPadding($pad);
  19574. }
  19575. if (isset($tag['attribute']['cellspacing'])) {
  19576. $cs = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
  19577. }
  19578. $prev_y = $this->y;
  19579. if ($this->checkPageBreak(((2 * $cp) + (2 * $cs) + $this->lasth), '', false) OR ($this->y < $prev_y)) {
  19580. $this->inthead = true;
  19581. // add a page (or trig AcceptPageBreak() for multicolumn mode)
  19582. $this->checkPageBreak($this->PageBreakTrigger + 1);
  19583. }
  19584. break;
  19585. }
  19586. case 'tr': {
  19587. // array of columns positions
  19588. $dom[$key]['cellpos'] = array();
  19589. break;
  19590. }
  19591. case 'hr': {
  19592. if ((isset($tag['height'])) AND ($tag['height'] != '')) {
  19593. $hrHeight = $this->getHTMLUnitToUnits($tag['height'], 1, 'px');
  19594. } else {
  19595. $hrHeight = $this->GetLineWidth();
  19596. }
  19597. $this->addHTMLVertSpace($hbz, ($hrHeight / 2), $cell, $firsttag);
  19598. $x = $this->GetX();
  19599. $y = $this->GetY();
  19600. $wtmp = $this->w - $this->lMargin - $this->rMargin;
  19601. if ($cell) {
  19602. $wtmp -= ($this->cell_padding['L'] + $this->cell_padding['R']);
  19603. }
  19604. if ((isset($tag['width'])) AND ($tag['width'] != '')) {
  19605. $hrWidth = $this->getHTMLUnitToUnits($tag['width'], $wtmp, 'px');
  19606. } else {
  19607. $hrWidth = $wtmp;
  19608. }
  19609. $prevlinewidth = $this->GetLineWidth();
  19610. $this->SetLineWidth($hrHeight);
  19611. $this->Line($x, $y, $x + $hrWidth, $y);
  19612. $this->SetLineWidth($prevlinewidth);
  19613. $this->addHTMLVertSpace(($hrHeight / 2), 0, $cell, !isset($dom[($key + 1)]));
  19614. break;
  19615. }
  19616. case 'a': {
  19617. if (array_key_exists('href', $tag['attribute'])) {
  19618. $this->HREF['url'] = $tag['attribute']['href'];
  19619. }
  19620. break;
  19621. }
  19622. case 'img': {
  19623. if (isset($tag['attribute']['src'])) {
  19624. // replace relative path with real server path
  19625. if (($tag['attribute']['src'][0] == '/') AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
  19626. $findroot = strpos($tag['attribute']['src'], $_SERVER['DOCUMENT_ROOT']);
  19627. if (($findroot === false) OR ($findroot > 1)) {
  19628. $tag['attribute']['src'] = $_SERVER['DOCUMENT_ROOT'].$tag['attribute']['src'];
  19629. }
  19630. }
  19631. $tag['attribute']['src'] = urldecode($tag['attribute']['src']);
  19632. $type = $this->getImageFileType($tag['attribute']['src']);
  19633. $testscrtype = @parse_url($tag['attribute']['src']);
  19634. if (!isset($testscrtype['query']) OR empty($testscrtype['query'])) {
  19635. // convert URL to server path
  19636. $tag['attribute']['src'] = str_replace(K_PATH_URL, K_PATH_MAIN, $tag['attribute']['src']);
  19637. }
  19638. if (!isset($tag['width'])) {
  19639. $tag['width'] = 0;
  19640. }
  19641. if (!isset($tag['height'])) {
  19642. $tag['height'] = 0;
  19643. }
  19644. //if (!isset($tag['attribute']['align'])) {
  19645. // the only alignment supported is "bottom"
  19646. // further development is required for other modes.
  19647. $tag['attribute']['align'] = 'bottom';
  19648. //}
  19649. switch($tag['attribute']['align']) {
  19650. case 'top': {
  19651. $align = 'T';
  19652. break;
  19653. }
  19654. case 'middle': {
  19655. $align = 'M';
  19656. break;
  19657. }
  19658. case 'bottom': {
  19659. $align = 'B';
  19660. break;
  19661. }
  19662. default: {
  19663. $align = 'B';
  19664. break;
  19665. }
  19666. }
  19667. $prevy = $this->y;
  19668. $xpos = $this->x;
  19669. // eliminate marker spaces
  19670. if (isset($dom[($key - 1)])) {
  19671. if (($dom[($key - 1)]['value'] == ' ') OR (isset($dom[($key - 1)]['trimmed_space']))) {
  19672. $xpos -= $this->GetStringWidth(chr(32));
  19673. } elseif ($this->rtl AND $dom[($key - 1)]['value'] == ' ') {
  19674. $xpos += (2 * $this->GetStringWidth(chr(32)));
  19675. }
  19676. }
  19677. $imglink = '';
  19678. if (isset($this->HREF['url']) AND !$this->empty_string($this->HREF['url'])) {
  19679. $imglink = $this->HREF['url'];
  19680. if ($imglink{0} == '#') {
  19681. // convert url to internal link
  19682. $lnkdata = explode(',', $imglink);
  19683. if (isset($lnkdata[0])) {
  19684. $page = intval(substr($lnkdata[0], 1));
  19685. if (empty($page) OR ($page <= 0)) {
  19686. $page = $this->page;
  19687. }
  19688. if (isset($lnkdata[1]) AND (strlen($lnkdata[1]) > 0)) {
  19689. $lnky = floatval($lnkdata[1]);
  19690. } else {
  19691. $lnky = 0;
  19692. }
  19693. $imglink = $this->AddLink();
  19694. $this->SetLink($imglink, $lnky, $page);
  19695. }
  19696. }
  19697. }
  19698. $border = 0;
  19699. if (isset($tag['border']) AND !empty($tag['border'])) {
  19700. // currently only support 1 (frame) or a combination of 'LTRB'
  19701. $border = $tag['border'];
  19702. }
  19703. $iw = '';
  19704. if (isset($tag['width'])) {
  19705. $iw = $this->getHTMLUnitToUnits($tag['width'], 1, 'px', false);
  19706. }
  19707. $ih = '';
  19708. if (isset($tag['height'])) {
  19709. $ih = $this->getHTMLUnitToUnits($tag['height'], 1, 'px', false);
  19710. }
  19711. if (($type == 'eps') OR ($type == 'ai')) {
  19712. $this->ImageEps($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, $imglink, true, $align, '', $border, true);
  19713. } elseif ($type == 'svg') {
  19714. $this->ImageSVG($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, $imglink, $align, '', $border, true);
  19715. } else {
  19716. $this->Image($tag['attribute']['src'], $xpos, $this->y, $iw, $ih, '', $imglink, $align, false, 300, '', false, false, $border, false, false, true);
  19717. }
  19718. switch($align) {
  19719. case 'T': {
  19720. $this->y = $prevy;
  19721. break;
  19722. }
  19723. case 'M': {
  19724. $this->y = (($this->img_rb_y + $prevy - ($tag['fontsize'] / $this->k)) / 2) ;
  19725. break;
  19726. }
  19727. case 'B': {
  19728. $this->y = $this->img_rb_y - ($tag['fontsize'] / $this->k);
  19729. break;
  19730. }
  19731. }
  19732. }
  19733. break;
  19734. }
  19735. case 'dl': {
  19736. ++$this->listnum;
  19737. if ($this->listnum == 1) {
  19738. $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
  19739. } else {
  19740. $this->addHTMLVertSpace(0, 0, $cell, $firsttag);
  19741. }
  19742. break;
  19743. }
  19744. case 'dt': {
  19745. $this->addHTMLVertSpace($hbz, 0, $cell, $firsttag);
  19746. break;
  19747. }
  19748. case 'dd': {
  19749. if ($this->rtl) {
  19750. $this->rMargin += $this->listindent;
  19751. } else {
  19752. $this->lMargin += $this->listindent;
  19753. }
  19754. ++$this->listindentlevel;
  19755. $this->addHTMLVertSpace($hbz, 0, $cell, $firsttag);
  19756. break;
  19757. }
  19758. case 'ul':
  19759. case 'ol': {
  19760. ++$this->listnum;
  19761. if ($tag['value'] == 'ol') {
  19762. $this->listordered[$this->listnum] = true;
  19763. } else {
  19764. $this->listordered[$this->listnum] = false;
  19765. }
  19766. if (isset($tag['attribute']['start'])) {
  19767. $this->listcount[$this->listnum] = intval($tag['attribute']['start']) - 1;
  19768. } else {
  19769. $this->listcount[$this->listnum] = 0;
  19770. }
  19771. if ($this->rtl) {
  19772. $this->rMargin += $this->listindent;
  19773. $this->x -= $this->listindent;
  19774. } else {
  19775. $this->lMargin += $this->listindent;
  19776. $this->x += $this->listindent;
  19777. }
  19778. ++$this->listindentlevel;
  19779. if ($this->listnum == 1) {
  19780. $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
  19781. } else {
  19782. $this->addHTMLVertSpace(0, 0, $cell, $firsttag);
  19783. }
  19784. break;
  19785. }
  19786. case 'li': {
  19787. $this->addHTMLVertSpace($hbz, 0, $cell, $firsttag);
  19788. if ($this->listordered[$this->listnum]) {
  19789. // ordered item
  19790. if (isset($parent['attribute']['type']) AND !$this->empty_string($parent['attribute']['type'])) {
  19791. $this->lispacer = $parent['attribute']['type'];
  19792. } elseif (isset($parent['listtype']) AND !$this->empty_string($parent['listtype'])) {
  19793. $this->lispacer = $parent['listtype'];
  19794. } elseif (isset($this->lisymbol) AND !$this->empty_string($this->lisymbol)) {
  19795. $this->lispacer = $this->lisymbol;
  19796. } else {
  19797. $this->lispacer = '#';
  19798. }
  19799. ++$this->listcount[$this->listnum];
  19800. if (isset($tag['attribute']['value'])) {
  19801. $this->listcount[$this->listnum] = intval($tag['attribute']['value']);
  19802. }
  19803. } else {
  19804. // unordered item
  19805. if (isset($parent['attribute']['type']) AND !$this->empty_string($parent['attribute']['type'])) {
  19806. $this->lispacer = $parent['attribute']['type'];
  19807. } elseif (isset($parent['listtype']) AND !$this->empty_string($parent['listtype'])) {
  19808. $this->lispacer = $parent['listtype'];
  19809. } elseif (isset($this->lisymbol) AND !$this->empty_string($this->lisymbol)) {
  19810. $this->lispacer = $this->lisymbol;
  19811. } else {
  19812. $this->lispacer = '!';
  19813. }
  19814. }
  19815. break;
  19816. }
  19817. case 'blockquote': {
  19818. if ($this->rtl) {
  19819. $this->rMargin += $this->listindent;
  19820. } else {
  19821. $this->lMargin += $this->listindent;
  19822. }
  19823. ++$this->listindentlevel;
  19824. $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
  19825. break;
  19826. }
  19827. case 'br': {
  19828. $this->addHTMLVertSpace($hbz, 0, $cell, $firsttag);
  19829. break;
  19830. }
  19831. case 'div': {
  19832. $this->addHTMLVertSpace($hbz, 0, $cell, $firsttag);
  19833. break;
  19834. }
  19835. case 'p': {
  19836. $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
  19837. break;
  19838. }
  19839. case 'pre': {
  19840. $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
  19841. $this->premode = true;
  19842. break;
  19843. }
  19844. case 'sup': {
  19845. $this->SetXY($this->GetX(), $this->GetY() - ((0.7 * $this->FontSizePt) / $this->k));
  19846. break;
  19847. }
  19848. case 'sub': {
  19849. $this->SetXY($this->GetX(), $this->GetY() + ((0.3 * $this->FontSizePt) / $this->k));
  19850. break;
  19851. }
  19852. case 'h1':
  19853. case 'h2':
  19854. case 'h3':
  19855. case 'h4':
  19856. case 'h5':
  19857. case 'h6': {
  19858. $this->addHTMLVertSpace($hbz, $hb, $cell, $firsttag);
  19859. break;
  19860. }
  19861. // Form fields (since 4.8.000 - 2009-09-07)
  19862. case 'form': {
  19863. if (isset($tag['attribute']['action'])) {
  19864. $this->form_action = $tag['attribute']['action'];
  19865. } else {
  19866. $this->form_action = K_PATH_URL.$_SERVER['SCRIPT_NAME'];
  19867. }
  19868. if (isset($tag['attribute']['enctype'])) {
  19869. $this->form_enctype = $tag['attribute']['enctype'];
  19870. } else {
  19871. $this->form_enctype = 'application/x-www-form-urlencoded';
  19872. }
  19873. if (isset($tag['attribute']['method'])) {
  19874. $this->form_mode = $tag['attribute']['method'];
  19875. } else {
  19876. $this->form_mode = 'post';
  19877. }
  19878. break;
  19879. }
  19880. case 'input': {
  19881. if (isset($tag['attribute']['name']) AND !$this->empty_string($tag['attribute']['name'])) {
  19882. $name = $tag['attribute']['name'];
  19883. } else {
  19884. break;
  19885. }
  19886. $prop = array();
  19887. $opt = array();
  19888. if (isset($tag['attribute']['readonly']) AND !$this->empty_string($tag['attribute']['readonly'])) {
  19889. $prop['readonly'] = true;
  19890. }
  19891. if (isset($tag['attribute']['value']) AND !$this->empty_string($tag['attribute']['value'])) {
  19892. $value = $tag['attribute']['value'];
  19893. }
  19894. if (isset($tag['attribute']['maxlength']) AND !$this->empty_string($tag['attribute']['maxlength'])) {
  19895. $opt['maxlen'] = intval($tag['attribute']['value']);
  19896. }
  19897. $h = $this->FontSize * $this->cell_height_ratio;
  19898. if (isset($tag['attribute']['size']) AND !$this->empty_string($tag['attribute']['size'])) {
  19899. $w = intval($tag['attribute']['size']) * $this->GetStringWidth(chr(32)) * 2;
  19900. } else {
  19901. $w = $h;
  19902. }
  19903. if (isset($tag['attribute']['checked']) AND (($tag['attribute']['checked'] == 'checked') OR ($tag['attribute']['checked'] == 'true'))) {
  19904. $checked = true;
  19905. } else {
  19906. $checked = false;
  19907. }
  19908. switch ($tag['attribute']['type']) {
  19909. case 'text': {
  19910. if (isset($value)) {
  19911. $opt['v'] = $value;
  19912. }
  19913. $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
  19914. break;
  19915. }
  19916. case 'password': {
  19917. if (isset($value)) {
  19918. $opt['v'] = $value;
  19919. }
  19920. $prop['password'] = 'true';
  19921. $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
  19922. break;
  19923. }
  19924. case 'checkbox': {
  19925. $this->CheckBox($name, $w, $checked, $prop, $opt, $value, '', '', false);
  19926. break;
  19927. }
  19928. case 'radio': {
  19929. $this->RadioButton($name, $w, $prop, $opt, $value, $checked, '', '', false);
  19930. break;
  19931. }
  19932. case 'submit': {
  19933. $w = $this->GetStringWidth($value) * 1.5;
  19934. $h *= 1.6;
  19935. $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
  19936. $action = array();
  19937. $action['S'] = 'SubmitForm';
  19938. $action['F'] = $this->form_action;
  19939. if ($this->form_enctype != 'FDF') {
  19940. $action['Flags'] = array('ExportFormat');
  19941. }
  19942. if ($this->form_mode == 'get') {
  19943. $action['Flags'] = array('GetMethod');
  19944. }
  19945. $this->Button($name, $w, $h, $value, $action, $prop, $opt, '', '', false);
  19946. break;
  19947. }
  19948. case 'reset': {
  19949. $w = $this->GetStringWidth($value) * 1.5;
  19950. $h *= 1.6;
  19951. $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
  19952. $this->Button($name, $w, $h, $value, array('S'=>'ResetForm'), $prop, $opt, '', '', false);
  19953. break;
  19954. }
  19955. case 'file': {
  19956. $prop['fileSelect'] = 'true';
  19957. $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
  19958. if (!isset($value)) {
  19959. $value = '*';
  19960. }
  19961. $w = $this->GetStringWidth($value) * 2;
  19962. $h *= 1.2;
  19963. $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
  19964. $jsaction = 'var f=this.getField(\''.$name.'\'); f.browseForFileToSubmit();';
  19965. $this->Button('FB_'.$name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
  19966. break;
  19967. }
  19968. case 'hidden': {
  19969. if (isset($value)) {
  19970. $opt['v'] = $value;
  19971. }
  19972. $opt['f'] = array('invisible', 'hidden');
  19973. $this->TextField($name, 0, 0, $prop, $opt, '', '', false);
  19974. break;
  19975. }
  19976. case 'image': {
  19977. // THIS TYPE MUST BE FIXED
  19978. if (isset($tag['attribute']['src']) AND !$this->empty_string($tag['attribute']['src'])) {
  19979. $img = $tag['attribute']['src'];
  19980. } else {
  19981. break;
  19982. }
  19983. $value = 'img';
  19984. //$opt['mk'] = array('i'=>$img, 'tp'=>1, 'if'=>array('sw'=>'A', 's'=>'A', 'fb'=>false));
  19985. if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
  19986. $jsaction = $tag['attribute']['onclick'];
  19987. } else {
  19988. $jsaction = '';
  19989. }
  19990. $this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
  19991. break;
  19992. }
  19993. case 'button': {
  19994. $w = $this->GetStringWidth($value) * 1.5;
  19995. $h *= 1.6;
  19996. $prop = array('lineWidth'=>1, 'borderStyle'=>'beveled', 'fillColor'=>array(196, 196, 196), 'strokeColor'=>array(255, 255, 255));
  19997. if (isset($tag['attribute']['onclick']) AND !empty($tag['attribute']['onclick'])) {
  19998. $jsaction = $tag['attribute']['onclick'];
  19999. } else {
  20000. $jsaction = '';
  20001. }
  20002. $this->Button($name, $w, $h, $value, $jsaction, $prop, $opt, '', '', false);
  20003. break;
  20004. }
  20005. }
  20006. break;
  20007. }
  20008. case 'textarea': {
  20009. $prop = array();
  20010. $opt = array();
  20011. if (isset($tag['attribute']['readonly']) AND !$this->empty_string($tag['attribute']['readonly'])) {
  20012. $prop['readonly'] = true;
  20013. }
  20014. if (isset($tag['attribute']['name']) AND !$this->empty_string($tag['attribute']['name'])) {
  20015. $name = $tag['attribute']['name'];
  20016. } else {
  20017. break;
  20018. }
  20019. if (isset($tag['attribute']['value']) AND !$this->empty_string($tag['attribute']['value'])) {
  20020. $opt['v'] = $tag['attribute']['value'];
  20021. }
  20022. if (isset($tag['attribute']['cols']) AND !$this->empty_string($tag['attribute']['cols'])) {
  20023. $w = intval($tag['attribute']['cols']) * $this->GetStringWidth(chr(32)) * 2;
  20024. } else {
  20025. $w = 40;
  20026. }
  20027. if (isset($tag['attribute']['rows']) AND !$this->empty_string($tag['attribute']['rows'])) {
  20028. $h = intval($tag['attribute']['rows']) * $this->FontSize * $this->cell_height_ratio;
  20029. } else {
  20030. $h = 10;
  20031. }
  20032. $prop['multiline'] = 'true';
  20033. $this->TextField($name, $w, $h, $prop, $opt, '', '', false);
  20034. break;
  20035. }
  20036. case 'select': {
  20037. $h = $this->FontSize * $this->cell_height_ratio;
  20038. if (isset($tag['attribute']['size']) AND !$this->empty_string($tag['attribute']['size'])) {
  20039. $h *= ($tag['attribute']['size'] + 1);
  20040. }
  20041. $prop = array();
  20042. $opt = array();
  20043. if (isset($tag['attribute']['name']) AND !$this->empty_string($tag['attribute']['name'])) {
  20044. $name = $tag['attribute']['name'];
  20045. } else {
  20046. break;
  20047. }
  20048. $w = 0;
  20049. if (isset($tag['attribute']['opt']) AND !$this->empty_string($tag['attribute']['opt'])) {
  20050. $options = explode('#!NwL!#', $tag['attribute']['opt']);
  20051. $values = array();
  20052. foreach ($options as $val) {
  20053. if (strpos($val, '#!TaB!#') !== false) {
  20054. $opts = explode('#!TaB!#', $val);
  20055. $values[] = $opts;
  20056. $w = max($w, $this->GetStringWidth($opts[1]));
  20057. } else {
  20058. $values[] = $val;
  20059. $w = max($w, $this->GetStringWidth($val));
  20060. }
  20061. }
  20062. } else {
  20063. break;
  20064. }
  20065. $w *= 2;
  20066. if (isset($tag['attribute']['multiple']) AND ($tag['attribute']['multiple']='multiple')) {
  20067. $prop['multipleSelection'] = 'true';
  20068. $this->ListBox($name, $w, $h, $values, $prop, $opt, '', '', false);
  20069. } else {
  20070. $this->ComboBox($name, $w, $h, $values, $prop, $opt, '', '', false);
  20071. }
  20072. break;
  20073. }
  20074. case 'tcpdf': {
  20075. if (defined('K_TCPDF_CALLS_IN_HTML') AND (K_TCPDF_CALLS_IN_HTML === true)) {
  20076. // Special tag used to call TCPDF methods
  20077. if (isset($tag['attribute']['method'])) {
  20078. $tcpdf_method = $tag['attribute']['method'];
  20079. if (method_exists($this, $tcpdf_method)) {
  20080. if (isset($tag['attribute']['params']) AND (!empty($tag['attribute']['params']))) {
  20081. $params = unserialize(urldecode($tag['attribute']['params']));
  20082. call_user_func_array(array($this, $tcpdf_method), $params);
  20083. } else {
  20084. $this->$tcpdf_method();
  20085. }
  20086. $this->newline = true;
  20087. }
  20088. }
  20089. }
  20090. break;
  20091. }
  20092. default: {
  20093. break;
  20094. }
  20095. }
  20096. // define tags that support borders and background colors
  20097. $bordertags = array('blockquote','br','dd','dl','div','dt','h1','h2','h3','h4','h5','h6','hr','li','ol','p','pre','ul','tcpdf','table');
  20098. if (in_array($tag['value'], $bordertags)) {
  20099. // set border
  20100. $dom[$key]['borderposition'] = $this->getBorderStartPosition();
  20101. }
  20102. if ($dom[$key]['self'] AND isset($dom[$key]['attribute']['pagebreakafter'])) {
  20103. $pba = $dom[$key]['attribute']['pagebreakafter'];
  20104. // check for pagebreak
  20105. if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
  20106. // add a page (or trig AcceptPageBreak() for multicolumn mode)
  20107. $this->checkPageBreak($this->PageBreakTrigger + 1);
  20108. }
  20109. if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
  20110. OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
  20111. // add a page (or trig AcceptPageBreak() for multicolumn mode)
  20112. $this->checkPageBreak($this->PageBreakTrigger + 1);
  20113. }
  20114. }
  20115. }
  20116. /**
  20117. * Process closing tags.
  20118. * @param array $dom html dom array
  20119. * @param int $key current element id
  20120. * @param boolean $cell if true add the default left (or right if RTL) padding to each new line (default false).
  20121. * @param int $maxbottomliney maximum y value of current line
  20122. * @access protected
  20123. */
  20124. protected function closeHTMLTagHandler(&$dom, $key, $cell, $maxbottomliney=0) {
  20125. $tag = $dom[$key];
  20126. $parent = $dom[($dom[$key]['parent'])];
  20127. $lasttag = ((!isset($dom[($key + 1)])) OR ((!isset($dom[($key + 2)])) AND ($dom[($key + 1)]['value'] == 'marker')));
  20128. $in_table_head = false;
  20129. // maximum x position (used to draw borders)
  20130. if ($this->rtl) {
  20131. $xmax = $this->w;
  20132. } else {
  20133. $xmax = 0;
  20134. }
  20135. if ($tag['block']) {
  20136. $hbz = 0; // distance from y to line bottom
  20137. $hb = 0; // vertical space between block tags
  20138. // calculate vertical space for block tags
  20139. if (isset($this->tagvspaces[$tag['value']][1]['h']) AND ($this->tagvspaces[$tag['value']][1]['h'] >= 0)) {
  20140. $pre_h = $this->tagvspaces[$tag['value']][1]['h'];
  20141. } elseif (isset($parent['fontsize'])) {
  20142. $pre_h = (($parent['fontsize'] / $this->k) * $this->cell_height_ratio);
  20143. } else {
  20144. $pre_h = $this->FontSize * $this->cell_height_ratio;
  20145. }
  20146. if (isset($this->tagvspaces[$tag['value']][1]['n'])) {
  20147. $n = $this->tagvspaces[$tag['value']][1]['n'];
  20148. } elseif (preg_match('/[h][0-9]/', $tag['value']) > 0) {
  20149. $n = 0.6;
  20150. } else {
  20151. $n = 1;
  20152. }
  20153. $hb = ($n * $pre_h);
  20154. if ($this->y < $maxbottomliney) {
  20155. $hbz = ($maxbottomliney - $this->y);
  20156. }
  20157. }
  20158. // Closing tag
  20159. switch($tag['value']) {
  20160. case 'tr': {
  20161. $table_el = $dom[($dom[$key]['parent'])]['parent'];
  20162. if (!isset($parent['endy'])) {
  20163. $dom[($dom[$key]['parent'])]['endy'] = $this->y;
  20164. $parent['endy'] = $this->y;
  20165. }
  20166. if (!isset($parent['endpage'])) {
  20167. $dom[($dom[$key]['parent'])]['endpage'] = $this->page;
  20168. $parent['endpage'] = $this->page;
  20169. }
  20170. if (!isset($parent['endcolumn'])) {
  20171. $dom[($dom[$key]['parent'])]['endcolumn'] = $this->current_column;
  20172. $parent['endcolumn'] = $this->current_column;
  20173. }
  20174. // update row-spanned cells
  20175. if (isset($dom[$table_el]['rowspans'])) {
  20176. foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
  20177. $dom[$table_el]['rowspans'][$k]['rowspan'] -= 1;
  20178. if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
  20179. if (($dom[$table_el]['rowspans'][$k]['endpage'] == $parent['endpage']) AND ($dom[$table_el]['rowspans'][$k]['endcolumn'] == $parent['endcolumn'])) {
  20180. $dom[($dom[$key]['parent'])]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $parent['endy']);
  20181. } elseif (($dom[$table_el]['rowspans'][$k]['endpage'] > $parent['endpage']) OR ($dom[$table_el]['rowspans'][$k]['endcolumn'] > $parent['endcolumn'])) {
  20182. $dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
  20183. $dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
  20184. $dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
  20185. }
  20186. }
  20187. }
  20188. // report new endy and endpage to the rowspanned cells
  20189. foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
  20190. if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
  20191. $dom[$table_el]['rowspans'][$k]['endpage'] = max($dom[$table_el]['rowspans'][$k]['endpage'], $dom[($dom[$key]['parent'])]['endpage']);
  20192. $dom[($dom[$key]['parent'])]['endpage'] = $dom[$table_el]['rowspans'][$k]['endpage'];
  20193. $dom[$table_el]['rowspans'][$k]['endcolumn'] = max($dom[$table_el]['rowspans'][$k]['endcolumn'], $dom[($dom[$key]['parent'])]['endcolumn']);
  20194. $dom[($dom[$key]['parent'])]['endcolumn'] = $dom[$table_el]['rowspans'][$k]['endcolumn'];
  20195. $dom[$table_el]['rowspans'][$k]['endy'] = max($dom[$table_el]['rowspans'][$k]['endy'], $dom[($dom[$key]['parent'])]['endy']);
  20196. $dom[($dom[$key]['parent'])]['endy'] = $dom[$table_el]['rowspans'][$k]['endy'];
  20197. }
  20198. }
  20199. // update remaining rowspanned cells
  20200. foreach ($dom[$table_el]['rowspans'] as $k => $trwsp) {
  20201. if ($dom[$table_el]['rowspans'][$k]['rowspan'] == 0) {
  20202. $dom[$table_el]['rowspans'][$k]['endpage'] = $dom[($dom[$key]['parent'])]['endpage'];
  20203. $dom[$table_el]['rowspans'][$k]['endcolumn'] = $dom[($dom[$key]['parent'])]['endcolumn'];
  20204. $dom[$table_el]['rowspans'][$k]['endy'] = $dom[($dom[$key]['parent'])]['endy'];
  20205. }
  20206. }
  20207. }
  20208. $this->setPage($dom[($dom[$key]['parent'])]['endpage']);
  20209. if ($this->num_columns > 1) {
  20210. $this->selectColumn($dom[($dom[$key]['parent'])]['endcolumn']);
  20211. }
  20212. $this->y = $dom[($dom[$key]['parent'])]['endy'];
  20213. if (isset($dom[$table_el]['attribute']['cellspacing'])) {
  20214. $cellspacing = $this->getHTMLUnitToUnits($dom[$table_el]['attribute']['cellspacing'], 1, 'px');
  20215. $this->y += $cellspacing;
  20216. }
  20217. $this->Ln(0, $cell);
  20218. if ($this->current_column == $parent['startcolumn']) {
  20219. $this->x = $parent['startx'];
  20220. }
  20221. // account for booklet mode
  20222. if ($this->page > $parent['startpage']) {
  20223. if (($this->rtl) AND ($this->pagedim[$this->page]['orm'] != $this->pagedim[$parent['startpage']]['orm'])) {
  20224. $this->x -= ($this->pagedim[$this->page]['orm'] - $this->pagedim[$parent['startpage']]['orm']);
  20225. } elseif ((!$this->rtl) AND ($this->pagedim[$this->page]['olm'] != $this->pagedim[$parent['startpage']]['olm'])) {
  20226. $this->x += ($this->pagedim[$this->page]['olm'] - $this->pagedim[$parent['startpage']]['olm']);
  20227. }
  20228. }
  20229. break;
  20230. }
  20231. case 'tablehead':
  20232. // closing tag used for the thead part
  20233. $in_table_head = true;
  20234. $this->inthead = false;
  20235. case 'table': {
  20236. $table_el = $parent;
  20237. // set default border
  20238. if (isset($table_el['attribute']['border']) AND ($table_el['attribute']['border'] > 0)) {
  20239. // set default border
  20240. $border = array('LTRB' => array('width' => $this->getCSSBorderWidth($table_el['attribute']['border']), 'cap'=>'square', 'join'=>'miter', 'dash'=> 0, 'color'=>array(0,0,0)));
  20241. } else {
  20242. $border = 0;
  20243. }
  20244. $default_border = $border;
  20245. if (isset($table_el['attribute']['cellspacing'])) {
  20246. $cellspacing = $this->getHTMLUnitToUnits($table_el['attribute']['cellspacing'], 1, 'px');
  20247. } else {
  20248. $cellspacing = 0;
  20249. }
  20250. // fix bottom line alignment of last line before page break
  20251. foreach ($dom[($dom[$key]['parent'])]['trids'] as $j => $trkey) {
  20252. // update row-spanned cells
  20253. if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
  20254. foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
  20255. if ($trwsp['trid'] == $trkey) {
  20256. $dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] -= 1;
  20257. }
  20258. if (isset($prevtrkey) AND ($trwsp['trid'] == $prevtrkey) AND ($trwsp['mrowspan'] >= 0)) {
  20259. $dom[($dom[$key]['parent'])]['rowspans'][$k]['trid'] = $trkey;
  20260. }
  20261. }
  20262. }
  20263. if (isset($prevtrkey) AND ($dom[$trkey]['startpage'] > $dom[$prevtrkey]['endpage'])) {
  20264. $pgendy = $this->pagedim[$dom[$prevtrkey]['endpage']]['hk'] - $this->pagedim[$dom[$prevtrkey]['endpage']]['bm'];
  20265. $dom[$prevtrkey]['endy'] = $pgendy;
  20266. // update row-spanned cells
  20267. if (isset($dom[($dom[$key]['parent'])]['rowspans'])) {
  20268. foreach ($dom[($dom[$key]['parent'])]['rowspans'] as $k => $trwsp) {
  20269. if (($trwsp['trid'] == $trkey) AND ($trwsp['mrowspan'] > 1) AND ($trwsp['endpage'] == $dom[$prevtrkey]['endpage'])) {
  20270. $dom[($dom[$key]['parent'])]['rowspans'][$k]['endy'] = $pgendy;
  20271. $dom[($dom[$key]['parent'])]['rowspans'][$k]['mrowspan'] = -1;
  20272. }
  20273. }
  20274. }
  20275. }
  20276. $prevtrkey = $trkey;
  20277. $table_el = $dom[($dom[$key]['parent'])];
  20278. }
  20279. // for each row
  20280. unset($xmax);
  20281. foreach ($table_el['trids'] as $j => $trkey) {
  20282. $parent = $dom[$trkey];
  20283. if (!isset($xmax)) {
  20284. $xmax = $parent['cellpos'][(count($parent['cellpos']) - 1)]['endx'];
  20285. }
  20286. // for each cell on the row
  20287. foreach ($parent['cellpos'] as $k => $cellpos) {
  20288. if (isset($cellpos['rowspanid']) AND ($cellpos['rowspanid'] >= 0)) {
  20289. $cellpos['startx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['startx'];
  20290. $cellpos['endx'] = $table_el['rowspans'][($cellpos['rowspanid'])]['endx'];
  20291. $endy = $table_el['rowspans'][($cellpos['rowspanid'])]['endy'];
  20292. $startpage = $table_el['rowspans'][($cellpos['rowspanid'])]['startpage'];
  20293. $endpage = $table_el['rowspans'][($cellpos['rowspanid'])]['endpage'];
  20294. $startcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['startcolumn'];
  20295. $endcolumn = $table_el['rowspans'][($cellpos['rowspanid'])]['endcolumn'];
  20296. } else {
  20297. $endy = $parent['endy'];
  20298. $startpage = $parent['startpage'];
  20299. $endpage = $parent['endpage'];
  20300. $startcolumn = $parent['startcolumn'];
  20301. $endcolumn = $parent['endcolumn'];
  20302. }
  20303. if ($this->num_columns == 0) {
  20304. $this->num_columns = 1;
  20305. }
  20306. if (isset($cellpos['border'])) {
  20307. $border = $cellpos['border'];
  20308. }
  20309. if (isset($cellpos['bgcolor']) AND ($cellpos['bgcolor']) !== false) {
  20310. $this->SetFillColorArray($cellpos['bgcolor']);
  20311. $fill = true;
  20312. } else {
  20313. $fill = false;
  20314. }
  20315. $x = $cellpos['startx'];
  20316. $y = $parent['starty'];
  20317. $starty = $y;
  20318. $w = abs($cellpos['endx'] - $cellpos['startx']);
  20319. // get border modes
  20320. $border_start = $this->getBorderMode($border, $position='start');
  20321. $border_end = $this->getBorderMode($border, $position='end');
  20322. $border_middle = $this->getBorderMode($border, $position='middle');
  20323. // design borders around HTML cells.
  20324. for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
  20325. $ccode = '';
  20326. $this->setPage($page);
  20327. if ($this->num_columns < 2) {
  20328. // single-column mode
  20329. $this->x = $x;
  20330. $this->y = $this->tMargin;
  20331. }
  20332. // account for margin changes
  20333. if ($page > $startpage) {
  20334. if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
  20335. $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
  20336. } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
  20337. $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
  20338. }
  20339. }
  20340. if ($startpage == $endpage) { // single page
  20341. $deltacol = 0;
  20342. $deltath = 0;
  20343. for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
  20344. $this->selectColumn($column);
  20345. if ($startcolumn == $endcolumn) { // single column
  20346. $cborder = $border;
  20347. $h = $endy - $parent['starty'];
  20348. $this->y = $y;
  20349. $this->x = $x;
  20350. } elseif ($column == $startcolumn) { // first column
  20351. $cborder = $border_start;
  20352. $this->y = $starty;
  20353. $this->x = $x;
  20354. $h = $this->h - $this->y - $this->bMargin;
  20355. if ($this->rtl) {
  20356. $deltacol = $this->x + $this->rMargin - $this->w;
  20357. } else {
  20358. $deltacol = $this->x - $this->lMargin;
  20359. }
  20360. } elseif ($column == $endcolumn) { // end column
  20361. $cborder = $border_end;
  20362. if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
  20363. $this->y = $this->columns[$column]['th']['\''.$page.'\''];
  20364. }
  20365. $this->x += $deltacol;
  20366. $h = $endy - $this->y;
  20367. } else { // middle column
  20368. $cborder = $border_middle;
  20369. if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
  20370. $this->y = $this->columns[$column]['th']['\''.$page.'\''];
  20371. }
  20372. $this->x += $deltacol;
  20373. $h = $this->h - $this->y - $this->bMargin;
  20374. }
  20375. $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
  20376. } // end for each column
  20377. } elseif ($page == $startpage) { // first page
  20378. $deltacol = 0;
  20379. $deltath = 0;
  20380. for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
  20381. $this->selectColumn($column);
  20382. if ($column == $startcolumn) { // first column
  20383. $cborder = $border_start;
  20384. $this->y = $starty;
  20385. $this->x = $x;
  20386. $h = $this->h - $this->y - $this->bMargin;
  20387. if ($this->rtl) {
  20388. $deltacol = $this->x + $this->rMargin - $this->w;
  20389. } else {
  20390. $deltacol = $this->x - $this->lMargin;
  20391. }
  20392. } else { // middle column
  20393. $cborder = $border_middle;
  20394. if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
  20395. $this->y = $this->columns[$column]['th']['\''.$page.'\''];
  20396. }
  20397. $this->x += $deltacol;
  20398. $h = $this->h - $this->y - $this->bMargin;
  20399. }
  20400. $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
  20401. } // end for each column
  20402. } elseif ($page == $endpage) { // last page
  20403. $deltacol = 0;
  20404. $deltath = 0;
  20405. for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
  20406. $this->selectColumn($column);
  20407. if ($column == $endcolumn) { // end column
  20408. $cborder = $border_end;
  20409. if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
  20410. $this->y = $this->columns[$column]['th']['\''.$page.'\''];
  20411. }
  20412. $this->x += $deltacol;
  20413. $h = $endy - $this->y;
  20414. } else { // middle column
  20415. $cborder = $border_middle;
  20416. if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
  20417. $this->y = $this->columns[$column]['th']['\''.$page.'\''];
  20418. }
  20419. $this->x += $deltacol;
  20420. $h = $this->h - $this->y - $this->bMargin;
  20421. }
  20422. $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
  20423. } // end for each column
  20424. } else { // middle page
  20425. $deltacol = 0;
  20426. $deltath = 0;
  20427. for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
  20428. $this->selectColumn($column);
  20429. $cborder = $border_middle;
  20430. if (isset($this->columns[$column]['th']['\''.$page.'\''])) {
  20431. $this->y = $this->columns[$column]['th']['\''.$page.'\''];
  20432. }
  20433. $this->x += $deltacol;
  20434. $h = $this->h - $this->y - $this->bMargin;
  20435. $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
  20436. } // end for each column
  20437. }
  20438. if ($cborder OR $fill) {
  20439. // draw border and fill
  20440. if ($this->inxobj) {
  20441. // we are inside an XObject template
  20442. if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
  20443. $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
  20444. $pagemark = &$this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
  20445. } else {
  20446. $pagemark = &$this->xobjects[$this->xobjid]['intmrk'];
  20447. }
  20448. $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
  20449. $pstart = substr($pagebuff, 0, $pagemark);
  20450. $pend = substr($pagebuff, $pagemark);
  20451. $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
  20452. $pagemark += strlen($ccode);
  20453. } else {
  20454. // draw border and fill
  20455. if (end($this->transfmrk[$this->page]) !== false) {
  20456. $pagemarkkey = key($this->transfmrk[$this->page]);
  20457. $pagemark = &$this->transfmrk[$this->page][$pagemarkkey];
  20458. } elseif ($this->InFooter) {
  20459. $pagemark = &$this->footerpos[$this->page];
  20460. } else {
  20461. $pagemark = &$this->intmrk[$this->page];
  20462. }
  20463. $pagebuff = $this->getPageBuffer($this->page);
  20464. $pstart = substr($pagebuff, 0, $pagemark);
  20465. $pend = substr($pagebuff, $pagemark);
  20466. $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
  20467. $pagemark += strlen($ccode);
  20468. }
  20469. }
  20470. } // end for each page
  20471. // restore default border
  20472. $border = $default_border;
  20473. } // end for each cell on the row
  20474. if (isset($table_el['attribute']['cellspacing'])) {
  20475. $cellspacing = $this->getHTMLUnitToUnits($table_el['attribute']['cellspacing'], 1, 'px');
  20476. $this->y += $cellspacing;
  20477. }
  20478. $this->Ln(0, $cell);
  20479. $this->x = $parent['startx'];
  20480. if ($endpage > $startpage) {
  20481. if (($this->rtl) AND ($this->pagedim[$endpage]['orm'] != $this->pagedim[$startpage]['orm'])) {
  20482. $this->x += ($this->pagedim[$endpage]['orm'] - $this->pagedim[$startpage]['orm']);
  20483. } elseif ((!$this->rtl) AND ($this->pagedim[$endpage]['olm'] != $this->pagedim[$startpage]['olm'])) {
  20484. $this->x += ($this->pagedim[$endpage]['olm'] - $this->pagedim[$startpage]['olm']);
  20485. }
  20486. }
  20487. }
  20488. if (!$in_table_head) { // we are not inside a thead section
  20489. $this->cell_padding = $table_el['old_cell_padding'];
  20490. // reset row height
  20491. $this->resetLastH();
  20492. if (($this->page == ($this->numpages - 1)) AND ($this->pageopen[$this->numpages]) AND ($this->emptypagemrk[$this->numpages] == $this->pagelen[$this->numpages])) {
  20493. // remove last blank page
  20494. $this->deletePage($this->numpages);
  20495. }
  20496. if (isset($this->theadMargins['top'])) {
  20497. // restore top margin
  20498. $this->tMargin = $this->theadMargins['top'];
  20499. $this->pagedim[$this->page]['tm'] = $this->tMargin;
  20500. }
  20501. if (!isset($table_el['attribute']['nested']) OR ($table_el['attribute']['nested'] != 'true')) {
  20502. // reset main table header
  20503. $this->thead = '';
  20504. $this->theadMargins = array();
  20505. }
  20506. }
  20507. $parent = $table_el;
  20508. break;
  20509. }
  20510. case 'a': {
  20511. $this->HREF = '';
  20512. break;
  20513. }
  20514. case 'sup': {
  20515. $this->SetXY($this->GetX(), $this->GetY() + ((0.7 * $parent['fontsize']) / $this->k));
  20516. break;
  20517. }
  20518. case 'sub': {
  20519. $this->SetXY($this->GetX(), $this->GetY() - ((0.3 * $parent['fontsize'])/$this->k));
  20520. break;
  20521. }
  20522. case 'div': {
  20523. $this->addHTMLVertSpace($hbz, 0, $cell, false, $lasttag);
  20524. break;
  20525. }
  20526. case 'blockquote': {
  20527. if ($this->rtl) {
  20528. $this->rMargin -= $this->listindent;
  20529. } else {
  20530. $this->lMargin -= $this->listindent;
  20531. }
  20532. --$this->listindentlevel;
  20533. $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
  20534. break;
  20535. }
  20536. case 'p': {
  20537. $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
  20538. break;
  20539. }
  20540. case 'pre': {
  20541. $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
  20542. $this->premode = false;
  20543. break;
  20544. }
  20545. case 'dl': {
  20546. --$this->listnum;
  20547. if ($this->listnum <= 0) {
  20548. $this->listnum = 0;
  20549. $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
  20550. } else {
  20551. $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
  20552. }
  20553. $this->resetLastH();
  20554. break;
  20555. }
  20556. case 'dt': {
  20557. $this->lispacer = '';
  20558. $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
  20559. break;
  20560. }
  20561. case 'dd': {
  20562. $this->lispacer = '';
  20563. if ($this->rtl) {
  20564. $this->rMargin -= $this->listindent;
  20565. } else {
  20566. $this->lMargin -= $this->listindent;
  20567. }
  20568. --$this->listindentlevel;
  20569. $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
  20570. break;
  20571. }
  20572. case 'ul':
  20573. case 'ol': {
  20574. --$this->listnum;
  20575. $this->lispacer = '';
  20576. if ($this->rtl) {
  20577. $this->rMargin -= $this->listindent;
  20578. } else {
  20579. $this->lMargin -= $this->listindent;
  20580. }
  20581. --$this->listindentlevel;
  20582. if ($this->listnum <= 0) {
  20583. $this->listnum = 0;
  20584. $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
  20585. } else {
  20586. $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
  20587. }
  20588. $this->resetLastH();
  20589. break;
  20590. }
  20591. case 'li': {
  20592. $this->lispacer = '';
  20593. $this->addHTMLVertSpace(0, 0, $cell, false, $lasttag);
  20594. break;
  20595. }
  20596. case 'h1':
  20597. case 'h2':
  20598. case 'h3':
  20599. case 'h4':
  20600. case 'h5':
  20601. case 'h6': {
  20602. $this->addHTMLVertSpace($hbz, $hb, $cell, false, $lasttag);
  20603. break;
  20604. }
  20605. // Form fields (since 4.8.000 - 2009-09-07)
  20606. case 'form': {
  20607. $this->form_action = '';
  20608. $this->form_enctype = 'application/x-www-form-urlencoded';
  20609. break;
  20610. }
  20611. default : {
  20612. break;
  20613. }
  20614. }
  20615. // draw border and background (if any)
  20616. $this->drawHTMLTagBorder($parent, $xmax);
  20617. if (isset($dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'])) {
  20618. $pba = $dom[($dom[$key]['parent'])]['attribute']['pagebreakafter'];
  20619. // check for pagebreak
  20620. if (($pba == 'true') OR ($pba == 'left') OR ($pba == 'right')) {
  20621. // add a page (or trig AcceptPageBreak() for multicolumn mode)
  20622. $this->checkPageBreak($this->PageBreakTrigger + 1);
  20623. }
  20624. if ((($pba == 'left') AND (((!$this->rtl) AND (($this->page % 2) == 0)) OR (($this->rtl) AND (($this->page % 2) != 0))))
  20625. OR (($pba == 'right') AND (((!$this->rtl) AND (($this->page % 2) != 0)) OR (($this->rtl) AND (($this->page % 2) == 0))))) {
  20626. // add a page (or trig AcceptPageBreak() for multicolumn mode)
  20627. $this->checkPageBreak($this->PageBreakTrigger + 1);
  20628. }
  20629. }
  20630. $this->tmprtl = false;
  20631. }
  20632. /**
  20633. * Add vertical spaces if needed.
  20634. * @param string $hbz Distance between current y and line bottom.
  20635. * @param string $hb The height of the break.
  20636. * @param boolean $cell if true add the default left (or right if RTL) padding to each new line (default false).
  20637. * @param boolean $firsttag set to true when the tag is the first.
  20638. * @param boolean $lasttag set to true when the tag is the last.
  20639. * @access protected
  20640. */
  20641. protected function addHTMLVertSpace($hbz=0, $hb=0, $cell=false, $firsttag=false, $lasttag=false) {
  20642. if ($firsttag) {
  20643. $this->Ln(0, $cell);
  20644. $this->htmlvspace = 0;
  20645. return;
  20646. }
  20647. if ($lasttag) {
  20648. $this->Ln($hbz, $cell);
  20649. $this->htmlvspace = 0;
  20650. return;
  20651. }
  20652. if ($hb < $this->htmlvspace) {
  20653. $hd = 0;
  20654. } else {
  20655. $hd = $hb - $this->htmlvspace;
  20656. $this->htmlvspace = $hb;
  20657. }
  20658. $this->Ln(($hbz + $hd), $cell);
  20659. }
  20660. /**
  20661. * Return the starting coordinates to draw an html border
  20662. * @return array containing top-left border coordinates
  20663. * @access protected
  20664. * @since 5.7.000 (2010-08-03)
  20665. */
  20666. protected function getBorderStartPosition() {
  20667. if ($this->rtl) {
  20668. $xmax = $this->lMargin;
  20669. } else {
  20670. $xmax = $this->w - $this->rMargin;
  20671. }
  20672. return array('page' => $this->page, 'column' => $this->current_column, 'x' => $this->x, 'y' => $this->y, 'xmax' => $xmax);
  20673. }
  20674. /**
  20675. * Draw an HTML block border and fill
  20676. * @param array $tag array of tag properties.
  20677. * @param int $xmax end X coordinate for border.
  20678. * @access protected
  20679. * @since 5.7.000 (2010-08-03)
  20680. */
  20681. protected function drawHTMLTagBorder($tag, $xmax) {
  20682. if (!isset($tag['borderposition'])) {
  20683. // nothing to draw
  20684. return;
  20685. }
  20686. $prev_x = $this->x;
  20687. $prev_y = $this->y;
  20688. $prev_lasth = $this->lasth;
  20689. $border = 0;
  20690. $fill = false;
  20691. $this->lasth = 0;
  20692. if (isset($tag['border']) AND !empty($tag['border'])) {
  20693. // get border style
  20694. $border = $tag['border'];
  20695. if (!$this->empty_string($this->thead) AND (!$this->inthead)) {
  20696. // border for table header
  20697. $border = $this->getBorderMode($border, $position='middle');
  20698. }
  20699. }
  20700. if (isset($tag['bgcolor']) AND ($tag['bgcolor'] !== false)) {
  20701. // get background color
  20702. $old_bgcolor = $this->bgcolor;
  20703. $this->SetFillColorArray($tag['bgcolor']);
  20704. $fill = true;
  20705. }
  20706. if (!$border AND !$fill) {
  20707. // nothing to draw
  20708. return;
  20709. }
  20710. if (isset($tag['attribute']['cellspacing'])) {
  20711. $cellspacing = $this->getHTMLUnitToUnits($tag['attribute']['cellspacing'], 1, 'px');
  20712. } else {
  20713. $cellspacing = 0;
  20714. }
  20715. if (($tag['value'] != 'table') AND (is_array($border)) AND (!empty($border))) {
  20716. // draw the border externally respect the sqare edge.
  20717. $border['mode'] = 'ext';
  20718. }
  20719. if ($this->rtl) {
  20720. if ($xmax >= $tag['borderposition']['x']) {
  20721. $xmax = $tag['borderposition']['xmax'];
  20722. }
  20723. $w = ($tag['borderposition']['x'] - $xmax);
  20724. } else {
  20725. if ($xmax <= $tag['borderposition']['x']) {
  20726. $xmax = $tag['borderposition']['xmax'];
  20727. }
  20728. $w = ($xmax - $tag['borderposition']['x']);
  20729. }
  20730. if ($w <= 0) {
  20731. return;
  20732. }
  20733. $w += $cellspacing;
  20734. $startpage = $tag['borderposition']['page'];
  20735. $startcolumn = $tag['borderposition']['column'];
  20736. $x = $tag['borderposition']['x'];
  20737. $y = $tag['borderposition']['y'];
  20738. $endpage = $this->page;
  20739. $starty = $tag['borderposition']['y'] - $cellspacing;
  20740. $currentY = $this->y;
  20741. $this->x = $x;
  20742. // get latest column
  20743. $endcolumn = $this->current_column;
  20744. if ($this->num_columns == 0) {
  20745. $this->num_columns = 1;
  20746. }
  20747. // get border modes
  20748. $border_start = $this->getBorderMode($border, $position='start');
  20749. $border_end = $this->getBorderMode($border, $position='end');
  20750. $border_middle = $this->getBorderMode($border, $position='middle');
  20751. // design borders around HTML cells.
  20752. for ($page = $startpage; $page <= $endpage; ++$page) { // for each page
  20753. $ccode = '';
  20754. $this->setPage($page);
  20755. if ($this->num_columns < 2) {
  20756. // single-column mode
  20757. $this->x = $x;
  20758. $this->y = $this->tMargin;
  20759. }
  20760. // account for margin changes
  20761. if ($page > $startpage) {
  20762. if (($this->rtl) AND ($this->pagedim[$page]['orm'] != $this->pagedim[$startpage]['orm'])) {
  20763. $this->x -= ($this->pagedim[$page]['orm'] - $this->pagedim[$startpage]['orm']);
  20764. } elseif ((!$this->rtl) AND ($this->pagedim[$page]['olm'] != $this->pagedim[$startpage]['olm'])) {
  20765. $this->x += ($this->pagedim[$page]['olm'] - $this->pagedim[$startpage]['olm']);
  20766. }
  20767. }
  20768. if ($startpage == $endpage) {
  20769. // single page
  20770. for ($column = $startcolumn; $column <= $endcolumn; ++$column) { // for each column
  20771. $this->selectColumn($column);
  20772. if ($startcolumn == $endcolumn) { // single column
  20773. $cborder = $border;
  20774. $h = ($currentY - $y) + $cellspacing;
  20775. $this->y = $starty;
  20776. } elseif ($column == $startcolumn) { // first column
  20777. $cborder = $border_start;
  20778. $this->y = $starty;
  20779. $h = $this->h - $this->y - $this->bMargin;
  20780. } elseif ($column == $endcolumn) { // end column
  20781. $cborder = $border_end;
  20782. $h = $currentY - $this->y;
  20783. } else { // middle column
  20784. $cborder = $border_middle;
  20785. $h = $this->h - $this->y - $this->bMargin;
  20786. }
  20787. $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
  20788. } // end for each column
  20789. } elseif ($page == $startpage) { // first page
  20790. for ($column = $startcolumn; $column < $this->num_columns; ++$column) { // for each column
  20791. $this->selectColumn($column);
  20792. if ($column == $startcolumn) { // first column
  20793. $cborder = $border_start;
  20794. $this->y = $starty;
  20795. $h = $this->h - $this->y - $this->bMargin;
  20796. } else { // middle column
  20797. $cborder = $border_middle;
  20798. $h = $this->h - $this->y - $this->bMargin;
  20799. }
  20800. $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
  20801. } // end for each column
  20802. } elseif ($page == $endpage) { // last page
  20803. for ($column = 0; $column <= $endcolumn; ++$column) { // for each column
  20804. $this->selectColumn($column);
  20805. if ($column == $endcolumn) {
  20806. // end column
  20807. $cborder = $border_end;
  20808. $h = $currentY - $this->y;
  20809. } else {
  20810. // middle column
  20811. $cborder = $border_middle;
  20812. $h = $this->h - $this->y - $this->bMargin;
  20813. }
  20814. $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
  20815. } // end for each column
  20816. } else { // middle page
  20817. for ($column = 0; $column < $this->num_columns; ++$column) { // for each column
  20818. $this->selectColumn($column);
  20819. $cborder = $border_middle;
  20820. $h = $this->h - $this->y - $this->bMargin;
  20821. $ccode .= $this->getCellCode($w, $h, '', $cborder, 1, '', $fill, '', 0, true)."\n";
  20822. } // end for each column
  20823. }
  20824. if ($cborder OR $fill) {
  20825. // draw border and fill
  20826. if ($this->inxobj) {
  20827. // we are inside an XObject template
  20828. if (end($this->xobjects[$this->xobjid]['transfmrk']) !== false) {
  20829. $pagemarkkey = key($this->xobjects[$this->xobjid]['transfmrk']);
  20830. $pagemark = &$this->xobjects[$this->xobjid]['transfmrk'][$pagemarkkey];
  20831. } else {
  20832. $pagemark = &$this->xobjects[$this->xobjid]['intmrk'];
  20833. }
  20834. $pagebuff = $this->xobjects[$this->xobjid]['outdata'];
  20835. $pstart = substr($pagebuff, 0, $pagemark);
  20836. $pend = substr($pagebuff, $pagemark);
  20837. $this->xobjects[$this->xobjid]['outdata'] = $pstart.$ccode.$pend;
  20838. $pagemark += strlen($ccode);
  20839. } else {
  20840. if (end($this->transfmrk[$this->page]) !== false) {
  20841. $pagemarkkey = key($this->transfmrk[$this->page]);
  20842. $pagemark = &$this->transfmrk[$this->page][$pagemarkkey];
  20843. } elseif ($this->InFooter) {
  20844. $pagemark = &$this->footerpos[$this->page];
  20845. } else {
  20846. $pagemark = &$this->intmrk[$this->page];
  20847. }
  20848. $pagebuff = $this->getPageBuffer($this->page);
  20849. $pstart = substr($pagebuff, 0, $this->bordermrk[$this->page]);
  20850. $pend = substr($pagebuff, $this->bordermrk[$this->page]);
  20851. $this->setPageBuffer($this->page, $pstart.$ccode.$pend);
  20852. $offsetlen = strlen($ccode);
  20853. $this->bordermrk[$this->page] += $offsetlen;
  20854. $this->cntmrk[$this->page] += $offsetlen;
  20855. $pagemark += $offsetlen;
  20856. }
  20857. }
  20858. } // end for each page
  20859. if (isset($old_bgcolor)) {
  20860. // restore background color
  20861. $this->SetFillColorArray($old_bgcolor);
  20862. }
  20863. // restore pointer position
  20864. $this->x = $prev_x;
  20865. $this->y = $prev_y;
  20866. $this->lasth = $prev_lasth;
  20867. }
  20868. /**
  20869. * Set the default bullet to be used as LI bullet symbol
  20870. * @param string $symbol character or string to be used (legal values are: '' = automatic, '!' = auto bullet, '#' = auto numbering, 'disc', 'disc', 'circle', 'square', '1', 'decimal', 'decimal-leading-zero', 'i', 'lower-roman', 'I', 'upper-roman', 'a', 'lower-alpha', 'lower-latin', 'A', 'upper-alpha', 'upper-latin', 'lower-greek')
  20871. * @access public
  20872. * @since 4.0.028 (2008-09-26)
  20873. */
  20874. public function setLIsymbol($symbol='!') {
  20875. $symbol = strtolower($symbol);
  20876. switch ($symbol) {
  20877. case '!' :
  20878. case '#' :
  20879. case 'disc' :
  20880. case 'circle' :
  20881. case 'square' :
  20882. case '1':
  20883. case 'decimal':
  20884. case 'decimal-leading-zero':
  20885. case 'i':
  20886. case 'lower-roman':
  20887. case 'I':
  20888. case 'upper-roman':
  20889. case 'a':
  20890. case 'lower-alpha':
  20891. case 'lower-latin':
  20892. case 'A':
  20893. case 'upper-alpha':
  20894. case 'upper-latin':
  20895. case 'lower-greek': {
  20896. $this->lisymbol = $symbol;
  20897. break;
  20898. }
  20899. default : {
  20900. $this->lisymbol = '';
  20901. }
  20902. }
  20903. }
  20904. /**
  20905. * Set the booklet mode for double-sided pages.
  20906. * @param boolean $booklet true set the booklet mode on, false otherwise.
  20907. * @param float $inner Inner page margin.
  20908. * @param float $outer Outer page margin.
  20909. * @access public
  20910. * @since 4.2.000 (2008-10-29)
  20911. */
  20912. public function SetBooklet($booklet=true, $inner=-1, $outer=-1) {
  20913. $this->booklet = $booklet;
  20914. if ($inner >= 0) {
  20915. $this->lMargin = $inner;
  20916. }
  20917. if ($outer >= 0) {
  20918. $this->rMargin = $outer;
  20919. }
  20920. }
  20921. /**
  20922. * Swap the left and right margins.
  20923. * @param boolean $reverse if true swap left and right margins.
  20924. * @access protected
  20925. * @since 4.2.000 (2008-10-29)
  20926. */
  20927. protected function swapMargins($reverse=true) {
  20928. if ($reverse) {
  20929. // swap left and right margins
  20930. $mtemp = $this->original_lMargin;
  20931. $this->original_lMargin = $this->original_rMargin;
  20932. $this->original_rMargin = $mtemp;
  20933. $deltam = $this->original_lMargin - $this->original_rMargin;
  20934. $this->lMargin += $deltam;
  20935. $this->rMargin -= $deltam;
  20936. }
  20937. }
  20938. /**
  20939. * Set the vertical spaces for HTML tags.
  20940. * The array must have the following structure (example):
  20941. * $tagvs = array('h1' => array(0 => array('h' => '', 'n' => 2), 1 => array('h' => 1.3, 'n' => 1)));
  20942. * The first array level contains the tag names,
  20943. * the second level contains 0 for opening tags or 1 for closing tags,
  20944. * the third level contains the vertical space unit (h) and the number spaces to add (n).
  20945. * If the h parameter is not specified, default values are used.
  20946. * @param array $tagvs array of tags and relative vertical spaces.
  20947. * @access public
  20948. * @since 4.2.001 (2008-10-30)
  20949. */
  20950. public function setHtmlVSpace($tagvs) {
  20951. $this->tagvspaces = $tagvs;
  20952. }
  20953. /**
  20954. * Set custom width for list indentation.
  20955. * @param float $width width of the indentation. Use negative value to disable it.
  20956. * @access public
  20957. * @since 4.2.007 (2008-11-12)
  20958. */
  20959. public function setListIndentWidth($width) {
  20960. return $this->customlistindent = floatval($width);
  20961. }
  20962. /**
  20963. * Set the top/bottom cell sides to be open or closed when the cell cross the page.
  20964. * @param boolean $isopen if true keeps the top/bottom border open for the cell sides that cross the page.
  20965. * @access public
  20966. * @since 4.2.010 (2008-11-14)
  20967. */
  20968. public function setOpenCell($isopen) {
  20969. $this->opencell = $isopen;
  20970. }
  20971. /**
  20972. * Set the color and font style for HTML links.
  20973. * @param array $color RGB array of colors
  20974. * @param string $fontstyle additional font styles to add
  20975. * @access public
  20976. * @since 4.4.003 (2008-12-09)
  20977. */
  20978. public function setHtmlLinksStyle($color=array(0,0,255), $fontstyle='U') {
  20979. $this->htmlLinkColorArray = $color;
  20980. $this->htmlLinkFontStyle = $fontstyle;
  20981. }
  20982. /**
  20983. * Convert HTML string containing value and unit of measure to user's units or points.
  20984. * @param string $htmlval string containing values and unit
  20985. * @param string $refsize reference value in points
  20986. * @param string $defaultunit default unit (can be one of the following: %, em, ex, px, in, mm, pc, pt).
  20987. * @param boolean $point if true returns points, otherwise returns value in user's units
  20988. * @return float value in user's unit or point if $points=true
  20989. * @access public
  20990. * @since 4.4.004 (2008-12-10)
  20991. */
  20992. public function getHTMLUnitToUnits($htmlval, $refsize=1, $defaultunit='px', $points=false) {
  20993. $supportedunits = array('%', 'em', 'ex', 'px', 'in', 'cm', 'mm', 'pc', 'pt');
  20994. $retval = 0;
  20995. $value = 0;
  20996. $unit = 'px';
  20997. $k = $this->k;
  20998. if ($points) {
  20999. $k = 1;
  21000. }
  21001. if (in_array($defaultunit, $supportedunits)) {
  21002. $unit = $defaultunit;
  21003. }
  21004. if (is_numeric($htmlval)) {
  21005. $value = floatval($htmlval);
  21006. } elseif (preg_match('/([0-9\.\-\+]+)/', $htmlval, $mnum)) {
  21007. $value = floatval($mnum[1]);
  21008. if (preg_match('/([a-z%]+)/', $htmlval, $munit)) {
  21009. if (in_array($munit[1], $supportedunits)) {
  21010. $unit = $munit[1];
  21011. }
  21012. }
  21013. }
  21014. switch ($unit) {
  21015. // percentage
  21016. case '%': {
  21017. $retval = (($value * $refsize) / 100);
  21018. break;
  21019. }
  21020. // relative-size
  21021. case 'em': {
  21022. $retval = ($value * $refsize);
  21023. break;
  21024. }
  21025. // height of lower case 'x' (about half the font-size)
  21026. case 'ex': {
  21027. $retval = $value * ($refsize / 2);
  21028. break;
  21029. }
  21030. // absolute-size
  21031. case 'in': {
  21032. $retval = ($value * $this->dpi) / $k;
  21033. break;
  21034. }
  21035. // centimeters
  21036. case 'cm': {
  21037. $retval = ($value / 2.54 * $this->dpi) / $k;
  21038. break;
  21039. }
  21040. // millimeters
  21041. case 'mm': {
  21042. $retval = ($value / 25.4 * $this->dpi) / $k;
  21043. break;
  21044. }
  21045. // one pica is 12 points
  21046. case 'pc': {
  21047. $retval = ($value * 12) / $k;
  21048. break;
  21049. }
  21050. // points
  21051. case 'pt': {
  21052. $retval = $value / $k;
  21053. break;
  21054. }
  21055. // pixels
  21056. case 'px': {
  21057. $retval = $this->pixelsToUnits($value);
  21058. break;
  21059. }
  21060. }
  21061. return $retval;
  21062. }
  21063. /**
  21064. * Returns the Roman representation of an integer number
  21065. * @param int number to convert
  21066. * @return string roman representation of the specified number
  21067. * @access public
  21068. * @since 4.4.004 (2008-12-10)
  21069. */
  21070. public function intToRoman($number) {
  21071. $roman = '';
  21072. while ($number >= 1000) {
  21073. $roman .= 'M';
  21074. $number -= 1000;
  21075. }
  21076. while ($number >= 900) {
  21077. $roman .= 'CM';
  21078. $number -= 900;
  21079. }
  21080. while ($number >= 500) {
  21081. $roman .= 'D';
  21082. $number -= 500;
  21083. }
  21084. while ($number >= 400) {
  21085. $roman .= 'CD';
  21086. $number -= 400;
  21087. }
  21088. while ($number >= 100) {
  21089. $roman .= 'C';
  21090. $number -= 100;
  21091. }
  21092. while ($number >= 90) {
  21093. $roman .= 'XC';
  21094. $number -= 90;
  21095. }
  21096. while ($number >= 50) {
  21097. $roman .= 'L';
  21098. $number -= 50;
  21099. }
  21100. while ($number >= 40) {
  21101. $roman .= 'XL';
  21102. $number -= 40;
  21103. }
  21104. while ($number >= 10) {
  21105. $roman .= 'X';
  21106. $number -= 10;
  21107. }
  21108. while ($number >= 9) {
  21109. $roman .= 'IX';
  21110. $number -= 9;
  21111. }
  21112. while ($number >= 5) {
  21113. $roman .= 'V';
  21114. $number -= 5;
  21115. }
  21116. while ($number >= 4) {
  21117. $roman .= 'IV';
  21118. $number -= 4;
  21119. }
  21120. while ($number >= 1) {
  21121. $roman .= 'I';
  21122. --$number;
  21123. }
  21124. return $roman;
  21125. }
  21126. /**
  21127. * Output an HTML list bullet or ordered item symbol
  21128. * @param int $listdepth list nesting level
  21129. * @param string $listtype type of list
  21130. * @param float $size current font size
  21131. * @access protected
  21132. * @since 4.4.004 (2008-12-10)
  21133. */
  21134. protected function putHtmlListBullet($listdepth, $listtype='', $size=10) {
  21135. $size /= $this->k;
  21136. $fill = '';
  21137. $color = $this->fgcolor;
  21138. $width = 0;
  21139. $textitem = '';
  21140. $tmpx = $this->x;
  21141. $lspace = $this->GetStringWidth(' ');
  21142. if ($listtype == '^') {
  21143. // special symbol used for avoid justification of rect bullet
  21144. $this->lispacer = '';
  21145. return;
  21146. } elseif ($listtype == '!') {
  21147. // set default list type for unordered list
  21148. $deftypes = array('disc', 'circle', 'square');
  21149. $listtype = $deftypes[($listdepth - 1) % 3];
  21150. } elseif ($listtype == '#') {
  21151. // set default list type for ordered list
  21152. $listtype = 'decimal';
  21153. }
  21154. switch ($listtype) {
  21155. // unordered types
  21156. case 'none': {
  21157. break;
  21158. }
  21159. case 'disc': {
  21160. $fill = 'F';
  21161. }
  21162. case 'circle': {
  21163. $fill .= 'D';
  21164. $r = $size / 6;
  21165. $lspace += (2 * $r);
  21166. if ($this->rtl) {
  21167. $this->x += $lspace;
  21168. } else {
  21169. $this->x -= $lspace;
  21170. }
  21171. $this->Circle(($this->x + $r), ($this->y + ($this->lasth / 2)), $r, 0, 360, $fill, array('color'=>$color), $color, 8);
  21172. break;
  21173. }
  21174. case 'square': {
  21175. $l = $size / 3;
  21176. $lspace += $l;
  21177. if ($this->rtl) {;
  21178. $this->x += $lspace;
  21179. } else {
  21180. $this->x -= $lspace;
  21181. }
  21182. $this->Rect($this->x, ($this->y + (($this->lasth - $l)/ 2)), $l, $l, 'F', array(), $color);
  21183. break;
  21184. }
  21185. // ordered types
  21186. // $this->listcount[$this->listnum];
  21187. // $textitem
  21188. case '1':
  21189. case 'decimal': {
  21190. $textitem = $this->listcount[$this->listnum];
  21191. break;
  21192. }
  21193. case 'decimal-leading-zero': {
  21194. $textitem = sprintf('%02d', $this->listcount[$this->listnum]);
  21195. break;
  21196. }
  21197. case 'i':
  21198. case 'lower-roman': {
  21199. $textitem = strtolower($this->intToRoman($this->listcount[$this->listnum]));
  21200. break;
  21201. }
  21202. case 'I':
  21203. case 'upper-roman': {
  21204. $textitem = $this->intToRoman($this->listcount[$this->listnum]);
  21205. break;
  21206. }
  21207. case 'a':
  21208. case 'lower-alpha':
  21209. case 'lower-latin': {
  21210. $textitem = chr(97 + $this->listcount[$this->listnum] - 1);
  21211. break;
  21212. }
  21213. case 'A':
  21214. case 'upper-alpha':
  21215. case 'upper-latin': {
  21216. $textitem = chr(65 + $this->listcount[$this->listnum] - 1);
  21217. break;
  21218. }
  21219. case 'lower-greek': {
  21220. $textitem = $this->unichr(945 + $this->listcount[$this->listnum] - 1);
  21221. break;
  21222. }
  21223. /*
  21224. // Types to be implemented (special handling)
  21225. case 'hebrew': {
  21226. break;
  21227. }
  21228. case 'armenian': {
  21229. break;
  21230. }
  21231. case 'georgian': {
  21232. break;
  21233. }
  21234. case 'cjk-ideographic': {
  21235. break;
  21236. }
  21237. case 'hiragana': {
  21238. break;
  21239. }
  21240. case 'katakana': {
  21241. break;
  21242. }
  21243. case 'hiragana-iroha': {
  21244. break;
  21245. }
  21246. case 'katakana-iroha': {
  21247. break;
  21248. }
  21249. */
  21250. default: {
  21251. $textitem = $this->listcount[$this->listnum];
  21252. }
  21253. }
  21254. if (!$this->empty_string($textitem)) {
  21255. // print ordered item
  21256. if ($this->rtl) {
  21257. $textitem = '.'.$textitem;
  21258. } else {
  21259. $textitem = $textitem.'.';
  21260. }
  21261. $lspace += $this->GetStringWidth($textitem);
  21262. if ($this->rtl) {
  21263. $this->x += $lspace;
  21264. } else {
  21265. $this->x -= $lspace;
  21266. }
  21267. $this->Write($this->lasth, $textitem, '', false, '', false, 0, false);
  21268. }
  21269. $this->x = $tmpx;
  21270. $this->lispacer = '^';
  21271. }
  21272. /**
  21273. * Returns current graphic variables as array.
  21274. * @return array of graphic variables
  21275. * @access protected
  21276. * @since 4.2.010 (2008-11-14)
  21277. */
  21278. protected function getGraphicVars() {
  21279. $grapvars = array(
  21280. 'FontFamily' => $this->FontFamily,
  21281. 'FontStyle' => $this->FontStyle,
  21282. 'FontSizePt' => $this->FontSizePt,
  21283. 'rMargin' => $this->rMargin,
  21284. 'lMargin' => $this->lMargin,
  21285. 'cell_padding' => $this->cell_padding,
  21286. 'cell_margin' => $this->cell_margin,
  21287. 'LineWidth' => $this->LineWidth,
  21288. 'linestyleWidth' => $this->linestyleWidth,
  21289. 'linestyleCap' => $this->linestyleCap,
  21290. 'linestyleJoin' => $this->linestyleJoin,
  21291. 'linestyleDash' => $this->linestyleDash,
  21292. 'textrendermode' => $this->textrendermode,
  21293. 'textstrokewidth' => $this->textstrokewidth,
  21294. 'DrawColor' => $this->DrawColor,
  21295. 'FillColor' => $this->FillColor,
  21296. 'TextColor' => $this->TextColor,
  21297. 'ColorFlag' => $this->ColorFlag,
  21298. 'bgcolor' => $this->bgcolor,
  21299. 'fgcolor' => $this->fgcolor,
  21300. 'htmlvspace' => $this->htmlvspace,
  21301. 'listindent' => $this->listindent,
  21302. 'listindentlevel' => $this->listindentlevel,
  21303. 'listnum' => $this->listnum,
  21304. 'listordered' => $this->listordered,
  21305. 'listcount' => $this->listcount,
  21306. 'lispacer' => $this->lispacer,
  21307. 'cell_height_ratio' => $this->cell_height_ratio,
  21308. 'font_stretching' => $this->font_stretching,
  21309. 'font_spacing' => $this->font_spacing,
  21310. // extended
  21311. 'lasth' => $this->lasth,
  21312. 'tMargin' => $this->tMargin,
  21313. 'bMargin' => $this->bMargin,
  21314. 'AutoPageBreak' => $this->AutoPageBreak,
  21315. 'PageBreakTrigger' => $this->PageBreakTrigger,
  21316. 'x' => $this->x,
  21317. 'y' => $this->y,
  21318. 'w' => $this->w,
  21319. 'h' => $this->h,
  21320. 'wPt' => $this->wPt,
  21321. 'hPt' => $this->hPt,
  21322. 'fwPt' => $this->fwPt,
  21323. 'fhPt' => $this->fhPt,
  21324. 'page' => $this->page,
  21325. 'current_column' => $this->current_column,
  21326. 'num_columns' => $this->num_columns
  21327. );
  21328. return $grapvars;
  21329. }
  21330. /**
  21331. * Set graphic variables.
  21332. * @param array $gvars array of graphic variablesto restore
  21333. * @param boolean $extended if true restore extended graphic variables
  21334. * @access protected
  21335. * @since 4.2.010 (2008-11-14)
  21336. */
  21337. protected function setGraphicVars($gvars, $extended=false) {
  21338. $this->FontFamily = $gvars['FontFamily'];
  21339. $this->FontStyle = $gvars['FontStyle'];
  21340. $this->FontSizePt = $gvars['FontSizePt'];
  21341. $this->rMargin = $gvars['rMargin'];
  21342. $this->lMargin = $gvars['lMargin'];
  21343. $this->cell_padding = $gvars['cell_padding'];
  21344. $this->cell_margin = $gvars['cell_margin'];
  21345. $this->LineWidth = $gvars['LineWidth'];
  21346. $this->linestyleWidth = $gvars['linestyleWidth'];
  21347. $this->linestyleCap = $gvars['linestyleCap'];
  21348. $this->linestyleJoin = $gvars['linestyleJoin'];
  21349. $this->linestyleDash = $gvars['linestyleDash'];
  21350. $this->textrendermode = $gvars['textrendermode'];
  21351. $this->textstrokewidth = $gvars['textstrokewidth'];
  21352. $this->DrawColor = $gvars['DrawColor'];
  21353. $this->FillColor = $gvars['FillColor'];
  21354. $this->TextColor = $gvars['TextColor'];
  21355. $this->ColorFlag = $gvars['ColorFlag'];
  21356. $this->bgcolor = $gvars['bgcolor'];
  21357. $this->fgcolor = $gvars['fgcolor'];
  21358. $this->htmlvspace = $gvars['htmlvspace'];
  21359. $this->listindent = $gvars['listindent'];
  21360. $this->listindentlevel = $gvars['listindentlevel'];
  21361. $this->listnum = $gvars['listnum'];
  21362. $this->listordered = $gvars['listordered'];
  21363. $this->listcount = $gvars['listcount'];
  21364. $this->lispacer = $gvars['lispacer'];
  21365. $this->cell_height_ratio = $gvars['cell_height_ratio'];
  21366. $this->font_stretching = $gvars['font_stretching'];
  21367. $this->font_spacing = $gvars['font_spacing'];
  21368. if ($extended) {
  21369. // restore extended values
  21370. $this->lasth = $gvars['lasth'];
  21371. $this->tMargin = $gvars['tMargin'];
  21372. $this->bMargin = $gvars['bMargin'];
  21373. $this->AutoPageBreak = $gvars['AutoPageBreak'];
  21374. $this->PageBreakTrigger = $gvars['PageBreakTrigger'];
  21375. $this->x = $gvars['x'];
  21376. $this->y = $gvars['y'];
  21377. $this->w = $gvars['w'];
  21378. $this->h = $gvars['h'];
  21379. $this->wPt = $gvars['wPt'];
  21380. $this->hPt = $gvars['hPt'];
  21381. $this->fwPt = $gvars['fwPt'];
  21382. $this->fhPt = $gvars['fhPt'];
  21383. $this->page = $gvars['page'];
  21384. $this->current_column = $gvars['current_column'];
  21385. $this->num_columns = $gvars['num_columns'];
  21386. }
  21387. $this->_out(''.$this->linestyleWidth.' '.$this->linestyleCap.' '.$this->linestyleJoin.' '.$this->linestyleDash.' '.$this->DrawColor.' '.$this->FillColor.'');
  21388. if (!$this->empty_string($this->FontFamily)) {
  21389. $this->SetFont($this->FontFamily, $this->FontStyle, $this->FontSizePt);
  21390. }
  21391. }
  21392. /**
  21393. * Returns a temporary filename for caching object on filesystem.
  21394. * @param string $prefix prefix to add to filename
  21395. * return string filename.
  21396. * @access protected
  21397. * @since 4.5.000 (2008-12-31)
  21398. */
  21399. protected function getObjFilename($name) {
  21400. return tempnam(K_PATH_CACHE, $name.'_');
  21401. }
  21402. /**
  21403. * Writes data to a temporary file on filesystem.
  21404. * @param string $file file name
  21405. * @param mixed $data data to write on file
  21406. * @param boolean $append if true append data, false replace.
  21407. * @access protected
  21408. * @since 4.5.000 (2008-12-31)
  21409. */
  21410. protected function writeDiskCache($filename, $data, $append=false) {
  21411. if ($append) {
  21412. $fmode = 'ab+';
  21413. } else {
  21414. $fmode = 'wb+';
  21415. }
  21416. $f = @fopen($filename, $fmode);
  21417. if (!$f) {
  21418. $this->Error('Unable to write cache file: '.$filename);
  21419. } else {
  21420. fwrite($f, $data);
  21421. fclose($f);
  21422. }
  21423. // update file length (needed for transactions)
  21424. if (!isset($this->cache_file_length['_'.$filename])) {
  21425. $this->cache_file_length['_'.$filename] = strlen($data);
  21426. } else {
  21427. $this->cache_file_length['_'.$filename] += strlen($data);
  21428. }
  21429. }
  21430. /**
  21431. * Read data from a temporary file on filesystem.
  21432. * @param string $file file name
  21433. * @return mixed retrieved data
  21434. * @access protected
  21435. * @since 4.5.000 (2008-12-31)
  21436. */
  21437. protected function readDiskCache($filename) {
  21438. return file_get_contents($filename);
  21439. }
  21440. /**
  21441. * Set buffer content (always append data).
  21442. * @param string $data data
  21443. * @access protected
  21444. * @since 4.5.000 (2009-01-02)
  21445. */
  21446. protected function setBuffer($data) {
  21447. $this->bufferlen += strlen($data);
  21448. if ($this->diskcache) {
  21449. if (!isset($this->buffer) OR $this->empty_string($this->buffer)) {
  21450. $this->buffer = $this->getObjFilename('buffer');
  21451. }
  21452. $this->writeDiskCache($this->buffer, $data, true);
  21453. } else {
  21454. $this->buffer .= $data;
  21455. }
  21456. }
  21457. /**
  21458. * Replace the buffer content
  21459. * @param string $data data
  21460. * @access protected
  21461. * @since 5.5.000 (2010-06-22)
  21462. */
  21463. protected function replaceBuffer($data) {
  21464. $this->bufferlen = strlen($data);
  21465. if ($this->diskcache) {
  21466. if (!isset($this->buffer) OR $this->empty_string($this->buffer)) {
  21467. $this->buffer = $this->getObjFilename('buffer');
  21468. }
  21469. $this->writeDiskCache($this->buffer, $data, false);
  21470. } else {
  21471. $this->buffer = $data;
  21472. }
  21473. }
  21474. /**
  21475. * Get buffer content.
  21476. * @return string buffer content
  21477. * @access protected
  21478. * @since 4.5.000 (2009-01-02)
  21479. */
  21480. protected function getBuffer() {
  21481. if ($this->diskcache) {
  21482. return $this->readDiskCache($this->buffer);
  21483. } else {
  21484. return $this->buffer;
  21485. }
  21486. }
  21487. /**
  21488. * Set page buffer content.
  21489. * @param int $page page number
  21490. * @param string $data page data
  21491. * @param boolean $append if true append data, false replace.
  21492. * @access protected
  21493. * @since 4.5.000 (2008-12-31)
  21494. */
  21495. protected function setPageBuffer($page, $data, $append=false) {
  21496. if ($this->diskcache) {
  21497. if (!isset($this->pages[$page])) {
  21498. $this->pages[$page] = $this->getObjFilename('page'.$page);
  21499. }
  21500. $this->writeDiskCache($this->pages[$page], $data, $append);
  21501. } else {
  21502. if ($append) {
  21503. $this->pages[$page] .= $data;
  21504. } else {
  21505. $this->pages[$page] = $data;
  21506. }
  21507. }
  21508. if ($append AND isset($this->pagelen[$page])) {
  21509. $this->pagelen[$page] += strlen($data);
  21510. } else {
  21511. $this->pagelen[$page] = strlen($data);
  21512. }
  21513. }
  21514. /**
  21515. * Get page buffer content.
  21516. * @param int $page page number
  21517. * @return string page buffer content or false in case of error
  21518. * @access protected
  21519. * @since 4.5.000 (2008-12-31)
  21520. */
  21521. protected function getPageBuffer($page) {
  21522. if ($this->diskcache) {
  21523. return $this->readDiskCache($this->pages[$page]);
  21524. } elseif (isset($this->pages[$page])) {
  21525. return $this->pages[$page];
  21526. }
  21527. return false;
  21528. }
  21529. /**
  21530. * Set image buffer content.
  21531. * @param string $image image key
  21532. * @param array $data image data
  21533. * @access protected
  21534. * @since 4.5.000 (2008-12-31)
  21535. */
  21536. protected function setImageBuffer($image, $data) {
  21537. if ($this->diskcache) {
  21538. if (!isset($this->images[$image])) {
  21539. $this->images[$image] = $this->getObjFilename('image'.$image);
  21540. }
  21541. $this->writeDiskCache($this->images[$image], serialize($data));
  21542. } else {
  21543. $this->images[$image] = $data;
  21544. }
  21545. if (!in_array($image, $this->imagekeys)) {
  21546. $this->imagekeys[] = $image;
  21547. ++$this->numimages;
  21548. }
  21549. }
  21550. /**
  21551. * Set image buffer content for a specified sub-key.
  21552. * @param string $image image key
  21553. * @param string $key image sub-key
  21554. * @param array $data image data
  21555. * @access protected
  21556. * @since 4.5.000 (2008-12-31)
  21557. */
  21558. protected function setImageSubBuffer($image, $key, $data) {
  21559. if (!isset($this->images[$image])) {
  21560. $this->setImageBuffer($image, array());
  21561. }
  21562. if ($this->diskcache) {
  21563. $tmpimg = $this->getImageBuffer($image);
  21564. $tmpimg[$key] = $data;
  21565. $this->writeDiskCache($this->images[$image], serialize($tmpimg));
  21566. } else {
  21567. $this->images[$image][$key] = $data;
  21568. }
  21569. }
  21570. /**
  21571. * Get image buffer content.
  21572. * @param string $image image key
  21573. * @return string image buffer content or false in case of error
  21574. * @access protected
  21575. * @since 4.5.000 (2008-12-31)
  21576. */
  21577. protected function getImageBuffer($image) {
  21578. if ($this->diskcache AND isset($this->images[$image])) {
  21579. return unserialize($this->readDiskCache($this->images[$image]));
  21580. } elseif (isset($this->images[$image])) {
  21581. return $this->images[$image];
  21582. }
  21583. return false;
  21584. }
  21585. /**
  21586. * Set font buffer content.
  21587. * @param string $font font key
  21588. * @param array $data font data
  21589. * @access protected
  21590. * @since 4.5.000 (2009-01-02)
  21591. */
  21592. protected function setFontBuffer($font, $data) {
  21593. if ($this->diskcache) {
  21594. if (!isset($this->fonts[$font])) {
  21595. $this->fonts[$font] = $this->getObjFilename('font');
  21596. }
  21597. $this->writeDiskCache($this->fonts[$font], serialize($data));
  21598. } else {
  21599. $this->fonts[$font] = $data;
  21600. }
  21601. if (!in_array($font, $this->fontkeys)) {
  21602. $this->fontkeys[] = $font;
  21603. // store object ID for current font
  21604. ++$this->n;
  21605. $this->font_obj_ids[$font] = $this->n;
  21606. $this->setFontSubBuffer($font, 'n', $this->n);
  21607. }
  21608. }
  21609. /**
  21610. * Set font buffer content.
  21611. * @param string $font font key
  21612. * @param string $key font sub-key
  21613. * @param array $data font data
  21614. * @access protected
  21615. * @since 4.5.000 (2009-01-02)
  21616. */
  21617. protected function setFontSubBuffer($font, $key, $data) {
  21618. if (!isset($this->fonts[$font])) {
  21619. $this->setFontBuffer($font, array());
  21620. }
  21621. if ($this->diskcache) {
  21622. $tmpfont = $this->getFontBuffer($font);
  21623. $tmpfont[$key] = $data;
  21624. $this->writeDiskCache($this->fonts[$font], serialize($tmpfont));
  21625. } else {
  21626. $this->fonts[$font][$key] = $data;
  21627. }
  21628. }
  21629. /**
  21630. * Get font buffer content.
  21631. * @param string $font font key
  21632. * @return string font buffer content or false in case of error
  21633. * @access protected
  21634. * @since 4.5.000 (2009-01-02)
  21635. */
  21636. protected function getFontBuffer($font) {
  21637. if ($this->diskcache AND isset($this->fonts[$font])) {
  21638. return unserialize($this->readDiskCache($this->fonts[$font]));
  21639. } elseif (isset($this->fonts[$font])) {
  21640. return $this->fonts[$font];
  21641. }
  21642. return false;
  21643. }
  21644. /**
  21645. * Move a page to a previous position.
  21646. * @param int $frompage number of the source page
  21647. * @param int $topage number of the destination page (must be less than $frompage)
  21648. * @return true in case of success, false in case of error.
  21649. * @access public
  21650. * @since 4.5.000 (2009-01-02)
  21651. */
  21652. public function movePage($frompage, $topage) {
  21653. if (($frompage > $this->numpages) OR ($frompage <= $topage)) {
  21654. return false;
  21655. }
  21656. if ($frompage == $this->page) {
  21657. // close the page before moving it
  21658. $this->endPage();
  21659. }
  21660. // move all page-related states
  21661. $tmppage = $this->pages[$frompage];
  21662. $tmppagedim = $this->pagedim[$frompage];
  21663. $tmppagelen = $this->pagelen[$frompage];
  21664. $tmpintmrk = $this->intmrk[$frompage];
  21665. $tmpbordermrk = $this->bordermrk[$frompage];
  21666. $tmpcntmrk = $this->cntmrk[$frompage];
  21667. if (isset($this->footerpos[$frompage])) {
  21668. $tmpfooterpos = $this->footerpos[$frompage];
  21669. }
  21670. if (isset($this->footerlen[$frompage])) {
  21671. $tmpfooterlen = $this->footerlen[$frompage];
  21672. }
  21673. if (isset($this->transfmrk[$frompage])) {
  21674. $tmptransfmrk = $this->transfmrk[$frompage];
  21675. }
  21676. if (isset($this->PageAnnots[$frompage])) {
  21677. $tmpannots = $this->PageAnnots[$frompage];
  21678. }
  21679. if (isset($this->newpagegroup[$frompage])) {
  21680. $tmpnewpagegroup = $this->newpagegroup[$frompage];
  21681. }
  21682. for ($i = $frompage; $i > $topage; --$i) {
  21683. $j = $i - 1;
  21684. // shift pages down
  21685. $this->pages[$i] = $this->pages[$j];
  21686. $this->pagedim[$i] = $this->pagedim[$j];
  21687. $this->pagelen[$i] = $this->pagelen[$j];
  21688. $this->intmrk[$i] = $this->intmrk[$j];
  21689. $this->bordermrk[$i] = $this->bordermrk[$j];
  21690. $this->cntmrk[$i] = $this->cntmrk[$j];
  21691. if (isset($this->footerpos[$j])) {
  21692. $this->footerpos[$i] = $this->footerpos[$j];
  21693. } elseif (isset($this->footerpos[$i])) {
  21694. unset($this->footerpos[$i]);
  21695. }
  21696. if (isset($this->footerlen[$j])) {
  21697. $this->footerlen[$i] = $this->footerlen[$j];
  21698. } elseif (isset($this->footerlen[$i])) {
  21699. unset($this->footerlen[$i]);
  21700. }
  21701. if (isset($this->transfmrk[$j])) {
  21702. $this->transfmrk[$i] = $this->transfmrk[$j];
  21703. } elseif (isset($this->transfmrk[$i])) {
  21704. unset($this->transfmrk[$i]);
  21705. }
  21706. if (isset($this->PageAnnots[$j])) {
  21707. $this->PageAnnots[$i] = $this->PageAnnots[$j];
  21708. } elseif (isset($this->PageAnnots[$i])) {
  21709. unset($this->PageAnnots[$i]);
  21710. }
  21711. if (isset($this->newpagegroup[$j])) {
  21712. $this->newpagegroup[$i] = $this->newpagegroup[$j];
  21713. } elseif (isset($this->newpagegroup[$i])) {
  21714. unset($this->newpagegroup[$i]);
  21715. }
  21716. }
  21717. $this->pages[$topage] = $tmppage;
  21718. $this->pagedim[$topage] = $tmppagedim;
  21719. $this->pagelen[$topage] = $tmppagelen;
  21720. $this->intmrk[$topage] = $tmpintmrk;
  21721. $this->bordermrk[$topage] = $tmpbordermrk;
  21722. $this->cntmrk[$topage] = $tmpcntmrk;
  21723. if (isset($tmpfooterpos)) {
  21724. $this->footerpos[$topage] = $tmpfooterpos;
  21725. } elseif (isset($this->footerpos[$topage])) {
  21726. unset($this->footerpos[$topage]);
  21727. }
  21728. if (isset($tmpfooterlen)) {
  21729. $this->footerlen[$topage] = $tmpfooterlen;
  21730. } elseif (isset($this->footerlen[$topage])) {
  21731. unset($this->footerlen[$topage]);
  21732. }
  21733. if (isset($tmptransfmrk)) {
  21734. $this->transfmrk[$topage] = $tmptransfmrk;
  21735. } elseif (isset($this->transfmrk[$topage])) {
  21736. unset($this->transfmrk[$topage]);
  21737. }
  21738. if (isset($tmpannots)) {
  21739. $this->PageAnnots[$topage] = $tmpannots;
  21740. } elseif (isset($this->PageAnnots[$topage])) {
  21741. unset($this->PageAnnots[$topage]);
  21742. }
  21743. if (isset($tmpnewpagegroup)) {
  21744. $this->newpagegroup[$topage] = $tmpnewpagegroup;
  21745. } elseif (isset($this->newpagegroup[$topage])) {
  21746. unset($this->newpagegroup[$topage]);
  21747. }
  21748. // adjust outlines
  21749. $tmpoutlines = $this->outlines;
  21750. foreach ($tmpoutlines as $key => $outline) {
  21751. if (($outline['p'] >= $topage) AND ($outline['p'] < $frompage)) {
  21752. $this->outlines[$key]['p'] = $outline['p'] + 1;
  21753. } elseif ($outline['p'] == $frompage) {
  21754. $this->outlines[$key]['p'] = $topage;
  21755. }
  21756. }
  21757. // adjust links
  21758. $tmplinks = $this->links;
  21759. foreach ($tmplinks as $key => $link) {
  21760. if (($link[0] >= $topage) AND ($link[0] < $frompage)) {
  21761. $this->links[$key][0] = $link[0] + 1;
  21762. } elseif ($link[0] == $frompage) {
  21763. $this->links[$key][0] = $topage;
  21764. }
  21765. }
  21766. // adjust javascript
  21767. $tmpjavascript = $this->javascript;
  21768. global $jfrompage, $jtopage;
  21769. $jfrompage = $frompage;
  21770. $jtopage = $topage;
  21771. $this->javascript = preg_replace_callback('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/',
  21772. create_function('$matches', 'global $jfrompage, $jtopage;
  21773. $pagenum = intval($matches[3]) + 1;
  21774. if (($pagenum >= $jtopage) AND ($pagenum < $jfrompage)) {
  21775. $newpage = ($pagenum + 1);
  21776. } elseif ($pagenum == $jfrompage) {
  21777. $newpage = $jtopage;
  21778. } else {
  21779. $newpage = $pagenum;
  21780. }
  21781. --$newpage;
  21782. return "this.addField(\'".$matches[1]."\',\'".$matches[2]."\',".$newpage."";'), $tmpjavascript);
  21783. // return to last page
  21784. $this->lastPage(true);
  21785. return true;
  21786. }
  21787. /**
  21788. * Remove the specified page.
  21789. * @param int $page page to remove
  21790. * @return true in case of success, false in case of error.
  21791. * @access public
  21792. * @since 4.6.004 (2009-04-23)
  21793. */
  21794. public function deletePage($page) {
  21795. if (($page < 1) OR ($page > $this->numpages)) {
  21796. return false;
  21797. }
  21798. // delete current page
  21799. unset($this->pages[$page]);
  21800. unset($this->pagedim[$page]);
  21801. unset($this->pagelen[$page]);
  21802. unset($this->intmrk[$page]);
  21803. unset($this->bordermrk[$page]);
  21804. unset($this->cntmrk[$page]);
  21805. if (isset($this->footerpos[$page])) {
  21806. unset($this->footerpos[$page]);
  21807. }
  21808. if (isset($this->footerlen[$page])) {
  21809. unset($this->footerlen[$page]);
  21810. }
  21811. if (isset($this->transfmrk[$page])) {
  21812. unset($this->transfmrk[$page]);
  21813. }
  21814. if (isset($this->PageAnnots[$page])) {
  21815. unset($this->PageAnnots[$page]);
  21816. }
  21817. if (isset($this->newpagegroup[$page])) {
  21818. unset($this->newpagegroup[$page]);
  21819. }
  21820. if (isset($this->pageopen[$page])) {
  21821. unset($this->pageopen[$page]);
  21822. }
  21823. // update remaining pages
  21824. for ($i = $page; $i < $this->numpages; ++$i) {
  21825. $j = $i + 1;
  21826. // shift pages
  21827. $this->pages[$i] = $this->pages[$j];
  21828. $this->pagedim[$i] = $this->pagedim[$j];
  21829. $this->pagelen[$i] = $this->pagelen[$j];
  21830. $this->intmrk[$i] = $this->intmrk[$j];
  21831. $this->bordermrk[$i] = $this->bordermrk[$j];
  21832. $this->cntmrk[$i] = $this->cntmrk[$j];
  21833. if (isset($this->footerpos[$j])) {
  21834. $this->footerpos[$i] = $this->footerpos[$j];
  21835. } elseif (isset($this->footerpos[$i])) {
  21836. unset($this->footerpos[$i]);
  21837. }
  21838. if (isset($this->footerlen[$j])) {
  21839. $this->footerlen[$i] = $this->footerlen[$j];
  21840. } elseif (isset($this->footerlen[$i])) {
  21841. unset($this->footerlen[$i]);
  21842. }
  21843. if (isset($this->transfmrk[$j])) {
  21844. $this->transfmrk[$i] = $this->transfmrk[$j];
  21845. } elseif (isset($this->transfmrk[$i])) {
  21846. unset($this->transfmrk[$i]);
  21847. }
  21848. if (isset($this->PageAnnots[$j])) {
  21849. $this->PageAnnots[$i] = $this->PageAnnots[$j];
  21850. } elseif (isset($this->PageAnnots[$i])) {
  21851. unset($this->PageAnnots[$i]);
  21852. }
  21853. if (isset($this->newpagegroup[$j])) {
  21854. $this->newpagegroup[$i] = $this->newpagegroup[$j];
  21855. } elseif (isset($this->newpagegroup[$i])) {
  21856. unset($this->newpagegroup[$i]);
  21857. }
  21858. if (isset($this->pageopen[$j])) {
  21859. $this->pageopen[$i] = $this->pageopen[$j];
  21860. } elseif (isset($this->pageopen[$i])) {
  21861. unset($this->pageopen[$i]);
  21862. }
  21863. }
  21864. // remove last page
  21865. unset($this->pages[$this->numpages]);
  21866. unset($this->pagedim[$this->numpages]);
  21867. unset($this->pagelen[$this->numpages]);
  21868. unset($this->intmrk[$this->numpages]);
  21869. unset($this->bordermrk[$this->numpages]);
  21870. unset($this->cntmrk[$this->numpages]);
  21871. if (isset($this->footerpos[$this->numpages])) {
  21872. unset($this->footerpos[$this->numpages]);
  21873. }
  21874. if (isset($this->footerlen[$this->numpages])) {
  21875. unset($this->footerlen[$this->numpages]);
  21876. }
  21877. if (isset($this->transfmrk[$this->numpages])) {
  21878. unset($this->transfmrk[$this->numpages]);
  21879. }
  21880. if (isset($this->PageAnnots[$this->numpages])) {
  21881. unset($this->PageAnnots[$this->numpages]);
  21882. }
  21883. if (isset($this->newpagegroup[$this->numpages])) {
  21884. unset($this->newpagegroup[$this->numpages]);
  21885. }
  21886. if (isset($this->pageopen[$this->numpages])) {
  21887. unset($this->pageopen[$this->numpages]);
  21888. }
  21889. --$this->numpages;
  21890. $this->page = $this->numpages;
  21891. // adjust outlines
  21892. $tmpoutlines = $this->outlines;
  21893. foreach ($tmpoutlines as $key => $outline) {
  21894. if ($outline['p'] > $page) {
  21895. $this->outlines[$key]['p'] = $outline['p'] - 1;
  21896. } elseif ($outline['p'] == $page) {
  21897. unset($this->outlines[$key]);
  21898. }
  21899. }
  21900. // adjust links
  21901. $tmplinks = $this->links;
  21902. foreach ($tmplinks as $key => $link) {
  21903. if ($link[0] > $page) {
  21904. $this->links[$key][0] = $link[0] - 1;
  21905. } elseif ($link[0] == $page) {
  21906. unset($this->links[$key]);
  21907. }
  21908. }
  21909. // adjust javascript
  21910. $tmpjavascript = $this->javascript;
  21911. global $jpage;
  21912. $jpage = $page;
  21913. $this->javascript = preg_replace_callback('/this\.addField\(\'([^\']*)\',\'([^\']*)\',([0-9]+)/',
  21914. create_function('$matches', 'global $jpage;
  21915. $pagenum = intval($matches[3]) + 1;
  21916. if ($pagenum >= $jpage) {
  21917. $newpage = ($pagenum - 1);
  21918. } elseif ($pagenum == $jpage) {
  21919. $newpage = 1;
  21920. } else {
  21921. $newpage = $pagenum;
  21922. }
  21923. --$newpage;
  21924. return "this.addField(\'".$matches[1]."\',\'".$matches[2]."\',".$newpage."";'), $tmpjavascript);
  21925. // return to last page
  21926. $this->lastPage(true);
  21927. return true;
  21928. }
  21929. /**
  21930. * Clone the specified page to a new page.
  21931. * @param int $page number of page to copy (0 = current page)
  21932. * @return true in case of success, false in case of error.
  21933. * @access public
  21934. * @since 4.9.015 (2010-04-20)
  21935. */
  21936. public function copyPage($page=0) {
  21937. if ($page == 0) {
  21938. // default value
  21939. $page = $this->page;
  21940. }
  21941. if (($page < 1) OR ($page > $this->numpages)) {
  21942. return false;
  21943. }
  21944. if ($page == $this->page) {
  21945. // close the page before cloning it
  21946. $this->endPage();
  21947. }
  21948. // copy all page-related states
  21949. ++$this->numpages;
  21950. $this->page = $this->numpages;
  21951. $this->pages[$this->page] = $this->pages[$page];
  21952. $this->pagedim[$this->page] = $this->pagedim[$page];
  21953. $this->pagelen[$this->page] = $this->pagelen[$page];
  21954. $this->intmrk[$this->page] = $this->intmrk[$page];
  21955. $this->bordermrk[$this->page] = $this->bordermrk[$page];
  21956. $this->cntmrk[$this->page] = $this->cntmrk[$page];
  21957. $this->pageopen[$this->page] = false;
  21958. if (isset($this->footerpos[$page])) {
  21959. $this->footerpos[$this->page] = $this->footerpos[$page];
  21960. }
  21961. if (isset($this->footerlen[$page])) {
  21962. $this->footerlen[$this->page] = $this->footerlen[$page];
  21963. }
  21964. if (isset($this->transfmrk[$page])) {
  21965. $this->transfmrk[$this->page] = $this->transfmrk[$page];
  21966. }
  21967. if (isset($this->PageAnnots[$page])) {
  21968. $this->PageAnnots[$this->page] = $this->PageAnnots[$page];
  21969. }
  21970. if (isset($this->newpagegroup[$page])) {
  21971. $this->newpagegroup[$this->page] = $this->newpagegroup[$page];
  21972. }
  21973. // copy outlines
  21974. $tmpoutlines = $this->outlines;
  21975. foreach ($tmpoutlines as $key => $outline) {
  21976. if ($outline['p'] == $page) {
  21977. $this->outlines[] = array('t' => $outline['t'], 'l' => $outline['l'], 'y' => $outline['y'], 'p' => $this->page);
  21978. }
  21979. }
  21980. // copy links
  21981. $tmplinks = $this->links;
  21982. foreach ($tmplinks as $key => $link) {
  21983. if ($link[0] == $page) {
  21984. $this->links[] = array($this->page, $link[1]);
  21985. }
  21986. }
  21987. // return to last page
  21988. $this->lastPage(true);
  21989. return true;
  21990. }
  21991. /**
  21992. * Output a Table of Content Index (TOC).
  21993. * Before calling this method you have to open the page using the addTOCPage() method.
  21994. * After calling this method you have to call endTOCPage() to close the TOC page.
  21995. * You can override this method to achieve different styles.
  21996. * @param int $page page number where this TOC should be inserted (leave empty for current page).
  21997. * @param string $numbersfont set the font for page numbers (please use monospaced font for better alignment).
  21998. * @param string $filler string used to fill the space between text and page number.
  21999. * @param string $toc_name name to use for TOC bookmark.
  22000. * @access public
  22001. * @author Nicola Asuni
  22002. * @since 4.5.000 (2009-01-02)
  22003. * @see addTOCPage(), endTOCPage(), addHTMLTOC()
  22004. */
  22005. public function addTOC($page='', $numbersfont='', $filler='.', $toc_name='TOC') {
  22006. $fontsize = $this->FontSizePt;
  22007. $fontfamily = $this->FontFamily;
  22008. $fontstyle = $this->FontStyle;
  22009. $w = $this->w - $this->lMargin - $this->rMargin;
  22010. $spacer = $this->GetStringWidth(chr(32)) * 4;
  22011. $page_first = $this->getPage();
  22012. $lmargin = $this->lMargin;
  22013. $rmargin = $this->rMargin;
  22014. $x_start = $this->GetX();
  22015. $current_page = $this->page;
  22016. $current_column = $this->current_column;
  22017. if ($this->empty_string($numbersfont)) {
  22018. $numbersfont = $this->default_monospaced_font;
  22019. }
  22020. if ($this->empty_string($filler)) {
  22021. $filler = ' ';
  22022. }
  22023. if ($this->empty_string($page)) {
  22024. $gap = ' ';
  22025. } else {
  22026. $gap = '';
  22027. if ($page < 1) {
  22028. $page = 1;
  22029. }
  22030. }
  22031. foreach ($this->outlines as $key => $outline) {
  22032. if ($this->rtl) {
  22033. $aligntext = 'R';
  22034. $alignnum = 'L';
  22035. } else {
  22036. $aligntext = 'L';
  22037. $alignnum = 'R';
  22038. }
  22039. if ($outline['l'] == 0) {
  22040. $this->SetFont($fontfamily, $fontstyle.'B', $fontsize);
  22041. } else {
  22042. $this->SetFont($fontfamily, $fontstyle, $fontsize - $outline['l']);
  22043. }
  22044. // check for page break
  22045. $this->checkPageBreak(($this->FontSize * $this->cell_height_ratio));
  22046. // set margins and X position
  22047. if (($this->page == $current_page) AND ($this->current_column == $current_column)) {
  22048. $this->lMargin = $lmargin;
  22049. $this->rMargin = $rmargin;
  22050. } else {
  22051. if ($this->current_column != $current_column) {
  22052. if ($this->rtl) {
  22053. $x_start = $this->w - $this->columns[$this->current_column]['x'];
  22054. } else {
  22055. $x_start = $this->columns[$this->current_column]['x'];
  22056. }
  22057. }
  22058. $lmargin = $this->lMargin;
  22059. $rmargin = $this->rMargin;
  22060. $current_page = $this->page;
  22061. $current_column = $this->current_column;
  22062. }
  22063. $this->SetX($x_start);
  22064. $indent = ($spacer * $outline['l']);
  22065. if ($this->rtl) {
  22066. $this->rMargin += $indent;
  22067. $this->x -= $indent;
  22068. } else {
  22069. $this->lMargin += $indent;
  22070. $this->x += $indent;
  22071. }
  22072. $link = $this->AddLink();
  22073. $this->SetLink($link, $outline['y'], $outline['p']);
  22074. // write the text
  22075. $this->Write(0, $outline['t'], $link, 0, $aligntext, false, 0, false, false, 0);
  22076. $this->SetFont($numbersfont, $fontstyle, $fontsize);
  22077. if ($this->empty_string($page)) {
  22078. $pagenum = $outline['p'];
  22079. } else {
  22080. // placemark to be replaced with the correct number
  22081. $pagenum = '{#'.($outline['p']).'}';
  22082. if ($this->isUnicodeFont()) {
  22083. $pagenum = '{'.$pagenum.'}';
  22084. }
  22085. }
  22086. $numwidth = $this->GetStringWidth($pagenum);
  22087. if ($this->rtl) {
  22088. $tw = $this->x - $this->lMargin;
  22089. } else {
  22090. $tw = $this->w - $this->rMargin - $this->x;
  22091. }
  22092. $fw = $tw - $numwidth - $this->GetStringWidth(chr(32));
  22093. $numfills = floor($fw / $this->GetStringWidth($filler));
  22094. if ($numfills > 0) {
  22095. $rowfill = str_repeat($filler, $numfills);
  22096. } else {
  22097. $rowfill = '';
  22098. }
  22099. if ($this->rtl) {
  22100. $pagenum = $pagenum.$gap.$rowfill.' ';
  22101. } else {
  22102. $pagenum = ' '.$rowfill.$gap.$pagenum;
  22103. }
  22104. // write the number
  22105. $this->Cell($tw, 0, $pagenum, 0, 1, $alignnum, 0, $link, 0);
  22106. }
  22107. $page_last = $this->getPage();
  22108. $numpages = $page_last - $page_first + 1;
  22109. if (!$this->empty_string($page)) {
  22110. for ($p = $page_first; $p <= $page_last; ++$p) {
  22111. // get page data
  22112. $temppage = $this->getPageBuffer($p);
  22113. for ($n = 1; $n <= $this->numpages; ++$n) {
  22114. // update page numbers
  22115. $k = '{#'.$n.'}';
  22116. $ku = '{'.$k.'}';
  22117. $alias_a = $this->_escape($k);
  22118. $alias_au = $this->_escape($ku);
  22119. if ($this->isunicode) {
  22120. $alias_b = $this->_escape($this->UTF8ToLatin1($k));
  22121. $alias_bu = $this->_escape($this->UTF8ToLatin1($ku));
  22122. $alias_c = $this->_escape($this->utf8StrRev($k, false, $this->tmprtl));
  22123. $alias_cu = $this->_escape($this->utf8StrRev($ku, false, $this->tmprtl));
  22124. }
  22125. if ($n >= $page) {
  22126. $np = $n + $numpages;
  22127. } else {
  22128. $np = $n;
  22129. }
  22130. $ns = $this->formatTOCPageNumber($np);
  22131. $nu = $ns;
  22132. $sdiff = strlen($k) - strlen($ns) - 1;
  22133. $sdiffu = strlen($ku) - strlen($ns) - 1;
  22134. $sfill = str_repeat($filler, $sdiff);
  22135. $sfillu = str_repeat($filler, $sdiffu);
  22136. if ($this->rtl) {
  22137. $ns = $ns.' '.$sfill;
  22138. $nu = $nu.' '.$sfillu;
  22139. } else {
  22140. $ns = $sfill.' '.$ns;
  22141. $nu = $sfillu.' '.$nu;
  22142. }
  22143. $nu = $this->UTF8ToUTF16BE($nu, false);
  22144. $temppage = str_replace($alias_au, $nu, $temppage);
  22145. if ($this->isunicode) {
  22146. $temppage = str_replace($alias_bu, $nu, $temppage);
  22147. $temppage = str_replace($alias_cu, $nu, $temppage);
  22148. $temppage = str_replace($alias_b, $ns, $temppage);
  22149. $temppage = str_replace($alias_c, $ns, $temppage);
  22150. }
  22151. $temppage = str_replace($alias_a, $ns, $temppage);
  22152. }
  22153. // save changes
  22154. $this->setPageBuffer($p, $temppage);
  22155. }
  22156. // move pages
  22157. $this->Bookmark($toc_name, 0, 0, $page_first);
  22158. for ($i = 0; $i < $numpages; ++$i) {
  22159. $this->movePage($page_last, $page);
  22160. }
  22161. }
  22162. }
  22163. /**
  22164. * Output a Table Of Content Index (TOC) using HTML templates.
  22165. * Before calling this method you have to open the page using the addTOCPage() method.
  22166. * After calling this method you have to call endTOCPage() to close the TOC page.
  22167. * @param int $page page number where this TOC should be inserted (leave empty for current page).
  22168. * @param string $toc_name name to use for TOC bookmark.
  22169. * @param array $templates array of html templates. Use: #TOC_DESCRIPTION# for bookmark title, #TOC_PAGE_NUMBER# for page number.
  22170. * @param boolean $correct_align if true correct the number alignment (numbers must be in monospaced font like courier and right aligned on LTR, or left aligned on RTL)
  22171. * @access public
  22172. * @author Nicola Asuni
  22173. * @since 5.0.001 (2010-05-06)
  22174. * @see addTOCPage(), endTOCPage(), addTOC()
  22175. */
  22176. public function addHTMLTOC($page='', $toc_name='TOC', $templates=array(), $correct_align=true) {
  22177. $prev_htmlLinkColorArray = $this->htmlLinkColorArray;
  22178. $prev_htmlLinkFontStyle = $this->htmlLinkFontStyle;
  22179. // set new style for link
  22180. $this->htmlLinkColorArray = array();
  22181. $this->htmlLinkFontStyle = '';
  22182. $page_first = $this->getPage();
  22183. // get the font type used for numbers in each template
  22184. $current_font = $this->FontFamily;
  22185. foreach ($templates as $level => $html) {
  22186. $dom = $this->getHtmlDomArray($html);
  22187. foreach ($dom as $key => $value) {
  22188. if ($value['value'] == '#TOC_PAGE_NUMBER#') {
  22189. $this->SetFont($dom[($key - 1)]['fontname']);
  22190. $templates['F'.$level] = $this->isUnicodeFont();
  22191. }
  22192. }
  22193. }
  22194. $this->SetFont($current_font);
  22195. foreach ($this->outlines as $key => $outline) {
  22196. // get HTML template
  22197. $row = $templates[$outline['l']];
  22198. if ($this->empty_string($page)) {
  22199. $pagenum = $outline['p'];
  22200. } else {
  22201. // placemark to be replaced with the correct number
  22202. $pagenum = '{#'.($outline['p']).'}';
  22203. if ($templates['F'.$outline['l']]) {
  22204. $pagenum = '{'.$pagenum.'}';
  22205. }
  22206. }
  22207. // replace templates with current values
  22208. $row = str_replace('#TOC_DESCRIPTION#', $outline['t'], $row);
  22209. $row = str_replace('#TOC_PAGE_NUMBER#', $pagenum, $row);
  22210. // add link to page
  22211. $row = '<a href="#'.$outline['p'].','.$outline['y'].'">'.$row.'</a>';
  22212. // write bookmark entry
  22213. $this->writeHTML($row, false, false, true, false, '');
  22214. }
  22215. // restore link styles
  22216. $this->htmlLinkColorArray = $prev_htmlLinkColorArray;
  22217. $this->htmlLinkFontStyle = $prev_htmlLinkFontStyle;
  22218. // move TOC page and replace numbers
  22219. $page_last = $this->getPage();
  22220. $numpages = $page_last - $page_first + 1;
  22221. if (!$this->empty_string($page)) {
  22222. for ($p = $page_first; $p <= $page_last; ++$p) {
  22223. // get page data
  22224. $temppage = $this->getPageBuffer($p);
  22225. for ($n = 1; $n <= $this->numpages; ++$n) {
  22226. // update page numbers
  22227. $k = '{#'.$n.'}';
  22228. $ku = '{'.$k.'}';
  22229. $alias_a = $this->_escape($k);
  22230. $alias_au = $this->_escape('{'.$k.'}');
  22231. if ($this->isunicode) {
  22232. $alias_b = $this->_escape($this->UTF8ToLatin1($k));
  22233. $alias_bu = $this->_escape($this->UTF8ToLatin1($ku));
  22234. $alias_c = $this->_escape($this->utf8StrRev($k, false, $this->tmprtl));
  22235. $alias_cu = $this->_escape($this->utf8StrRev($ku, false, $this->tmprtl));
  22236. }
  22237. if ($n >= $page) {
  22238. $np = $n + $numpages;
  22239. } else {
  22240. $np = $n;
  22241. }
  22242. $ns = $this->formatTOCPageNumber($np);
  22243. $nu = $ns;
  22244. if ($correct_align) {
  22245. $sdiff = strlen($k) - strlen($ns);
  22246. $sdiffu = strlen($ku) - strlen($ns);
  22247. $sfill = str_repeat(' ', $sdiff);
  22248. $sfillu = str_repeat(' ', $sdiffu);
  22249. if ($this->rtl) {
  22250. $ns = $ns.$sfill;
  22251. $nu = $nu.$sfillu;
  22252. } else {
  22253. $ns = $sfill.$ns;
  22254. $nu = $sfillu.$nu;
  22255. }
  22256. }
  22257. $nu = $this->UTF8ToUTF16BE($nu, false);
  22258. $temppage = str_replace($alias_au, $nu, $temppage);
  22259. if ($this->isunicode) {
  22260. $temppage = str_replace($alias_bu, $nu, $temppage);
  22261. $temppage = str_replace($alias_cu, $nu, $temppage);
  22262. $temppage = str_replace($alias_b, $ns, $temppage);
  22263. $temppage = str_replace($alias_c, $ns, $temppage);
  22264. }
  22265. $temppage = str_replace($alias_a, $ns, $temppage);
  22266. }
  22267. // save changes
  22268. $this->setPageBuffer($p, $temppage);
  22269. }
  22270. // move pages
  22271. $this->Bookmark($toc_name, 0, 0, $page_first);
  22272. for ($i = 0; $i < $numpages; ++$i) {
  22273. $this->movePage($page_last, $page);
  22274. }
  22275. }
  22276. }
  22277. /**
  22278. * Stores a copy of the current TCPDF object used for undo operation.
  22279. * @access public
  22280. * @since 4.5.029 (2009-03-19)
  22281. */
  22282. public function startTransaction() {
  22283. if (isset($this->objcopy)) {
  22284. // remove previous copy
  22285. $this->commitTransaction();
  22286. }
  22287. // record current page number and Y position
  22288. $this->start_transaction_page = $this->page;
  22289. $this->start_transaction_y = $this->y;
  22290. // clone current object
  22291. $this->objcopy = $this->objclone($this);
  22292. }
  22293. /**
  22294. * Delete the copy of the current TCPDF object used for undo operation.
  22295. * @access public
  22296. * @since 4.5.029 (2009-03-19)
  22297. */
  22298. public function commitTransaction() {
  22299. if (isset($this->objcopy)) {
  22300. $this->objcopy->_destroy(true, true);
  22301. unset($this->objcopy);
  22302. }
  22303. }
  22304. /**
  22305. * This method allows to undo the latest transaction by returning the latest saved TCPDF object with startTransaction().
  22306. * @param boolean $self if true restores current class object to previous state without the need of reassignment via the returned value.
  22307. * @return TCPDF object.
  22308. * @access public
  22309. * @since 4.5.029 (2009-03-19)
  22310. */
  22311. public function rollbackTransaction($self=false) {
  22312. if (isset($this->objcopy)) {
  22313. if (isset($this->objcopy->diskcache) AND $this->objcopy->diskcache) {
  22314. // truncate files to previous values
  22315. foreach ($this->objcopy->cache_file_length as $file => $length) {
  22316. $file = substr($file, 1);
  22317. $handle = fopen($file, 'r+');
  22318. ftruncate($handle, $length);
  22319. }
  22320. }
  22321. $this->_destroy(true, true);
  22322. if ($self) {
  22323. $objvars = get_object_vars($this->objcopy);
  22324. foreach ($objvars as $key => $value) {
  22325. $this->$key = $value;
  22326. }
  22327. }
  22328. return $this->objcopy;
  22329. }
  22330. return $this;
  22331. }
  22332. /**
  22333. * Creates a copy of a class object
  22334. * @param object $object class object to be cloned
  22335. * @return cloned object
  22336. * @access public
  22337. * @since 4.5.029 (2009-03-19)
  22338. */
  22339. public function objclone($object) {
  22340. return @clone($object);
  22341. }
  22342. /**
  22343. * Determine whether a string is empty.
  22344. * @param string $str string to be checked
  22345. * @return boolean true if string is empty
  22346. * @access public
  22347. * @since 4.5.044 (2009-04-16)
  22348. */
  22349. public function empty_string($str) {
  22350. return (is_null($str) OR (is_string($str) AND (strlen($str) == 0)));
  22351. }
  22352. /**
  22353. * Find position of last occurrence of a substring in a string
  22354. * @param string $haystack The string to search in.
  22355. * @param string $needle substring to search.
  22356. * @param int $offset May be specified to begin searching an arbitrary number of characters into the string.
  22357. * @return Returns the position where the needle exists. Returns FALSE if the needle was not found.
  22358. * @access public
  22359. * @since 4.8.038 (2010-03-13)
  22360. */
  22361. public function revstrpos($haystack, $needle, $offset = 0) {
  22362. $length = strlen($haystack);
  22363. $offset = ($offset > 0)?($length - $offset):abs($offset);
  22364. $pos = strpos(strrev($haystack), strrev($needle), $offset);
  22365. return ($pos === false)?false:($length - $pos - strlen($needle));
  22366. }
  22367. // --- MULTI COLUMNS METHODS -----------------------
  22368. /**
  22369. * Set multiple columns of the same size
  22370. * @param int $numcols number of columns (set to zero to disable columns mode)
  22371. * @param int $width column width
  22372. * @param int $y column starting Y position (leave empty for current Y position)
  22373. * @access public
  22374. * @since 4.9.001 (2010-03-28)
  22375. */
  22376. public function setEqualColumns($numcols=0, $width=0, $y='') {
  22377. $this->columns = array();
  22378. if ($numcols < 2) {
  22379. $numcols = 0;
  22380. $this->columns = array();
  22381. } else {
  22382. // maximum column width
  22383. $maxwidth = ($this->w - $this->original_lMargin - $this->original_rMargin) / $numcols;
  22384. if (($width == 0) OR ($width > $maxwidth)) {
  22385. $width = $maxwidth;
  22386. }
  22387. if ($this->empty_string($y)) {
  22388. $y = $this->y;
  22389. }
  22390. // space between columns
  22391. $space = (($this->w - $this->original_lMargin - $this->original_rMargin - ($numcols * $width)) / ($numcols - 1));
  22392. // fill the columns array (with, space, starting Y position)
  22393. for ($i = 0; $i < $numcols; ++$i) {
  22394. $this->columns[$i] = array('w' => $width, 's' => $space, 'y' => $y);
  22395. }
  22396. }
  22397. $this->num_columns = $numcols;
  22398. $this->current_column = 0;
  22399. $this->column_start_page = $this->page;
  22400. }
  22401. /**
  22402. * Set columns array.
  22403. * Each column is represented by and array with the following keys: (w = width, s = space between columns, y = column top position).
  22404. * @param array $columns
  22405. * @access public
  22406. * @since 4.9.001 (2010-03-28)
  22407. */
  22408. public function setColumnsArray($columns) {
  22409. $this->columns = $columns;
  22410. $this->num_columns = count($columns);
  22411. $this->current_column = 0;
  22412. $this->column_start_page = $this->page;
  22413. }
  22414. /**
  22415. * Set position at a given column
  22416. * @param int $col column number (from 0 to getNumberOfColumns()-1); empty string = current column.
  22417. * @access public
  22418. * @since 4.9.001 (2010-03-28)
  22419. */
  22420. public function selectColumn($col='') {
  22421. if (is_string($col)) {
  22422. $col = $this->current_column;
  22423. } elseif($col >= $this->num_columns) {
  22424. $col = 0;
  22425. }
  22426. $xshift = 0;
  22427. $enable_thead = false;
  22428. if ($this->num_columns > 1) {
  22429. if ($col != $this->current_column) {
  22430. // move Y pointer at the top of the column
  22431. if ($this->column_start_page == $this->page) {
  22432. $this->y = $this->columns[$col]['y'];
  22433. } else {
  22434. $this->y = $this->tMargin;
  22435. }
  22436. // Avoid to write table headers more than once
  22437. if (($this->page > $this->maxselcol['page']) OR (($this->page == $this->maxselcol['page']) AND ($col > $this->maxselcol['column']))) {
  22438. $enable_thead = true;
  22439. $this->maxselcol['page'] = $this->page;
  22440. $this->maxselcol['column'] = $col;
  22441. }
  22442. }
  22443. $xshift = $this->colxshift;
  22444. // set X position of the current column by case
  22445. $listindent = ($this->listindentlevel * $this->listindent);
  22446. $colpos = ($col * ($this->columns[$col]['w'] + $this->columns[$col]['s']));
  22447. if ($this->rtl) {
  22448. $x = $this->w - $this->original_rMargin - $colpos;
  22449. $this->rMargin = ($this->w - $x + $listindent);
  22450. $this->lMargin = ($x - $this->columns[$col]['w']);
  22451. $this->x = $x - $listindent;
  22452. } else {
  22453. $x = $this->original_lMargin + $colpos;
  22454. $this->lMargin = ($x + $listindent);
  22455. $this->rMargin = ($this->w - $x - $this->columns[$col]['w']);
  22456. $this->x = $x + $listindent;
  22457. }
  22458. $this->columns[$col]['x'] = $x;
  22459. }
  22460. $this->current_column = $col;
  22461. // fix for HTML mode
  22462. $this->newline = true;
  22463. // print HTML table header (if any)
  22464. if ((!$this->empty_string($this->thead)) AND (!$this->inthead)) {
  22465. if ($enable_thead) {
  22466. // print table header
  22467. $this->writeHTML($this->thead, false, false, false, false, '');
  22468. $this->y += $xshift['s'];
  22469. // store end of header position
  22470. if (!isset($this->columns[$col]['th'])) {
  22471. $this->columns[$col]['th'] = array();
  22472. }
  22473. $this->columns[$col]['th']['\''.$this->page.'\''] = $this->y;
  22474. $this->lasth = 0;
  22475. } elseif (isset($this->columns[$col]['th']['\''.$this->page.'\''])) {
  22476. $this->y = $this->columns[$col]['th']['\''.$this->page.'\''];
  22477. }
  22478. }
  22479. // account for an html table cell over multiple columns
  22480. if ($this->rtl) {
  22481. $this->rMargin += $xshift['x'];
  22482. $this->x -= ($xshift['x'] + $xshift['p']);
  22483. } else {
  22484. $this->lMargin += $xshift['x'];
  22485. $this->x += $xshift['x'] + $xshift['p'];
  22486. }
  22487. }
  22488. /**
  22489. * Return the current column number
  22490. * @return int current column number
  22491. * @access public
  22492. * @since 5.5.011 (2010-07-08)
  22493. */
  22494. public function getColumn() {
  22495. return $this->current_column;
  22496. }
  22497. /**
  22498. * Return the current number of columns.
  22499. * @return int number of columns
  22500. * @access public
  22501. * @since 5.8.018 (2010-08-25)
  22502. */
  22503. public function getNumberOfColumns() {
  22504. return $this->num_columns;
  22505. }
  22506. /**
  22507. * Serialize an array of parameters to be used with TCPDF tag in HTML code.
  22508. * @param array $pararray parameters array
  22509. * @return sting containing serialized data
  22510. * @access public
  22511. * @since 4.9.006 (2010-04-02)
  22512. */
  22513. public function serializeTCPDFtagParameters($pararray) {
  22514. return urlencode(serialize($pararray));
  22515. }
  22516. /**
  22517. * Set Text rendering mode.
  22518. * @param int $stroke outline size in user units (0 = disable).
  22519. * @param boolean $fill if true fills the text (default).
  22520. * @param boolean $clip if true activate clipping mode
  22521. * @access public
  22522. * @since 4.9.008 (2009-04-02)
  22523. */
  22524. public function setTextRenderingMode($stroke=0, $fill=true, $clip=false) {
  22525. // Ref.: PDF 32000-1:2008 - 9.3.6 Text Rendering Mode
  22526. // convert text rendering parameters
  22527. if ($stroke < 0) {
  22528. $stroke = 0;
  22529. }
  22530. if ($fill === true) {
  22531. if ($stroke > 0) {
  22532. if ($clip === true) {
  22533. // Fill, then stroke text and add to path for clipping
  22534. $textrendermode = 6;
  22535. } else {
  22536. // Fill, then stroke text
  22537. $textrendermode = 2;
  22538. }
  22539. $textstrokewidth = $stroke;
  22540. } else {
  22541. if ($clip === true) {
  22542. // Fill text and add to path for clipping
  22543. $textrendermode = 4;
  22544. } else {
  22545. // Fill text
  22546. $textrendermode = 0;
  22547. }
  22548. }
  22549. } else {
  22550. if ($stroke > 0) {
  22551. if ($clip === true) {
  22552. // Stroke text and add to path for clipping
  22553. $textrendermode = 5;
  22554. } else {
  22555. // Stroke text
  22556. $textrendermode = 1;
  22557. }
  22558. $textstrokewidth = $stroke;
  22559. } else {
  22560. if ($clip === true) {
  22561. // Add text to path for clipping
  22562. $textrendermode = 7;
  22563. } else {
  22564. // Neither fill nor stroke text (invisible)
  22565. $textrendermode = 3;
  22566. }
  22567. }
  22568. }
  22569. $this->textrendermode = $textrendermode;
  22570. $this->textstrokewidth = $stroke * $this->k;
  22571. }
  22572. /**
  22573. * Returns an array of chars containing soft hyphens.
  22574. * @param array $word array of chars
  22575. * @param array $patterns Array of hypenation patterns.
  22576. * @param array $dictionary Array of words to be returned without applying the hyphenation algoritm.
  22577. * @param int $leftmin Minimum number of character to leave on the left of the word without applying the hyphens.
  22578. * @param int $rightmin Minimum number of character to leave on the right of the word without applying the hyphens.
  22579. * @param int $charmin Minimum word lenght to apply the hyphenation algoritm.
  22580. * @param int $charmax Maximum lenght of broken piece of word.
  22581. * @return array text with soft hyphens
  22582. * @author Nicola Asuni
  22583. * @since 4.9.012 (2010-04-12)
  22584. * @access protected
  22585. */
  22586. protected function hyphenateWord($word, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
  22587. $hyphenword = array(); // hyphens positions
  22588. $numchars = count($word);
  22589. if ($numchars <= $charmin) {
  22590. return $word;
  22591. }
  22592. $word_string = $this->UTF8ArrSubString($word);
  22593. // some words will be returned as-is
  22594. $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})(\]?)$/';
  22595. if (preg_match($pattern, $word_string) > 0) {
  22596. // email
  22597. return $word;
  22598. }
  22599. $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})(\]?)$/';
  22600. if (preg_match($pattern, $word_string) > 0) {
  22601. // URL
  22602. return $word;
  22603. }
  22604. if (isset($dictionary[$word_string])) {
  22605. return $this->UTF8StringToArray($dictionary[$word_string]);
  22606. }
  22607. // suround word with '_' characters
  22608. $tmpword = array_merge(array(95), $word, array(95));
  22609. $tmpnumchars = $numchars + 2;
  22610. $maxpos = $tmpnumchars - $charmin;
  22611. for ($pos = 0; $pos < $maxpos; ++$pos) {
  22612. $imax = min(($tmpnumchars - $pos), $charmax);
  22613. for ($i = $charmin; $i <= $imax; ++$i) {
  22614. $subword = strtolower($this->UTF8ArrSubString($tmpword, $pos, $pos + $i));
  22615. if (isset($patterns[$subword])) {
  22616. $pattern = $this->UTF8StringToArray($patterns[$subword]);
  22617. $pattern_length = count($pattern);
  22618. $digits = 1;
  22619. for ($j = 0; $j < $pattern_length; ++$j) {
  22620. // check if $pattern[$j] is a number
  22621. if (($pattern[$j] >= 48) AND ($pattern[$j] <= 57)) {
  22622. if ($j == 0) {
  22623. $zero = $pos - 1;
  22624. } else {
  22625. $zero = $pos + $j - $digits;
  22626. }
  22627. if (!isset($hyphenword[$zero]) OR ($hyphenword[$zero] != $pattern[$j])) {
  22628. $hyphenword[$zero] = $this->unichr($pattern[$j]);
  22629. }
  22630. ++$digits;
  22631. }
  22632. }
  22633. }
  22634. }
  22635. }
  22636. $inserted = 0;
  22637. $maxpos = $numchars - $rightmin;
  22638. for($i = $leftmin; $i <= $maxpos; ++$i) {
  22639. if(isset($hyphenword[$i]) AND (($hyphenword[$i] % 2) != 0)) {
  22640. // 173 = soft hyphen character
  22641. array_splice($word, $i + $inserted, 0, 173);
  22642. ++$inserted;
  22643. }
  22644. }
  22645. return $word;
  22646. }
  22647. /**
  22648. * Returns an array of hyphenation patterns.
  22649. * @param string $file TEX file containing hypenation patterns. TEX pattrns can be downloaded from http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/
  22650. * @return array of hyphenation patterns
  22651. * @author Nicola Asuni
  22652. * @since 4.9.012 (2010-04-12)
  22653. * @access public
  22654. */
  22655. public function getHyphenPatternsFromTEX($file) {
  22656. // TEX patterns are available at:
  22657. // http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/
  22658. $data = file_get_contents($file);
  22659. $patterns = array();
  22660. // remove comments
  22661. $data = preg_replace('/\%[^\n]*/', '', $data);
  22662. // extract the patterns part
  22663. preg_match('/\\\\patterns\{([^\}]*)\}/i', $data, $matches);
  22664. $data = trim(substr($matches[0], 10, -1));
  22665. // extract each pattern
  22666. $patterns_array = preg_split('/[\s]+/', $data);
  22667. // create new language array of patterns
  22668. $patterns = array();
  22669. foreach($patterns_array as $val) {
  22670. if (!$this->empty_string($val)) {
  22671. $val = trim($val);
  22672. $val = str_replace('\'', '\\\'', $val);
  22673. $key = preg_replace('/[0-9]+/', '', $val);
  22674. $patterns[$key] = $val;
  22675. }
  22676. }
  22677. return $patterns;
  22678. }
  22679. /**
  22680. * Returns text with soft hyphens.
  22681. * @param string $text text to process
  22682. * @param mixed $patterns Array of hypenation patterns or a TEX file containing hypenation patterns. TEX patterns can be downloaded from http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/
  22683. * @param array $dictionary Array of words to be returned without applying the hyphenation algoritm.
  22684. * @param int $leftmin Minimum number of character to leave on the left of the word without applying the hyphens.
  22685. * @param int $rightmin Minimum number of character to leave on the right of the word without applying the hyphens.
  22686. * @param int $charmin Minimum word lenght to apply the hyphenation algoritm.
  22687. * @param int $charmax Maximum lenght of broken piece of word.
  22688. * @return array text with soft hyphens
  22689. * @author Nicola Asuni
  22690. * @since 4.9.012 (2010-04-12)
  22691. * @access public
  22692. */
  22693. public function hyphenateText($text, $patterns, $dictionary=array(), $leftmin=1, $rightmin=2, $charmin=1, $charmax=8) {
  22694. $text = $this->unhtmlentities($text);
  22695. $word = array(); // last word
  22696. $txtarr = array(); // text to be returned
  22697. $intag = false; // true if we are inside an HTML tag
  22698. if (!is_array($patterns)) {
  22699. $patterns = $this->getHyphenPatternsFromTEX($patterns);
  22700. }
  22701. // get array of characters
  22702. $unichars = $this->UTF8StringToArray($text);
  22703. // for each char
  22704. foreach ($unichars as $char) {
  22705. if ((!$intag) AND $this->unicode->uni_type[$char] == 'L') {
  22706. // letter character
  22707. $word[] = $char;
  22708. } else {
  22709. // other type of character
  22710. if (!$this->empty_string($word)) {
  22711. // hypenate the word
  22712. $txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
  22713. $word = array();
  22714. }
  22715. $txtarr[] = $char;
  22716. if (chr($char) == '<') {
  22717. // we are inside an HTML tag
  22718. $intag = true;
  22719. } elseif ($intag AND (chr($char) == '>')) {
  22720. // end of HTML tag
  22721. $intag = false;
  22722. }
  22723. }
  22724. }
  22725. if (!$this->empty_string($word)) {
  22726. // hypenate the word
  22727. $txtarr = array_merge($txtarr, $this->hyphenateWord($word, $patterns, $dictionary, $leftmin, $rightmin, $charmin, $charmax));
  22728. }
  22729. // convert char array to string and return
  22730. return $this->UTF8ArrSubString($txtarr);
  22731. }
  22732. /**
  22733. * Enable/disable rasterization of vector images using ImageMagick library.
  22734. * @param boolean $mode if true enable rasterization, false otherwise.
  22735. * @access public
  22736. * @since 5.0.000 (2010-04-27)
  22737. */
  22738. public function setRasterizeVectorImages($mode) {
  22739. $this->rasterize_vector_images = $mode;
  22740. }
  22741. /**
  22742. * Get the Path-Painting Operators.
  22743. * @param string $style Style of rendering. Possible values are:
  22744. * <ul>
  22745. * <li>S or D: Stroke the path.</li>
  22746. * <li>s or d: Close and stroke the path.</li>
  22747. * <li>f or F: Fill the path, using the nonzero winding number rule to determine the region to fill.</li>
  22748. * <li>f* or F*: Fill the path, using the even-odd rule to determine the region to fill.</li>
  22749. * <li>B or FD or DF: Fill and then stroke the path, using the nonzero winding number rule to determine the region to fill.</li>
  22750. * <li>B* or F*D or DF*: Fill and then stroke the path, using the even-odd rule to determine the region to fill.</li>
  22751. * <li>b or fd or df: Close, fill, and then stroke the path, using the nonzero winding number rule to determine the region to fill.</li>
  22752. * <li>b or f*d or df*: Close, fill, and then stroke the path, using the even-odd rule to determine the region to fill.</li>
  22753. * <li>CNZ: Clipping mode using the even-odd rule to determine which regions lie inside the clipping path.</li>
  22754. * <li>CEO: Clipping mode using the nonzero winding number rule to determine which regions lie inside the clipping path</li>
  22755. * <li>n: End the path object without filling or stroking it.</li>
  22756. * </ul>
  22757. * @param string $default default style
  22758. * @param boolean $mode if true enable rasterization, false otherwise.
  22759. * @author Nicola Asuni
  22760. * @access protected
  22761. * @since 5.0.000 (2010-04-30)
  22762. */
  22763. protected function getPathPaintOperator($style, $default='S') {
  22764. $op = '';
  22765. switch($style) {
  22766. case 'S':
  22767. case 'D': {
  22768. $op = 'S';
  22769. break;
  22770. }
  22771. case 's':
  22772. case 'd': {
  22773. $op = 's';
  22774. break;
  22775. }
  22776. case 'f':
  22777. case 'F': {
  22778. $op = 'f';
  22779. break;
  22780. }
  22781. case 'f*':
  22782. case 'F*': {
  22783. $op = 'f*';
  22784. break;
  22785. }
  22786. case 'B':
  22787. case 'FD':
  22788. case 'DF': {
  22789. $op = 'B';
  22790. break;
  22791. }
  22792. case 'B*':
  22793. case 'F*D':
  22794. case 'DF*': {
  22795. $op = 'B*';
  22796. break;
  22797. }
  22798. case 'b':
  22799. case 'fd':
  22800. case 'df': {
  22801. $op = 'b';
  22802. break;
  22803. }
  22804. case 'b*':
  22805. case 'f*d':
  22806. case 'df*': {
  22807. $op = 'b*';
  22808. break;
  22809. }
  22810. case 'CNZ': {
  22811. $op = 'W n';
  22812. break;
  22813. }
  22814. case 'CEO': {
  22815. $op = 'W* n';
  22816. break;
  22817. }
  22818. case 'n': {
  22819. $op = 'n';
  22820. break;
  22821. }
  22822. default: {
  22823. if (!empty($default)) {
  22824. $op = $this->getPathPaintOperator($default, '');
  22825. } else {
  22826. $op = '';
  22827. }
  22828. }
  22829. }
  22830. return $op;
  22831. }
  22832. /**
  22833. * Enable or disable default option for font subsetting.
  22834. * @param boolean $enable if true enable font subsetting by default.
  22835. * @author Nicola Asuni
  22836. * @access public
  22837. * @since 5.3.002 (2010-06-07)
  22838. */
  22839. public function setFontSubsetting($enable=true) {
  22840. $this->font_subsetting = $enable ? true : false;
  22841. }
  22842. /**
  22843. * Return the default option for font subsetting.
  22844. * @return boolean default font subsetting state.
  22845. * @author Nicola Asuni
  22846. * @access public
  22847. * @since 5.3.002 (2010-06-07)
  22848. */
  22849. public function getFontSubsetting() {
  22850. return $this->font_subsetting;
  22851. }
  22852. /**
  22853. * Left trim the input string
  22854. * @param string $str string to trim
  22855. * @param string $replace string that replace spaces.
  22856. * @return left trimmed string
  22857. * @author Nicola Asuni
  22858. * @access public
  22859. * @since 5.8.000 (2010-08-11)
  22860. */
  22861. public function stringLeftTrim($str, $replace='') {
  22862. return preg_replace('/^'.$this->re_space['p'].'+/'.$this->re_space['m'], $replace, $str);
  22863. }
  22864. /**
  22865. * Right trim the input string
  22866. * @param string $str string to trim
  22867. * @param string $replace string that replace spaces.
  22868. * @return right trimmed string
  22869. * @author Nicola Asuni
  22870. * @access public
  22871. * @since 5.8.000 (2010-08-11)
  22872. */
  22873. public function stringRightTrim($str, $replace='') {
  22874. return preg_replace('/'.$this->re_space['p'].'+$/'.$this->re_space['m'], $replace, $str);
  22875. }
  22876. /**
  22877. * Trim the input string
  22878. * @param string $str string to trim
  22879. * @param string $replace string that replace spaces.
  22880. * @return trimmed string
  22881. * @author Nicola Asuni
  22882. * @access public
  22883. * @since 5.8.000 (2010-08-11)
  22884. */
  22885. public function stringTrim($str, $replace='') {
  22886. $str = $this->stringLeftTrim($str, $replace);
  22887. $str = $this->stringRightTrim($str, $replace);
  22888. return $str;
  22889. }
  22890. /**
  22891. * Return true if the current font is unicode type.
  22892. * @return true for unicode font, false otherwise.
  22893. * @author Nicola Asuni
  22894. * @access public
  22895. * @since 5.8.002 (2010-08-14)
  22896. */
  22897. public function isUnicodeFont() {
  22898. return (($this->CurrentFont['type'] == 'TrueTypeUnicode') OR ($this->CurrentFont['type'] == 'cidfont0'));
  22899. }
  22900. /**
  22901. * Return normalized font name
  22902. * @param string $fontfamily property string containing font family names
  22903. * @return string normalized font name
  22904. * @author Nicola Asuni
  22905. * @access public
  22906. * @since 5.8.004 (2010-08-17)
  22907. */
  22908. public function getFontFamilyName($fontfamily) {
  22909. // remove spaces and symbols
  22910. $fontfamily = preg_replace('/[^a-z0-9\,]/', '', strtolower($fontfamily));
  22911. // extract all font names
  22912. $fontslist = preg_split('/[,]/', $fontfamily);
  22913. // find first valid font name
  22914. foreach ($fontslist as $font) {
  22915. // replace font variations
  22916. $font = preg_replace('/italic$/', 'I', $font);
  22917. $font = preg_replace('/oblique$/', 'I', $font);
  22918. $font = preg_replace('/bold([I]?)$/', 'B\\1', $font);
  22919. // replace common family names and core fonts
  22920. $pattern = array();
  22921. $replacement = array();
  22922. $pattern[] = '/^serif|^cursive|^fantasy|^timesnewroman/';
  22923. $replacement[] = 'times';
  22924. $pattern[] = '/^sansserif/';
  22925. $replacement[] = 'helvetica';
  22926. $pattern[] = '/^monospace/';
  22927. $replacement[] = 'courier';
  22928. $font = preg_replace($pattern, $replacement, $font);
  22929. if (in_array(strtolower($font), $this->fontlist) OR in_array($font, $this->fontkeys)) {
  22930. return $font;
  22931. }
  22932. }
  22933. // return current font as default
  22934. return $this->CurrentFont['fontkey'];
  22935. }
  22936. /**
  22937. * Start a new XObject Template.
  22938. * An XObject Template is a PDF block that is a self-contained description of any sequence of graphics objects (including path objects, text objects, and sampled images).
  22939. * An XObject Template may be painted multiple times, either on several pages or at several locations on the same page and produces the same results each time, subject only to the graphics state at the time it is invoked.
  22940. * Note: X,Y coordinates will be reset to 0,0.
  22941. * @param int $w Template width in user units (empty string or zero = page width less margins)
  22942. * @param int $h Template height in user units (empty string or zero = page height less margins)
  22943. * @return int the XObject Template ID in case of success or false in case of error.
  22944. * @author Nicola Asuni
  22945. * @access public
  22946. * @since 5.8.017 (2010-08-24)
  22947. * @see endTemplate(), printTemplate()
  22948. */
  22949. public function startTemplate($w=0, $h=0) {
  22950. if ($this->inxobj) {
  22951. // we are already inside an XObject template
  22952. return false;
  22953. }
  22954. $this->inxobj = true;
  22955. ++$this->n;
  22956. // XObject ID
  22957. $this->xobjid = 'XT'.$this->n;
  22958. // object ID
  22959. $this->xobjects[$this->xobjid] = array('n' => $this->n);
  22960. // store current graphic state
  22961. $this->xobjects[$this->xobjid]['gvars'] = $this->getGraphicVars();
  22962. // initialize data
  22963. $this->xobjects[$this->xobjid]['intmrk'] = 0;
  22964. $this->xobjects[$this->xobjid]['transfmrk'] = array();
  22965. $this->xobjects[$this->xobjid]['outdata'] = '';
  22966. $this->xobjects[$this->xobjid]['xobjects'] = array();
  22967. $this->xobjects[$this->xobjid]['images'] = array();
  22968. $this->xobjects[$this->xobjid]['fonts'] = array();
  22969. $this->xobjects[$this->xobjid]['annotations'] = array();
  22970. // set new environment
  22971. $this->num_columns = 1;
  22972. $this->current_column = 0;
  22973. $this->SetAutoPageBreak(false);
  22974. if (($w === '') OR ($w <= 0)) {
  22975. $w = $this->w - $this->lMargin - $this->rMargin;
  22976. }
  22977. if (($h === '') OR ($h <= 0)) {
  22978. $h = $this->h - $this->tMargin - $this->bMargin;
  22979. }
  22980. $this->xobjects[$this->xobjid]['x'] = 0;
  22981. $this->xobjects[$this->xobjid]['y'] = 0;
  22982. $this->xobjects[$this->xobjid]['w'] = $w;
  22983. $this->xobjects[$this->xobjid]['h'] = $h;
  22984. $this->w = $w;
  22985. $this->h = $h;
  22986. $this->wPt = $this->w * $this->k;
  22987. $this->hPt = $this->h * $this->k;
  22988. $this->fwPt = $this->wPt;
  22989. $this->fhPt = $this->hPt;
  22990. $this->x = 0;
  22991. $this->y = 0;
  22992. $this->lMargin = 0;
  22993. $this->rMargin = 0;
  22994. $this->tMargin = 0;
  22995. $this->bMargin = 0;
  22996. return $this->xobjid;
  22997. }
  22998. /**
  22999. * End the current XObject Template started with startTemplate() and restore the previous graphic state.
  23000. * An XObject Template is a PDF block that is a self-contained description of any sequence of graphics objects (including path objects, text objects, and sampled images).
  23001. * An XObject Template may be painted multiple times, either on several pages or at several locations on the same page and produces the same results each time, subject only to the graphics state at the time it is invoked.
  23002. * @return int the XObject Template ID in case of success or false in case of error.
  23003. * @author Nicola Asuni
  23004. * @access public
  23005. * @since 5.8.017 (2010-08-24)
  23006. * @see startTemplate(), printTemplate()
  23007. */
  23008. public function endTemplate() {
  23009. if (!$this->inxobj) {
  23010. // we are not inside a template
  23011. return false;
  23012. }
  23013. $this->inxobj = false;
  23014. // restore previous graphic state
  23015. $this->setGraphicVars($this->xobjects[$this->xobjid]['gvars'], true);
  23016. return $this->xobjid;
  23017. }
  23018. /**
  23019. * Print an XObject Template.
  23020. * You can print an XObject Template inside the currently opened Template.
  23021. * An XObject Template is a PDF block that is a self-contained description of any sequence of graphics objects (including path objects, text objects, and sampled images).
  23022. * An XObject Template may be painted multiple times, either on several pages or at several locations on the same page and produces the same results each time, subject only to the graphics state at the time it is invoked.
  23023. * @param string $id The ID of XObject Template to print.
  23024. * @param int $x X position in user units (empty string = current x position)
  23025. * @param int $y Y position in user units (empty string = current y position)
  23026. * @param int $w Width in user units (zero = remaining page width)
  23027. * @param int $h Height in user units (zero = remaining page height)
  23028. * @param string $align Indicates the alignment of the pointer next to template insertion relative to template height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul>
  23029. * @param string $palign Allows to center or align the template on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
  23030. * @param boolean $fitonpage if true the template is resized to not exceed page dimensions.
  23031. * @author Nicola Asuni
  23032. * @access public
  23033. * @since 5.8.017 (2010-08-24)
  23034. * @see startTemplate(), endTemplate()
  23035. */
  23036. public function printTemplate($id, $x='', $y='', $w=0, $h=0, $align='', $palign='', $fitonpage=false) {
  23037. if (!isset($this->xobjects[$id])) {
  23038. $this->Error('The XObject Template \''.$id.'\' doesn\'t exist!');
  23039. }
  23040. if ($this->inxobj) {
  23041. if ($id == $this->xobjid) {
  23042. // close current template
  23043. $this->endTemplate();
  23044. } else {
  23045. // use the template as resource for the template currently opened
  23046. $this->xobjects[$this->xobjid]['xobjects'][$id] = $this->xobjects[$id];
  23047. }
  23048. }
  23049. // set default values
  23050. if ($x === '') {
  23051. $x = $this->x;
  23052. }
  23053. if ($y === '') {
  23054. $y = $this->y;
  23055. }
  23056. // check page for no-write regions and adapt page margins if necessary
  23057. $this->checkPageRegions($h, $x, $y);
  23058. $ow = $this->xobjects[$id]['w'];
  23059. $oh = $this->xobjects[$id]['h'];
  23060. // calculate template width and height on document
  23061. if (($w <= 0) AND ($h <= 0)) {
  23062. $w = $ow;
  23063. $h = $oh;
  23064. } elseif ($w <= 0) {
  23065. $w = $h * $ow / $oh;
  23066. } elseif ($h <= 0) {
  23067. $h = $w * $oh / $ow;
  23068. }
  23069. // fit the template on available space
  23070. $this->fitBlock($w, $h, $x, $y, $fitonpage);
  23071. // set page alignment
  23072. $rb_y = $y + $h;
  23073. // set alignment
  23074. if ($this->rtl) {
  23075. if ($palign == 'L') {
  23076. $xt = $this->lMargin;
  23077. } elseif ($palign == 'C') {
  23078. $xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
  23079. } elseif ($palign == 'R') {
  23080. $xt = $this->w - $this->rMargin - $w;
  23081. } else {
  23082. $xt = $x - $w;
  23083. }
  23084. $rb_x = $xt;
  23085. } else {
  23086. if ($palign == 'L') {
  23087. $xt = $this->lMargin;
  23088. } elseif ($palign == 'C') {
  23089. $xt = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
  23090. } elseif ($palign == 'R') {
  23091. $xt = $this->w - $this->rMargin - $w;
  23092. } else {
  23093. $xt = $x;
  23094. }
  23095. $rb_x = $xt + $w;
  23096. }
  23097. // print XObject Template + Transformation matrix
  23098. $this->StartTransform();
  23099. // translate and scale
  23100. $sx = ($w / $this->xobjects[$id]['w']);
  23101. $sy = ($h / $this->xobjects[$id]['h']);
  23102. $tm = array();
  23103. $tm[0] = $sx;
  23104. $tm[1] = 0;
  23105. $tm[2] = 0;
  23106. $tm[3] = $sy;
  23107. $tm[4] = $xt * $this->k;
  23108. $tm[5] = ($this->h - $h - $y) * $this->k;
  23109. $this->Transform($tm);
  23110. // set object
  23111. $this->_out('/'.$id.' Do');
  23112. $this->StopTransform();
  23113. // add annotations
  23114. if (!empty($this->xobjects[$id]['annotations'])) {
  23115. foreach ($this->xobjects[$id]['annotations'] as $annot) {
  23116. // transform original coordinates
  23117. $coordlt = $this->getTransformationMatrixProduct($tm, array(1, 0, 0, 1, ($annot['x'] * $this->k), (-$annot['y'] * $this->k)));
  23118. $ax = ($coordlt[4] / $this->k);
  23119. $ay = ($this->h - $h - ($coordlt[5] / $this->k));
  23120. $coordrb = $this->getTransformationMatrixProduct($tm, array(1, 0, 0, 1, (($annot['x'] + $annot['w']) * $this->k), ((-$annot['y'] - $annot['h']) * $this->k)));
  23121. $aw = ($coordrb[4] / $this->k) - $ax;
  23122. $ah = ($this->h - $h - ($coordrb[5] / $this->k)) - $ay;
  23123. $this->Annotation($ax, $ay, $aw, $ah, $annot['text'], $annot['opt'], $annot['spaces']);
  23124. }
  23125. }
  23126. // set pointer to align the next text/objects
  23127. switch($align) {
  23128. case 'T': {
  23129. $this->y = $y;
  23130. $this->x = $rb_x;
  23131. break;
  23132. }
  23133. case 'M': {
  23134. $this->y = $y + round($h/2);
  23135. $this->x = $rb_x;
  23136. break;
  23137. }
  23138. case 'B': {
  23139. $this->y = $rb_y;
  23140. $this->x = $rb_x;
  23141. break;
  23142. }
  23143. case 'N': {
  23144. $this->SetY($rb_y);
  23145. break;
  23146. }
  23147. default:{
  23148. break;
  23149. }
  23150. }
  23151. }
  23152. /**
  23153. * Set the percentage of character stretching.
  23154. * @param int $perc percentage of stretching (100 = no stretching)
  23155. * @author Nicola Asuni
  23156. * @access public
  23157. * @since 5.9.000 (2010-09-29)
  23158. */
  23159. public function setFontStretching($perc=100) {
  23160. $this->font_stretching = $perc;
  23161. }
  23162. /**
  23163. * Get the percentage of character stretching.
  23164. * @return float stretching value
  23165. * @author Nicola Asuni
  23166. * @access public
  23167. * @since 5.9.000 (2010-09-29)
  23168. */
  23169. public function getFontStretching() {
  23170. return $this->font_stretching;
  23171. }
  23172. /**
  23173. * Set the amount to increase or decrease the space between characters in a text.
  23174. * @param float $spacing amount to increase or decrease the space between characters in a text (0 = default spacing)
  23175. * @author Nicola Asuni
  23176. * @access public
  23177. * @since 5.9.000 (2010-09-29)
  23178. */
  23179. public function setFontSpacing($spacing=0) {
  23180. $this->font_spacing = $spacing;
  23181. }
  23182. /**
  23183. * Get the amount to increase or decrease the space between characters in a text.
  23184. * @return int font spacing (tracking/kerning) value
  23185. * @author Nicola Asuni
  23186. * @access public
  23187. * @since 5.9.000 (2010-09-29)
  23188. */
  23189. public function getFontSpacing() {
  23190. return $this->font_spacing;
  23191. }
  23192. /**
  23193. * Return an array of no-write page regions
  23194. * @return array of no-write page regions
  23195. * @author Nicola Asuni
  23196. * @access public
  23197. * @since 5.9.003 (2010-10-13)
  23198. * @see setPageRegions(), addPageRegion()
  23199. */
  23200. public function getPageRegions() {
  23201. return $this->page_regions;
  23202. }
  23203. /**
  23204. * Set no-write regions on page.
  23205. * A no-write region is a portion of the page with a rectangular or trapezium shape that will not be covered when writing text or html code.
  23206. * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
  23207. * You can set multiple regions for the same page.
  23208. * @param array $regions array of no-write regions. For each region you can define an array as follow: ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right). Omit this parameter to remove all regions.
  23209. * @author Nicola Asuni
  23210. * @access public
  23211. * @since 5.9.003 (2010-10-13)
  23212. * @see addPageRegion(), getPageRegions()
  23213. */
  23214. public function setPageRegions($regions=array()) {
  23215. // empty current regions array
  23216. $this->page_regions = array();
  23217. // add regions
  23218. foreach ($regions as $data) {
  23219. $this->addPageRegion($data);
  23220. }
  23221. }
  23222. /**
  23223. * Add a single no-write region on selected page.
  23224. * A no-write region is a portion of the page with a rectangular or trapezium shape that will not be covered when writing text or html code.
  23225. * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
  23226. * You can set multiple regions for the same page.
  23227. * @param array $region array of a single no-write region array: ('page' => page number or empy for current page, 'xt' => X top, 'yt' => Y top, 'xb' => X bottom, 'yb' => Y bottom, 'side' => page side 'L' = left or 'R' = right).
  23228. * @author Nicola Asuni
  23229. * @access public
  23230. * @since 5.9.003 (2010-10-13)
  23231. * @see setPageRegions(), getPageRegions()
  23232. */
  23233. public function addPageRegion($region) {
  23234. if (!isset($region['page']) OR empty($region['page'])) {
  23235. $region['page'] = $this->page;
  23236. }
  23237. if (isset($region['xt']) AND isset($region['xb']) AND ($region['xt'] > 0) AND ($region['xb'] > 0)
  23238. AND isset($region['yt']) AND isset($region['yb']) AND ($region['yt'] >= 0) AND ($region['yt'] < $region['yb'])
  23239. AND isset($region['side']) AND (($region['side'] == 'L') OR ($region['side'] == 'R'))) {
  23240. $this->page_regions[] = $region;
  23241. }
  23242. }
  23243. /**
  23244. * Remove a single no-write region.
  23245. * @param int $key region key
  23246. * @author Nicola Asuni
  23247. * @access public
  23248. * @since 5.9.003 (2010-10-13)
  23249. * @see setPageRegions(), getPageRegions()
  23250. */
  23251. public function removePageRegion($key) {
  23252. if (isset($this->page_regions[$key])) {
  23253. unset($this->page_regions[$key]);
  23254. }
  23255. }
  23256. /**
  23257. * Check page for no-write regions and adapt current coordinates and page margins if necessary.
  23258. * A no-write region is a portion of the page with a rectangular or trapezium shape that will not be covered when writing text or html code.
  23259. * A region is always aligned on the left or right side of the page ad is defined using a vertical segment.
  23260. * @param float $h height of the text/image/object to print in user units
  23261. * @param float $x current X coordinate in user units
  23262. * @param float $y current Y coordinate in user units
  23263. * @author Nicola Asuni
  23264. * @access protected
  23265. * @since 5.9.003 (2010-10-13)
  23266. */
  23267. protected function checkPageRegions($h=0, &$x='', &$y='') {
  23268. // set default values
  23269. if ($x === '') {
  23270. $x = &$this->x;
  23271. }
  23272. if ($y === '') {
  23273. $y = &$this->y;
  23274. }
  23275. if (empty($this->page_regions)) {
  23276. // no page regions defined
  23277. return;
  23278. }
  23279. if (empty($h)) {
  23280. $h = ($this->FontSize * $this->cell_height_ratio) + $this->cell_padding['T'] + $this->cell_padding['B'];
  23281. }
  23282. if ($this->rtl) {
  23283. $this->lMargin = $this->original_lMargin;
  23284. } else {
  23285. $this->rMargin = $this->original_rMargin;
  23286. }
  23287. if ($this->AutoPageBreak AND !$this->InFooter AND (($y + $h) > $this->PageBreakTrigger)) {
  23288. // the content will be printed on a new page
  23289. return;
  23290. }
  23291. // adjust coordinates and page margins
  23292. foreach ($this->page_regions as $regid => $regdata) {
  23293. if ($regdata['page'] == $this->page) {
  23294. // check region boundaries
  23295. if (($y > ($regdata['yt'] - $h)) AND ($y <= $regdata['yb'])) {
  23296. // Y is inside the region
  23297. $minv = ($regdata['xb'] - $regdata['xt']) / ($regdata['yb'] - $regdata['yt']); // inverse of angular coefficient
  23298. $yt = max($y, $regdata['yt']);
  23299. $yb = min(($yt + $h), $regdata['yb']);
  23300. $xt = (($yt - $regdata['yt']) * $minv) + $regdata['xt'];
  23301. $xb = (($yb - $regdata['yt']) * $minv) + $regdata['xt'];
  23302. if ($regdata['side'] == 'L') { // left side
  23303. $new_margin = max($xt, $xb);
  23304. if ($this->lMargin < $new_margin) {
  23305. if ($this->rtl) {
  23306. // adjust left page margin
  23307. $this->lMargin = $new_margin;
  23308. }
  23309. if ($x < $new_margin) {
  23310. // adjust x position
  23311. $x = $new_margin;
  23312. }
  23313. }
  23314. } elseif ($regdata['side'] == 'R') { // right side
  23315. $new_margin = min($xt, $xb);
  23316. if (($this->w - $this->rMargin) > $new_margin) {
  23317. if (!$this->rtl) {
  23318. // adjust right page margin
  23319. $this->rMargin = ($this->w - $new_margin);
  23320. }
  23321. if ($x > $new_margin) {
  23322. // adjust x position
  23323. $x = $new_margin;
  23324. }
  23325. }
  23326. }
  23327. }
  23328. }
  23329. }
  23330. }
  23331. // -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-
  23332. // SVG METHODS
  23333. // -.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-.-
  23334. /**
  23335. * Embedd a Scalable Vector Graphics (SVG) image.
  23336. * NOTE: SVG standard is not yet fully implemented, use the setRasterizeVectorImages() method to enable/disable rasterization of vector images using ImageMagick library.
  23337. * @param string $file Name of the SVG file.
  23338. * @param float $x Abscissa of the upper-left corner.
  23339. * @param float $y Ordinate of the upper-left corner.
  23340. * @param float $w Width of the image in the page. If not specified or equal to zero, it is automatically calculated.
  23341. * @param float $h Height of the image in the page. If not specified or equal to zero, it is automatically calculated.
  23342. * @param mixed $link URL or identifier returned by AddLink().
  23343. * @param string $align Indicates the alignment of the pointer next to image insertion relative to image height. The value can be:<ul><li>T: top-right for LTR or top-left for RTL</li><li>M: middle-right for LTR or middle-left for RTL</li><li>B: bottom-right for LTR or bottom-left for RTL</li><li>N: next line</li></ul> If the alignment is an empty string, then the pointer will be restored on the starting SVG position.
  23344. * @param string $palign Allows to center or align the image on the current line. Possible values are:<ul><li>L : left align</li><li>C : center</li><li>R : right align</li><li>'' : empty string : left for LTR or right for RTL</li></ul>
  23345. * @param mixed $border Indicates if borders must be drawn around the cell. The value can be a number:<ul><li>0: no border (default)</li><li>1: frame</li></ul> or a string containing some or all of the following characters (in any order):<ul><li>L: left</li><li>T: top</li><li>R: right</li><li>B: bottom</li></ul> or an array of line styles for each border group - for example: array('LTRB' => array('width' => 2, 'cap' => 'butt', 'join' => 'miter', 'dash' => 0, 'color' => array(0, 0, 0)))
  23346. * @param boolean $fitonpage if true the image is resized to not exceed page dimensions.
  23347. * @author Nicola Asuni
  23348. * @since 5.0.000 (2010-05-02)
  23349. * @access public
  23350. */
  23351. public function ImageSVG($file, $x='', $y='', $w=0, $h=0, $link='', $align='', $palign='', $border=0, $fitonpage=false) {
  23352. if ($this->rasterize_vector_images AND ($w > 0) AND ($h > 0)) {
  23353. // convert SVG to raster image using GD or ImageMagick libraries
  23354. return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
  23355. }
  23356. $this->svgdir = dirname($file);
  23357. $svgdata = file_get_contents($file);
  23358. if ($svgdata === false) {
  23359. $this->Error('SVG file not found: '.$file);
  23360. }
  23361. if ($x === '') {
  23362. $x = $this->x;
  23363. }
  23364. if ($y === '') {
  23365. $y = $this->y;
  23366. }
  23367. // check page for no-write regions and adapt page margins if necessary
  23368. $this->checkPageRegions($x, $y);
  23369. $k = $this->k;
  23370. $ox = 0;
  23371. $oy = 0;
  23372. $ow = $w;
  23373. $oh = $h;
  23374. $aspect_ratio_align = 'xMidYMid';
  23375. $aspect_ratio_ms = 'meet';
  23376. $regs = array();
  23377. // get original image width and height
  23378. preg_match('/<svg([^\>]*)>/si', $svgdata, $regs);
  23379. if (isset($regs[1]) AND !empty($regs[1])) {
  23380. $tmp = array();
  23381. if (preg_match('/[\s]+x[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
  23382. $ox = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
  23383. }
  23384. $tmp = array();
  23385. if (preg_match('/[\s]+y[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
  23386. $oy = $this->getHTMLUnitToUnits($tmp[1], 0, $this->svgunit, false);
  23387. }
  23388. $tmp = array();
  23389. if (preg_match('/[\s]+width[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
  23390. $ow = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
  23391. }
  23392. $tmp = array();
  23393. if (preg_match('/[\s]+height[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
  23394. $oh = $this->getHTMLUnitToUnits($tmp[1], 1, $this->svgunit, false);
  23395. }
  23396. $tmp = array();
  23397. $view_box = array();
  23398. if (preg_match('/[\s]+viewBox[\s]*=[\s]*"[\s]*([0-9\.\-]+)[\s]+([0-9\.\-]+)[\s]+([0-9\.]+)[\s]+([0-9\.]+)[\s]*"/si', $regs[1], $tmp)) {
  23399. if (count($tmp) == 5) {
  23400. array_shift($tmp);
  23401. foreach ($tmp as $key => $val) {
  23402. $view_box[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
  23403. }
  23404. $ox = $view_box[0];
  23405. $oy = $view_box[1];
  23406. }
  23407. // get aspect ratio
  23408. $tmp = array();
  23409. if (preg_match('/[\s]+preserveAspectRatio[\s]*=[\s]*"([^"]*)"/si', $regs[1], $tmp)) {
  23410. $aspect_ratio = preg_split('/[\s]+/si', $tmp[1]);
  23411. switch (count($aspect_ratio)) {
  23412. case 3: {
  23413. $aspect_ratio_align = $aspect_ratio[1];
  23414. $aspect_ratio_ms = $aspect_ratio[2];
  23415. break;
  23416. }
  23417. case 2: {
  23418. $aspect_ratio_align = $aspect_ratio[0];
  23419. $aspect_ratio_ms = $aspect_ratio[1];
  23420. break;
  23421. }
  23422. case 1: {
  23423. $aspect_ratio_align = $aspect_ratio[0];
  23424. $aspect_ratio_ms = 'meet';
  23425. break;
  23426. }
  23427. }
  23428. }
  23429. }
  23430. }
  23431. // calculate image width and height on document
  23432. if (($w <= 0) AND ($h <= 0)) {
  23433. // convert image size to document unit
  23434. $w = $ow;
  23435. $h = $oh;
  23436. } elseif ($w <= 0) {
  23437. $w = $h * $ow / $oh;
  23438. } elseif ($h <= 0) {
  23439. $h = $w * $oh / $ow;
  23440. }
  23441. // fit the image on available space
  23442. $this->fitBlock($w, $h, $x, $y, $fitonpage);
  23443. if ($this->rasterize_vector_images) {
  23444. // convert SVG to raster image using GD or ImageMagick libraries
  23445. return $this->Image($file, $x, $y, $w, $h, 'SVG', $link, $align, true, 300, $palign, false, false, $border, false, false, false);
  23446. }
  23447. // set alignment
  23448. $this->img_rb_y = $y + $h;
  23449. // set alignment
  23450. if ($this->rtl) {
  23451. if ($palign == 'L') {
  23452. $ximg = $this->lMargin;
  23453. } elseif ($palign == 'C') {
  23454. $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
  23455. } elseif ($palign == 'R') {
  23456. $ximg = $this->w - $this->rMargin - $w;
  23457. } else {
  23458. $ximg = $x - $w;
  23459. }
  23460. $this->img_rb_x = $ximg;
  23461. } else {
  23462. if ($palign == 'L') {
  23463. $ximg = $this->lMargin;
  23464. } elseif ($palign == 'C') {
  23465. $ximg = ($this->w + $this->lMargin - $this->rMargin - $w) / 2;
  23466. } elseif ($palign == 'R') {
  23467. $ximg = $this->w - $this->rMargin - $w;
  23468. } else {
  23469. $ximg = $x;
  23470. }
  23471. $this->img_rb_x = $ximg + $w;
  23472. }
  23473. // store current graphic vars
  23474. $gvars = $this->getGraphicVars();
  23475. // store SVG position and scale factors
  23476. $svgoffset_x = ($ximg - $ox) * $this->k;
  23477. $svgoffset_y = -($y - $oy) * $this->k;
  23478. if (isset($view_box[2]) AND ($view_box[2] > 0) AND ($view_box[3] > 0)) {
  23479. $ow = $view_box[2];
  23480. $oh = $view_box[3];
  23481. }
  23482. $svgscale_x = $w / $ow;
  23483. $svgscale_y = $h / $oh;
  23484. // scaling and alignment
  23485. if ($aspect_ratio_align != 'none') {
  23486. // store current scaling values
  23487. $svgscale_old_x = $svgscale_x;
  23488. $svgscale_old_y = $svgscale_y;
  23489. // force uniform scaling
  23490. if ($aspect_ratio_ms == 'slice') {
  23491. // the entire viewport is covered by the viewBox
  23492. if ($svgscale_x > $svgscale_y) {
  23493. $svgscale_y = $svgscale_x;
  23494. } elseif ($svgscale_x < $svgscale_y) {
  23495. $svgscale_x = $svgscale_y;
  23496. }
  23497. } else { // meet
  23498. // the entire viewBox is visible within the viewport
  23499. if ($svgscale_x < $svgscale_y) {
  23500. $svgscale_y = $svgscale_x;
  23501. } elseif ($svgscale_x > $svgscale_y) {
  23502. $svgscale_x = $svgscale_y;
  23503. }
  23504. }
  23505. // correct X alignment
  23506. switch (substr($aspect_ratio_align, 1, 3)) {
  23507. case 'Min': {
  23508. // do nothing
  23509. break;
  23510. }
  23511. case 'Max': {
  23512. $svgoffset_x += (($w * $this->k) - ($ow * $this->k * $svgscale_x));
  23513. break;
  23514. }
  23515. default:
  23516. case 'Mid': {
  23517. $svgoffset_x += ((($w * $this->k) - ($ow * $this->k * $svgscale_x)) / 2);
  23518. break;
  23519. }
  23520. }
  23521. // correct Y alignment
  23522. switch (substr($aspect_ratio_align, 5)) {
  23523. case 'Min': {
  23524. // do nothing
  23525. break;
  23526. }
  23527. case 'Max': {
  23528. $svgoffset_y -= (($h * $this->k) - ($oh * $this->k * $svgscale_y));
  23529. break;
  23530. }
  23531. default:
  23532. case 'Mid': {
  23533. $svgoffset_y -= ((($h * $this->k) - ($oh * $this->k * $svgscale_y)) / 2);
  23534. break;
  23535. }
  23536. }
  23537. }
  23538. // store current page break mode
  23539. $page_break_mode = $this->AutoPageBreak;
  23540. $page_break_margin = $this->getBreakMargin();
  23541. $cell_padding = $this->cell_padding;
  23542. $this->SetCellPadding(0);
  23543. $this->SetAutoPageBreak(false);
  23544. // save the current graphic state
  23545. $this->_out('q'.$this->epsmarker);
  23546. // set initial clipping mask
  23547. $this->Rect($x, $y, $w, $h, 'CNZ', array(), array());
  23548. // scale and translate
  23549. $e = $ox * $this->k * (1 - $svgscale_x);
  23550. $f = ($this->h - $oy) * $this->k * (1 - $svgscale_y);
  23551. $this->_out(sprintf('%.3F %.3F %.3F %.3F %.3F %.3F cm', $svgscale_x, 0, 0, $svgscale_y, $e + $svgoffset_x, $f + $svgoffset_y));
  23552. // creates a new XML parser to be used by the other XML functions
  23553. $this->parser = xml_parser_create('UTF-8');
  23554. // the following function allows to use parser inside object
  23555. xml_set_object($this->parser, $this);
  23556. // disable case-folding for this XML parser
  23557. xml_parser_set_option($this->parser, XML_OPTION_CASE_FOLDING, 0);
  23558. // sets the element handler functions for the XML parser
  23559. xml_set_element_handler($this->parser, 'startSVGElementHandler', 'endSVGElementHandler');
  23560. // sets the character data handler function for the XML parser
  23561. xml_set_character_data_handler($this->parser, 'segSVGContentHandler');
  23562. // start parsing an XML document
  23563. if(!xml_parse($this->parser, $svgdata)) {
  23564. $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));
  23565. $this->Error($error_message);
  23566. }
  23567. // free this XML parser
  23568. xml_parser_free($this->parser);
  23569. // restore previous graphic state
  23570. $this->_out($this->epsmarker.'Q');
  23571. // restore graphic vars
  23572. $this->setGraphicVars($gvars);
  23573. $this->lasth = $gvars['lasth'];
  23574. if (!empty($border)) {
  23575. $bx = $this->x;
  23576. $by = $this->y;
  23577. $this->x = $ximg;
  23578. if ($this->rtl) {
  23579. $this->x += $w;
  23580. }
  23581. $this->y = $y;
  23582. $this->Cell($w, $h, '', $border, 0, '', 0, '', 0, true);
  23583. $this->x = $bx;
  23584. $this->y = $by;
  23585. }
  23586. if ($link) {
  23587. $this->Link($ximg, $y, $w, $h, $link, 0);
  23588. }
  23589. // set pointer to align the next text/objects
  23590. switch($align) {
  23591. case 'T':{
  23592. $this->y = $y;
  23593. $this->x = $this->img_rb_x;
  23594. break;
  23595. }
  23596. case 'M':{
  23597. $this->y = $y + round($h/2);
  23598. $this->x = $this->img_rb_x;
  23599. break;
  23600. }
  23601. case 'B':{
  23602. $this->y = $this->img_rb_y;
  23603. $this->x = $this->img_rb_x;
  23604. break;
  23605. }
  23606. case 'N':{
  23607. $this->SetY($this->img_rb_y);
  23608. break;
  23609. }
  23610. default:{
  23611. // restore pointer to starting position
  23612. $this->x = $gvars['x'];
  23613. $this->y = $gvars['y'];
  23614. $this->page = $gvars['page'];
  23615. $this->current_column = $gvars['current_column'];
  23616. $this->tMargin = $gvars['tMargin'];
  23617. $this->bMargin = $gvars['bMargin'];
  23618. $this->w = $gvars['w'];
  23619. $this->h = $gvars['h'];
  23620. $this->wPt = $gvars['wPt'];
  23621. $this->hPt = $gvars['hPt'];
  23622. $this->fwPt = $gvars['fwPt'];
  23623. $this->fhPt = $gvars['fhPt'];
  23624. break;
  23625. }
  23626. }
  23627. $this->endlinex = $this->img_rb_x;
  23628. // restore page break
  23629. $this->SetAutoPageBreak($page_break_mode, $page_break_margin);
  23630. $this->cell_padding = $cell_padding;
  23631. }
  23632. /**
  23633. * Get the tranformation matrix from SVG transform attribute
  23634. * @param string transformation
  23635. * @return array of transformations
  23636. * @author Nicola Asuni
  23637. * @since 5.0.000 (2010-05-02)
  23638. * @access protected
  23639. */
  23640. protected function getSVGTransformMatrix($attribute) {
  23641. // identity matrix
  23642. $tm = array(1, 0, 0, 1, 0, 0);
  23643. $transform = array();
  23644. if (preg_match_all('/(matrix|translate|scale|rotate|skewX|skewY)[\s]*\(([^\)]+)\)/si', $attribute, $transform, PREG_SET_ORDER) > 0) {
  23645. foreach ($transform as $key => $data) {
  23646. if (!empty($data[2])) {
  23647. $a = 1;
  23648. $b = 0;
  23649. $c = 0;
  23650. $d = 1;
  23651. $e = 0;
  23652. $f = 0;
  23653. $regs = array();
  23654. switch ($data[1]) {
  23655. case 'matrix': {
  23656. 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)) {
  23657. $a = $regs[1];
  23658. $b = $regs[2];
  23659. $c = $regs[3];
  23660. $d = $regs[4];
  23661. $e = $regs[5];
  23662. $f = $regs[6];
  23663. }
  23664. break;
  23665. }
  23666. case 'translate': {
  23667. if (preg_match('/([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)/si', $data[2], $regs)) {
  23668. $e = $regs[1];
  23669. $f = $regs[2];
  23670. } elseif (preg_match('/([a-z0-9\-\.]+)/si', $data[2], $regs)) {
  23671. $e = $regs[1];
  23672. }
  23673. break;
  23674. }
  23675. case 'scale': {
  23676. if (preg_match('/([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)/si', $data[2], $regs)) {
  23677. $a = $regs[1];
  23678. $d = $regs[2];
  23679. } elseif (preg_match('/([a-z0-9\-\.]+)/si', $data[2], $regs)) {
  23680. $a = $regs[1];
  23681. $d = $a;
  23682. }
  23683. break;
  23684. }
  23685. case 'rotate': {
  23686. if (preg_match('/([0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)[\,\s]+([a-z0-9\-\.]+)/si', $data[2], $regs)) {
  23687. $ang = deg2rad($regs[1]);
  23688. $x = $regs[2];
  23689. $y = $regs[3];
  23690. $a = cos($ang);
  23691. $b = sin($ang);
  23692. $c = -$b;
  23693. $d = $a;
  23694. $e = ($x * (1 - $a)) - ($y * $c);
  23695. $f = ($y * (1 - $d)) - ($x * $b);
  23696. } elseif (preg_match('/([0-9\-\.]+)/si', $data[2], $regs)) {
  23697. $ang = deg2rad($regs[1]);
  23698. $a = cos($ang);
  23699. $b = sin($ang);
  23700. $c = -$b;
  23701. $d = $a;
  23702. $e = 0;
  23703. $f = 0;
  23704. }
  23705. break;
  23706. }
  23707. case 'skewX': {
  23708. if (preg_match('/([0-9\-\.]+)/si', $data[2], $regs)) {
  23709. $c = tan(deg2rad($regs[1]));
  23710. }
  23711. break;
  23712. }
  23713. case 'skewY': {
  23714. if (preg_match('/([0-9\-\.]+)/si', $data[2], $regs)) {
  23715. $b = tan(deg2rad($regs[1]));
  23716. }
  23717. break;
  23718. }
  23719. }
  23720. $tm = $this->getTransformationMatrixProduct($tm, array($a, $b, $c, $d, $e, $f));
  23721. }
  23722. }
  23723. }
  23724. return $tm;
  23725. }
  23726. /**
  23727. * Get the product of two SVG tranformation matrices
  23728. * @param array $ta first SVG tranformation matrix
  23729. * @param array $tb second SVG tranformation matrix
  23730. * @return transformation array
  23731. * @author Nicola Asuni
  23732. * @since 5.0.000 (2010-05-02)
  23733. * @access protected
  23734. */
  23735. protected function getTransformationMatrixProduct($ta, $tb) {
  23736. $tm = array();
  23737. $tm[0] = ($ta[0] * $tb[0]) + ($ta[2] * $tb[1]);
  23738. $tm[1] = ($ta[1] * $tb[0]) + ($ta[3] * $tb[1]);
  23739. $tm[2] = ($ta[0] * $tb[2]) + ($ta[2] * $tb[3]);
  23740. $tm[3] = ($ta[1] * $tb[2]) + ($ta[3] * $tb[3]);
  23741. $tm[4] = ($ta[0] * $tb[4]) + ($ta[2] * $tb[5]) + $ta[4];
  23742. $tm[5] = ($ta[1] * $tb[4]) + ($ta[3] * $tb[5]) + $ta[5];
  23743. return $tm;
  23744. }
  23745. /**
  23746. * Convert SVG transformation matrix to PDF.
  23747. * @param array $tm original SVG transformation matrix
  23748. * @return array transformation matrix
  23749. * @access protected
  23750. * @since 5.0.000 (2010-05-02)
  23751. */
  23752. protected function convertSVGtMatrix($tm) {
  23753. $a = $tm[0];
  23754. $b = -$tm[1];
  23755. $c = -$tm[2];
  23756. $d = $tm[3];
  23757. $e = $this->getHTMLUnitToUnits($tm[4], 1, $this->svgunit, false) * $this->k;
  23758. $f = -$this->getHTMLUnitToUnits($tm[5], 1, $this->svgunit, false) * $this->k;
  23759. $x = 0;
  23760. $y = $this->h * $this->k;
  23761. $e = ($x * (1 - $a)) - ($y * $c) + $e;
  23762. $f = ($y * (1 - $d)) - ($x * $b) + $f;
  23763. return array($a, $b, $c, $d, $e, $f);
  23764. }
  23765. /**
  23766. * Apply SVG graphic transformation matrix.
  23767. * @param array $tm original SVG transformation matrix
  23768. * @access protected
  23769. * @since 5.0.000 (2010-05-02)
  23770. */
  23771. protected function SVGTransform($tm) {
  23772. $this->Transform($this->convertSVGtMatrix($tm));
  23773. }
  23774. /**
  23775. * Apply the requested SVG styles (*** TO BE COMPLETED ***)
  23776. * @param array $svgstyle array of SVG styles to apply
  23777. * @param array $prevsvgstyle array of previous SVG style
  23778. * @param int $x X origin of the bounding box
  23779. * @param int $y Y origin of the bounding box
  23780. * @param int $w width of the bounding box
  23781. * @param int $h height of the bounding box
  23782. * @param string $clip_function clip function
  23783. * @param array $clip_params array of parameters for clipping function
  23784. * @return object style
  23785. * @author Nicola Asuni
  23786. * @since 5.0.000 (2010-05-02)
  23787. * @access protected
  23788. */
  23789. protected function setSVGStyles($svgstyle, $prevsvgstyle, $x=0, $y=0, $w=1, $h=1, $clip_function='', $clip_params=array()) {
  23790. $objstyle = '';
  23791. if(!isset($svgstyle['opacity'])) {
  23792. return $objstyle;
  23793. }
  23794. // clip-path
  23795. $regs = array();
  23796. if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['clip-path'], $regs)) {
  23797. $clip_path = $this->svgclippaths[$regs[1]];
  23798. foreach ($clip_path as $cp) {
  23799. $this->startSVGElementHandler('clip-path', $cp['name'], $cp['attribs'], $cp['tm']);
  23800. }
  23801. }
  23802. // opacity
  23803. if ($svgstyle['opacity'] != 1) {
  23804. $this->SetAlpha($svgstyle['opacity']);
  23805. }
  23806. // color
  23807. $fill_color = $this->convertHTMLColorToDec($svgstyle['color']);
  23808. $this->SetFillColorArray($fill_color);
  23809. // text color
  23810. $text_color = $this->convertHTMLColorToDec($svgstyle['text-color']);
  23811. $this->SetTextColorArray($text_color);
  23812. // clip
  23813. if (preg_match('/rect\(([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)[\s]*([a-z0-9\-\.]*)\)/si', $svgstyle['clip'], $regs)) {
  23814. $top = (isset($regs[1])?$this->getHTMLUnitToUnits($regs[1], 0, $this->svgunit, false):0);
  23815. $right = (isset($regs[2])?$this->getHTMLUnitToUnits($regs[2], 0, $this->svgunit, false):0);
  23816. $bottom = (isset($regs[3])?$this->getHTMLUnitToUnits($regs[3], 0, $this->svgunit, false):0);
  23817. $left = (isset($regs[4])?$this->getHTMLUnitToUnits($regs[4], 0, $this->svgunit, false):0);
  23818. $cx = $x + $left;
  23819. $cy = $y + $top;
  23820. $cw = $w - $left - $right;
  23821. $ch = $h - $top - $bottom;
  23822. if ($svgstyle['clip-rule'] == 'evenodd') {
  23823. $clip_rule = 'CNZ';
  23824. } else {
  23825. $clip_rule = 'CEO';
  23826. }
  23827. $this->Rect($cx, $cy, $cw, $ch, $clip_rule, array(), array());
  23828. }
  23829. // fill
  23830. $regs = array();
  23831. if (preg_match('/url\([\s]*\#([^\)]*)\)/si', $svgstyle['fill'], $regs)) {
  23832. // gradient
  23833. $gradient = $this->svggradients[$regs[1]];
  23834. if (isset($gradient['xref'])) {
  23835. // reference to another gradient definition
  23836. $newgradient = $this->svggradients[$gradient['xref']];
  23837. $newgradient['coords'] = $gradient['coords'];
  23838. $newgradient['mode'] = $gradient['mode'];
  23839. $newgradient['gradientUnits'] = $gradient['gradientUnits'];
  23840. if (isset($gradient['gradientTransform'])) {
  23841. $newgradient['gradientTransform'] = $gradient['gradientTransform'];
  23842. }
  23843. $gradient = $newgradient;
  23844. }
  23845. //save current Graphic State
  23846. $this->_out('q');
  23847. //set clipping area
  23848. if (!empty($clip_function) AND method_exists($this, $clip_function)) {
  23849. $bbox = call_user_func_array(array($this, $clip_function), $clip_params);
  23850. if (is_array($bbox) AND (count($bbox) == 4)) {
  23851. list($x, $y, $w, $h) = $bbox;
  23852. }
  23853. }
  23854. if ($gradient['mode'] == 'measure') {
  23855. if (isset($gradient['gradientTransform']) AND !empty($gradient['gradientTransform'])) {
  23856. $gtm = $gradient['gradientTransform'];
  23857. // apply transformation matrix
  23858. $xa = ($gtm[0] * $gradient['coords'][0]) + ($gtm[2] * $gradient['coords'][1]) + $gtm[4];
  23859. $ya = ($gtm[1] * $gradient['coords'][0]) + ($gtm[3] * $gradient['coords'][1]) + $gtm[5];
  23860. $xb = ($gtm[0] * $gradient['coords'][2]) + ($gtm[2] * $gradient['coords'][3]) + $gtm[4];
  23861. $yb = ($gtm[1] * $gradient['coords'][2]) + ($gtm[3] * $gradient['coords'][3]) + $gtm[5];
  23862. if (isset($gradient['coords'][4])) {
  23863. $gradient['coords'][4] = sqrt(pow(($gtm[0] * $gradient['coords'][4]), 2) + pow(($gtm[1] * $gradient['coords'][4]), 2));
  23864. }
  23865. $gradient['coords'][0] = $xa;
  23866. $gradient['coords'][1] = $ya;
  23867. $gradient['coords'][2] = $xb;
  23868. $gradient['coords'][3] = $yb;
  23869. }
  23870. // convert SVG coordinates to user units
  23871. $gradient['coords'][0] = $this->getHTMLUnitToUnits($gradient['coords'][0], 0, $this->svgunit, false);
  23872. $gradient['coords'][1] = $this->getHTMLUnitToUnits($gradient['coords'][1], 0, $this->svgunit, false);
  23873. $gradient['coords'][2] = $this->getHTMLUnitToUnits($gradient['coords'][2], 0, $this->svgunit, false);
  23874. $gradient['coords'][3] = $this->getHTMLUnitToUnits($gradient['coords'][3], 0, $this->svgunit, false);
  23875. if (isset($gradient['coords'][4])) {
  23876. $gradient['coords'][4] = $this->getHTMLUnitToUnits($gradient['coords'][4], 0, $this->svgunit, false);
  23877. }
  23878. // shift units
  23879. if ($gradient['gradientUnits'] == 'objectBoundingBox') {
  23880. // convert to SVG coordinate system
  23881. $gradient['coords'][0] += $x;
  23882. $gradient['coords'][1] += $y;
  23883. $gradient['coords'][2] += $x;
  23884. $gradient['coords'][3] += $y;
  23885. }
  23886. // calculate percentages
  23887. $gradient['coords'][0] = ($gradient['coords'][0] - $x) / $w;
  23888. $gradient['coords'][1] = ($gradient['coords'][1] - $y) / $h;
  23889. $gradient['coords'][2] = ($gradient['coords'][2] - $x) / $w;
  23890. $gradient['coords'][3] = ($gradient['coords'][3] - $y) / $h;
  23891. if (isset($gradient['coords'][4])) {
  23892. $gradient['coords'][4] /= $w;
  23893. }
  23894. // fix values
  23895. foreach($gradient['coords'] as $key => $val) {
  23896. if ($val < 0) {
  23897. $gradient['coords'][$key] = 0;
  23898. } elseif ($val > 1) {
  23899. $gradient['coords'][$key] = 1;
  23900. }
  23901. }
  23902. if (($gradient['type'] == 2) AND ($gradient['coords'][0] == $gradient['coords'][2]) AND ($gradient['coords'][1] == $gradient['coords'][3])) {
  23903. // single color (no shading)
  23904. $gradient['coords'][0] = 1;
  23905. $gradient['coords'][1] = 0;
  23906. $gradient['coords'][2] = 0.999;
  23907. $gradient['coords'][3] = 0;
  23908. }
  23909. }
  23910. // swap Y coordinates
  23911. $tmp = $gradient['coords'][1];
  23912. $gradient['coords'][1] = $gradient['coords'][3];
  23913. $gradient['coords'][3] = $tmp;
  23914. // set transformation map for gradient
  23915. if (($gradient['type'] == 3) AND ($gradient['mode'] == 'measure')) {
  23916. // gradient is always circular
  23917. $cy = $this->h - $y - ($gradient['coords'][1] * ($w + $h));
  23918. $this->_out(sprintf('%.3F 0 0 %.3F %.3F %.3F cm', $w*$this->k, $w*$this->k, $x*$this->k, $cy*$this->k));
  23919. } else {
  23920. $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));
  23921. }
  23922. if (count($gradient['stops']) > 1) {
  23923. $this->Gradient($gradient['type'], $gradient['coords'], $gradient['stops'], array(), false);
  23924. }
  23925. } elseif ($svgstyle['fill'] != 'none') {
  23926. $fill_color = $this->convertHTMLColorToDec($svgstyle['fill']);
  23927. if ($svgstyle['fill-opacity'] != 1) {
  23928. $this->SetAlpha($svgstyle['fill-opacity']);
  23929. }
  23930. $this->SetFillColorArray($fill_color);
  23931. if ($svgstyle['fill-rule'] == 'evenodd') {
  23932. $objstyle .= 'F*';
  23933. } else {
  23934. $objstyle .= 'F';
  23935. }
  23936. }
  23937. // stroke
  23938. if ($svgstyle['stroke'] != 'none') {
  23939. $stroke_style = array(
  23940. 'color' => $this->convertHTMLColorToDec($svgstyle['stroke']),
  23941. 'width' => $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false),
  23942. 'cap' => $svgstyle['stroke-linecap'],
  23943. 'join' => $svgstyle['stroke-linejoin']
  23944. );
  23945. if (isset($svgstyle['stroke-dasharray']) AND !empty($svgstyle['stroke-dasharray']) AND ($svgstyle['stroke-dasharray'] != 'none')) {
  23946. $stroke_style['dash'] = $svgstyle['stroke-dasharray'];
  23947. }
  23948. $this->SetLineStyle($stroke_style);
  23949. $objstyle .= 'D';
  23950. }
  23951. // font
  23952. $regs = array();
  23953. if (!empty($svgstyle['font'])) {
  23954. if (preg_match('/font-family[\s]*:[\s]*([^\;\"]*)/si', $svgstyle['font'], $regs)) {
  23955. $font_family = $this->getFontFamilyName($regs[1]);
  23956. } else {
  23957. $font_family = $svgstyle['font-family'];
  23958. }
  23959. if (preg_match('/font-size[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
  23960. $font_size = trim($regs[1]);
  23961. } else {
  23962. $font_size = $svgstyle['font-size'];
  23963. }
  23964. if (preg_match('/font-style[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
  23965. $font_style = trim($regs[1]);
  23966. } else {
  23967. $font_style = $svgstyle['font-style'];
  23968. }
  23969. if (preg_match('/font-weight[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
  23970. $font_weight = trim($regs[1]);
  23971. } else {
  23972. $font_weight = $svgstyle['font-weight'];
  23973. }
  23974. if (preg_match('/font-stretch[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
  23975. $font_stretch = trim($regs[1]);
  23976. } else {
  23977. $font_stretch = $svgstyle['font-stretch'];
  23978. }
  23979. if (preg_match('/letter-spacing[\s]*:[\s]*([^\s\;\"]*)/si', $svgstyle['font'], $regs)) {
  23980. $font_spacing = trim($regs[1]);
  23981. } else {
  23982. $font_spacing = $svgstyle['letter-spacing'];
  23983. }
  23984. } else {
  23985. $font_family = $this->getFontFamilyName($svgstyle['font-family']);
  23986. $font_size = $svgstyle['font-size'];
  23987. $font_style = $svgstyle['font-style'];
  23988. $font_weight = $svgstyle['font-weight'];
  23989. $font_stretch = $svgstyle['font-stretch'];
  23990. $font_spacing = $svgstyle['letter-spacing'];
  23991. }
  23992. $font_size = $this->getHTMLUnitToUnits($font_size, $prevsvgstyle['font-size'], $this->svgunit, false) * $this->k;
  23993. $font_stretch = $this->getCSSFontStretching($font_stretch, $svgstyle['font-stretch']);
  23994. $font_spacing = $this->getCSSFontSpacing($font_spacing, $svgstyle['letter-spacing']);
  23995. switch ($font_style) {
  23996. case 'italic': {
  23997. $font_style = 'I';
  23998. break;
  23999. }
  24000. case 'oblique': {
  24001. $font_style = 'I';
  24002. break;
  24003. }
  24004. default:
  24005. case 'normal': {
  24006. $font_style = '';
  24007. break;
  24008. }
  24009. }
  24010. switch ($font_weight) {
  24011. case 'bold':
  24012. case 'bolder': {
  24013. $font_style .= 'B';
  24014. break;
  24015. }
  24016. }
  24017. switch ($svgstyle['text-decoration']) {
  24018. case 'underline': {
  24019. $font_style .= 'U';
  24020. break;
  24021. }
  24022. case 'overline': {
  24023. $font_style .= 'O';
  24024. break;
  24025. }
  24026. case 'line-through': {
  24027. $font_style .= 'D';
  24028. break;
  24029. }
  24030. default:
  24031. case 'none': {
  24032. break;
  24033. }
  24034. }
  24035. $this->SetFont($font_family, $font_style, $font_size);
  24036. $this->setFontStretching($font_stretch);
  24037. $this->setFontSpacing($font_spacing);
  24038. return $objstyle;
  24039. }
  24040. /**
  24041. * Draws an SVG path
  24042. * @param string $d attribute d of the path SVG element
  24043. * @param string $style Style of rendering. Possible values are:
  24044. * <ul>
  24045. * <li>D or empty string: Draw (default).</li>
  24046. * <li>F: Fill.</li>
  24047. * <li>F*: Fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
  24048. * <li>DF or FD: Draw and fill.</li>
  24049. * <li>DF* or FD*: Draw and fill using the even-odd rule to determine which regions lie inside the clipping path.</li>
  24050. * <li>CNZ: Clipping mode (using the even-odd rule to determine which regions lie inside the clipping path).</li>
  24051. * <li>CEO: Clipping mode (using the nonzero winding number rule to determine which regions lie inside the clipping path).</li>
  24052. * </ul>
  24053. * @return array of container box measures (x, y, w, h)
  24054. * @author Nicola Asuni
  24055. * @since 5.0.000 (2010-05-02)
  24056. * @access protected
  24057. */
  24058. protected function SVGPath($d, $style='') {
  24059. // set fill/stroke style
  24060. $op = $this->getPathPaintOperator($style, '');
  24061. if (empty($op)) {
  24062. return;
  24063. }
  24064. $paths = array();
  24065. $d = str_replace('-', ' -', $d);
  24066. $d = str_replace('+', ' +', $d);
  24067. preg_match_all('/([a-zA-Z])[\s]*([^a-zA-Z\"]*)/si', $d, $paths, PREG_SET_ORDER);
  24068. $x = 0;
  24069. $y = 0;
  24070. $x1 = 0;
  24071. $y1 = 0;
  24072. $x2 = 0;
  24073. $y2 = 0;
  24074. $xmin = 2147483647;
  24075. $xmax = 0;
  24076. $ymin = 2147483647;
  24077. $ymax = 0;
  24078. $relcoord = false;
  24079. // draw curve pieces
  24080. foreach ($paths as $key => $val) {
  24081. // get curve type
  24082. $cmd = trim($val[1]);
  24083. if (strtolower($cmd) == $cmd) {
  24084. // use relative coordinated instead of absolute
  24085. $relcoord = true;
  24086. $xoffset = $x;
  24087. $yoffset = $y;
  24088. } else {
  24089. $relcoord = false;
  24090. $xoffset = 0;
  24091. $yoffset = 0;
  24092. }
  24093. $params = array();
  24094. if (isset($val[2])) {
  24095. // get curve parameters
  24096. $rawparams = preg_split('/([\,\s]+)/si', trim($val[2]));
  24097. $params = array();
  24098. foreach ($rawparams as $ck => $cp) {
  24099. $params[$ck] = $this->getHTMLUnitToUnits($cp, 0, $this->svgunit, false);
  24100. }
  24101. }
  24102. switch (strtoupper($cmd)) {
  24103. case 'M': { // moveto
  24104. foreach ($params as $ck => $cp) {
  24105. if (($ck % 2) == 0) {
  24106. $x = $cp + $xoffset;
  24107. } else {
  24108. $y = $cp + $yoffset;
  24109. if ($ck == 1) {
  24110. $this->_outPoint($x, $y);
  24111. } else {
  24112. $this->_outLine($x, $y);
  24113. }
  24114. $xmin = min($xmin, $x);
  24115. $ymin = min($ymin, $y);
  24116. $xmax = max($xmax, $x);
  24117. $ymax = max($ymax, $y);
  24118. if ($relcoord) {
  24119. $xoffset = $x;
  24120. $yoffset = $y;
  24121. }
  24122. }
  24123. }
  24124. break;
  24125. }
  24126. case 'L': { // lineto
  24127. foreach ($params as $ck => $cp) {
  24128. if (($ck % 2) == 0) {
  24129. $x = $cp + $xoffset;
  24130. } else {
  24131. $y = $cp + $yoffset;
  24132. $this->_outLine($x, $y);
  24133. $xmin = min($xmin, $x);
  24134. $ymin = min($ymin, $y);
  24135. $xmax = max($xmax, $x);
  24136. $ymax = max($ymax, $y);
  24137. if ($relcoord) {
  24138. $xoffset = $x;
  24139. $yoffset = $y;
  24140. }
  24141. }
  24142. }
  24143. break;
  24144. }
  24145. case 'H': { // horizontal lineto
  24146. foreach ($params as $ck => $cp) {
  24147. $x = $cp + $xoffset;
  24148. $this->_outLine($x, $y);
  24149. $xmin = min($xmin, $x);
  24150. $xmax = max($xmax, $x);
  24151. if ($relcoord) {
  24152. $xoffset = $x;
  24153. }
  24154. }
  24155. break;
  24156. }
  24157. case 'V': { // vertical lineto
  24158. foreach ($params as $ck => $cp) {
  24159. $y = $cp + $yoffset;
  24160. $this->_outLine($x, $y);
  24161. $ymin = min($ymin, $y);
  24162. $ymax = max($ymax, $y);
  24163. if ($relcoord) {
  24164. $yoffset = $y;
  24165. }
  24166. }
  24167. break;
  24168. }
  24169. case 'C': { // curveto
  24170. foreach ($params as $ck => $cp) {
  24171. $params[$ck] = $cp;
  24172. if ((($ck + 1) % 6) == 0) {
  24173. $x1 = $params[($ck - 5)] + $xoffset;
  24174. $y1 = $params[($ck - 4)] + $yoffset;
  24175. $x2 = $params[($ck - 3)] + $xoffset;
  24176. $y2 = $params[($ck - 2)] + $yoffset;
  24177. $x = $params[($ck - 1)] + $xoffset;
  24178. $y = $params[($ck)] + $yoffset;
  24179. $this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
  24180. $xmin = min($xmin, $x, $x1, $x2);
  24181. $ymin = min($ymin, $y, $y1, $y2);
  24182. $xmax = max($xmax, $x, $x1, $x2);
  24183. $ymax = max($ymax, $y, $y1, $y2);
  24184. if ($relcoord) {
  24185. $xoffset = $x;
  24186. $yoffset = $y;
  24187. }
  24188. }
  24189. }
  24190. break;
  24191. }
  24192. case 'S': { // shorthand/smooth curveto
  24193. foreach ($params as $ck => $cp) {
  24194. $params[$ck] = $cp;
  24195. if ((($ck + 1) % 4) == 0) {
  24196. if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'C') OR (strtoupper($paths[($key - 1)][1]) == 'S'))) {
  24197. $x1 = (2 * $x) - $x2;
  24198. $y1 = (2 * $y) - $y2;
  24199. } else {
  24200. $x1 = $x;
  24201. $y1 = $y;
  24202. }
  24203. $x2 = $params[($ck - 3)] + $xoffset;
  24204. $y2 = $params[($ck - 2)] + $yoffset;
  24205. $x = $params[($ck - 1)] + $xoffset;
  24206. $y = $params[($ck)] + $yoffset;
  24207. $this->_outCurve($x1, $y1, $x2, $y2, $x, $y);
  24208. $xmin = min($xmin, $x, $x1, $x2);
  24209. $ymin = min($ymin, $y, $y1, $y2);
  24210. $xmax = max($xmax, $x, $x1, $x2);
  24211. $ymax = max($ymax, $y, $y1, $y2);
  24212. if ($relcoord) {
  24213. $xoffset = $x;
  24214. $yoffset = $y;
  24215. }
  24216. }
  24217. }
  24218. break;
  24219. }
  24220. case 'Q': { // quadratic Bézier curveto
  24221. foreach ($params as $ck => $cp) {
  24222. $params[$ck] = $cp;
  24223. if ((($ck + 1) % 4) == 0) {
  24224. // convert quadratic points to cubic points
  24225. $x1 = $params[($ck - 3)] + $xoffset;
  24226. $y1 = $params[($ck - 2)] + $yoffset;
  24227. $xa = ($x + (2 * $x1)) / 3;
  24228. $ya = ($y + (2 * $y1)) / 3;
  24229. $x = $params[($ck - 1)] + $xoffset;
  24230. $y = $params[($ck)] + $yoffset;
  24231. $xb = ($x + (2 * $x1)) / 3;
  24232. $yb = ($y + (2 * $y1)) / 3;
  24233. $this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
  24234. $xmin = min($xmin, $x, $xa, $xb);
  24235. $ymin = min($ymin, $y, $ya, $yb);
  24236. $xmax = max($xmax, $x, $xa, $xb);
  24237. $ymax = max($ymax, $y, $ya, $yb);
  24238. if ($relcoord) {
  24239. $xoffset = $x;
  24240. $yoffset = $y;
  24241. }
  24242. }
  24243. }
  24244. break;
  24245. }
  24246. case 'T': { // shorthand/smooth quadratic Bézier curveto
  24247. foreach ($params as $ck => $cp) {
  24248. $params[$ck] = $cp;
  24249. if (($ck % 2) != 0) {
  24250. if (($key > 0) AND ((strtoupper($paths[($key - 1)][1]) == 'Q') OR (strtoupper($paths[($key - 1)][1]) == 'T'))) {
  24251. $x1 = (2 * $x) - $x1;
  24252. $y1 = (2 * $y) - $y1;
  24253. } else {
  24254. $x1 = $x;
  24255. $y1 = $y;
  24256. }
  24257. // convert quadratic points to cubic points
  24258. $xa = ($x + (2 * $x1)) / 3;
  24259. $ya = ($y + (2 * $y1)) / 3;
  24260. $x = $params[($ck - 1)] + $xoffset;
  24261. $y = $params[($ck)] + $yoffset;
  24262. $xb = ($x + (2 * $x1)) / 3;
  24263. $yb = ($y + (2 * $y1)) / 3;
  24264. $this->_outCurve($xa, $ya, $xb, $yb, $x, $y);
  24265. $xmin = min($xmin, $x, $x1, $x2);
  24266. $ymin = min($ymin, $y, $y1, $y2);
  24267. $xmax = max($xmax, $x, $x1, $x2);
  24268. $ymax = max($ymax, $y, $y1, $y2);
  24269. if ($relcoord) {
  24270. $xoffset = $x;
  24271. $yoffset = $y;
  24272. }
  24273. }
  24274. }
  24275. break;
  24276. }
  24277. case 'A': { // elliptical arc
  24278. foreach ($params as $ck => $cp) {
  24279. $params[$ck] = $cp;
  24280. if ((($ck + 1) % 7) == 0) {
  24281. $x0 = $x;
  24282. $y0 = $y;
  24283. $rx = abs($params[($ck - 6)]);
  24284. $ry = abs($params[($ck - 5)]);
  24285. $ang = -$rawparams[($ck - 4)];
  24286. $angle = deg2rad($ang);
  24287. $fa = $rawparams[($ck - 3)]; // large-arc-flag
  24288. $fs = $rawparams[($ck - 2)]; // sweep-flag
  24289. $x = $params[($ck - 1)] + $xoffset;
  24290. $y = $params[$ck] + $yoffset;
  24291. $cos_ang = cos($angle);
  24292. $sin_ang = sin($angle);
  24293. $a = ($x0 - $x) / 2;
  24294. $b = ($y0 - $y) / 2;
  24295. $xa = ($a * $cos_ang) - ($b * $sin_ang);
  24296. $ya = ($a * $sin_ang) + ($b * $cos_ang);
  24297. $rx2 = $rx * $rx;
  24298. $ry2 = $ry * $ry;
  24299. $xa2 = $xa * $xa;
  24300. $ya2 = $ya * $ya;
  24301. $delta = ($xa2 / $rx2) + ($ya2 / $ry2);
  24302. if ($delta > 1) {
  24303. $rx *= sqrt($delta);
  24304. $ry *= sqrt($delta);
  24305. $rx2 = $rx * $rx;
  24306. $ry2 = $ry * $ry;
  24307. }
  24308. $numerator = (($rx2 * $ry2) - ($rx2 * $ya2) - ($ry2 * $xa2));
  24309. if ($numerator < 0) {
  24310. $root = 0;
  24311. } else {
  24312. $root = sqrt($numerator / (($rx2 * $ya2) + ($ry2 * $xa2)));
  24313. }
  24314. if ($fa == $fs) {
  24315. $root *= -1;
  24316. }
  24317. $cax = $root * (($rx * $ya) / $ry);
  24318. $cay = -$root * (($ry * $xa) / $rx);
  24319. // coordinates of ellipse center
  24320. $cx = ($cax * $cos_ang) - ($cay * $sin_ang) + (($x0 + $x) / 2);
  24321. $cy = ($cax * $sin_ang) + ($cay * $cos_ang) + (($y0 + $y) / 2);
  24322. // get angles
  24323. $angs = $this->getVectorsAngle(1, 0, (($xa - $cax) / $rx), (($cay - $ya) / $ry));
  24324. $dang = $this->getVectorsAngle((($xa - $cax) / $rx), (($ya - $cay) / $ry), ((-$xa - $cax) / $rx), ((-$ya - $cay) / $ry));
  24325. if (($fs == 0) AND ($dang > 0)) {
  24326. $dang -= (2 * M_PI);
  24327. } elseif (($fs == 1) AND ($dang < 0)) {
  24328. $dang += (2 * M_PI);
  24329. }
  24330. $angf = $angs - $dang;
  24331. if (($fs == 1) AND ($angs > $angf)) {
  24332. $tmp = $angs;
  24333. $angs = $angf;
  24334. $angf = $tmp;
  24335. }
  24336. $angs = rad2deg($angs);
  24337. $angf = rad2deg($angf);
  24338. $pie = false;
  24339. if ((isset($paths[($key + 1)][1])) AND (trim($paths[($key + 1)][1]) == 'z')) {
  24340. $pie = true;
  24341. }
  24342. $this->_outellipticalarc($cx, $cy, $rx, $ry, $ang, $angs, $angf, $pie, 2);
  24343. $this->_outPoint($x, $y);
  24344. $xmin = min($xmin, $x);
  24345. $ymin = min($ymin, $y);
  24346. $xmax = max($xmax, $x);
  24347. $ymax = max($ymax, $y);
  24348. if ($relcoord) {
  24349. $xoffset = $x;
  24350. $yoffset = $y;
  24351. }
  24352. }
  24353. }
  24354. break;
  24355. }
  24356. case 'Z': {
  24357. $this->_out('h');
  24358. break;
  24359. }
  24360. }
  24361. } // end foreach
  24362. if (!empty($op)) {
  24363. $this->_out($op);
  24364. }
  24365. return array($xmin, $ymin, ($xmax - $xmin), ($ymax - $ymin));
  24366. }
  24367. /**
  24368. * Returns the angle in radiants between two vectors
  24369. * @param int $x1 X coordiante of first vector point
  24370. * @param int $y1 Y coordiante of first vector point
  24371. * @param int $x2 X coordiante of second vector point
  24372. * @param int $y2 Y coordiante of second vector point
  24373. * @author Nicola Asuni
  24374. * @since 5.0.000 (2010-05-04)
  24375. * @access protected
  24376. */
  24377. protected function getVectorsAngle($x1, $y1, $x2, $y2) {
  24378. $dprod = ($x1 * $x2) + ($y1 * $y2);
  24379. $dist1 = sqrt(($x1 * $x1) + ($y1 * $y1));
  24380. $dist2 = sqrt(($x2 * $x2) + ($y2 * $y2));
  24381. $angle = acos($dprod / ($dist1 * $dist2));
  24382. if (is_nan($angle)) {
  24383. $angle = M_PI;
  24384. }
  24385. if ((($x1 * $y2) - ($x2 * $y1)) < 0) {
  24386. $angle *= -1;
  24387. }
  24388. return $angle;
  24389. }
  24390. /**
  24391. * Sets the opening SVG element handler function for the XML parser. (*** TO BE COMPLETED ***)
  24392. * @param resource $parser The first parameter, parser, is a reference to the XML parser calling the handler.
  24393. * @param string $name The second parameter, name, contains the name of the element for which this handler is called. If case-folding is in effect for this parser, the element name will be in uppercase letters.
  24394. * @param array $attribs The third parameter, attribs, contains an associative array with the element's attributes (if any). The keys of this array are the attribute names, the values are the attribute values. Attribute names are case-folded on the same criteria as element names. Attribute values are not case-folded. The original order of the attributes can be retrieved by walking through attribs the normal way, using each(). The first key in the array was the first attribute, and so on.
  24395. * @param array $ctm tranformation matrix for clipping mode (starting transformation matrix).
  24396. * @author Nicola Asuni
  24397. * @since 5.0.000 (2010-05-02)
  24398. * @access protected
  24399. */
  24400. protected function startSVGElementHandler($parser, $name, $attribs, $ctm=array()) {
  24401. // check if we are in clip mode
  24402. if ($this->svgclipmode) {
  24403. $this->svgclippaths[$this->svgclipid][] = array('name' => $name, 'attribs' => $attribs, 'tm' => $this->svgcliptm[$this->svgclipid]);
  24404. return;
  24405. }
  24406. if ($this->svgdefsmode AND !in_array($name, array('clipPath', 'linearGradient', 'radialGradient', 'stop'))) {
  24407. if (!isset($attribs['id'])) {
  24408. $attribs['id'] = 'DF_'.(count($this->svgdefs) + 1);
  24409. }
  24410. $this->svgdefs[$attribs['id']] = array('name' => $name, 'attribs' => $attribs);
  24411. return;
  24412. }
  24413. $clipping = false;
  24414. if ($parser == 'clip-path') {
  24415. // set clipping mode
  24416. $clipping = true;
  24417. }
  24418. // get styling properties
  24419. $prev_svgstyle = $this->svgstyles[(count($this->svgstyles) - 1)]; // previous style
  24420. $svgstyle = $this->svgstyles[0]; // set default style
  24421. if (isset($attribs['style']) AND !$this->empty_string($attribs['style'])) {
  24422. // fix style for regular expression
  24423. $attribs['style'] = ';'.$attribs['style'];
  24424. }
  24425. foreach ($prev_svgstyle as $key => $val) {
  24426. if (in_array($key, $this->svginheritprop)) {
  24427. // inherit previous value
  24428. $svgstyle[$key] = $val;
  24429. }
  24430. if (isset($attribs[$key]) AND !$this->empty_string($attribs[$key])) {
  24431. // specific attribute settings
  24432. if ($attribs[$key] == 'inherit') {
  24433. $svgstyle[$key] = $val;
  24434. } else {
  24435. $svgstyle[$key] = $attribs[$key];
  24436. }
  24437. } elseif (isset($attribs['style']) AND !$this->empty_string($attribs['style'])) {
  24438. // CSS style syntax
  24439. $attrval = array();
  24440. if (preg_match('/[;\"\s]{1}'.$key.'[\s]*:[\s]*([^;\"]*)/si', $attribs['style'], $attrval) AND isset($attrval[1])) {
  24441. if ($attrval[1] == 'inherit') {
  24442. $svgstyle[$key] = $val;
  24443. } else {
  24444. $svgstyle[$key] = $attrval[1];
  24445. }
  24446. }
  24447. }
  24448. }
  24449. // transformation matrix
  24450. if (!empty($ctm)) {
  24451. $tm = $ctm;
  24452. } else {
  24453. $tm = $this->svgstyles[(count($this->svgstyles) - 1)]['transfmatrix'];
  24454. }
  24455. if (isset($attribs['transform']) AND !empty($attribs['transform'])) {
  24456. $tm = $this->getTransformationMatrixProduct($tm, $this->getSVGTransformMatrix($attribs['transform']));
  24457. }
  24458. $svgstyle['transfmatrix'] = $tm;
  24459. $invisible = false;
  24460. if (($svgstyle['visibility'] == 'hidden') OR ($svgstyle['visibility'] == 'collapse') OR ($svgstyle['display'] == 'none')) {
  24461. // the current graphics element is invisible (nothing is painted)
  24462. $invisible = true;
  24463. }
  24464. // process tag
  24465. switch($name) {
  24466. case 'defs': {
  24467. $this->svgdefsmode = true;
  24468. break;
  24469. }
  24470. // clipPath
  24471. case 'clipPath': {
  24472. if ($invisible) {
  24473. break;
  24474. }
  24475. $this->svgclipmode = true;
  24476. if (!isset($attribs['id'])) {
  24477. $attribs['id'] = 'CP_'.(count($this->svgcliptm) + 1);
  24478. }
  24479. $this->svgclipid = $attribs['id'];
  24480. $this->svgclippaths[$this->svgclipid] = array();
  24481. $this->svgcliptm[$this->svgclipid] = $tm;
  24482. break;
  24483. }
  24484. case 'svg': {
  24485. // start of SVG object
  24486. break;
  24487. }
  24488. case 'g': {
  24489. // group together related graphics elements
  24490. array_push($this->svgstyles, $svgstyle);
  24491. $this->StartTransform();
  24492. $this->setSVGStyles($svgstyle, $prev_svgstyle);
  24493. break;
  24494. }
  24495. case 'linearGradient': {
  24496. if (!isset($attribs['id'])) {
  24497. $attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
  24498. }
  24499. $this->svggradientid = $attribs['id'];
  24500. $this->svggradients[$this->svggradientid] = array();
  24501. $this->svggradients[$this->svggradientid]['type'] = 2;
  24502. $this->svggradients[$this->svggradientid]['stops'] = array();
  24503. if (isset($attribs['gradientUnits'])) {
  24504. $this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
  24505. } else {
  24506. $this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
  24507. }
  24508. //$attribs['spreadMethod']
  24509. $x1 = (isset($attribs['x1'])?$attribs['x1']:0);
  24510. $y1 = (isset($attribs['y1'])?$attribs['y1']:0);
  24511. $x2 = (isset($attribs['x2'])?$attribs['x2']:1);
  24512. $y2 = (isset($attribs['y2'])?$attribs['y2']:0);
  24513. if (isset($attribs['x1']) AND (substr($attribs['x1'], -1) != '%')) {
  24514. $this->svggradients[$this->svggradientid]['mode'] = 'measure';
  24515. } else {
  24516. $this->svggradients[$this->svggradientid]['mode'] = 'percentage';
  24517. }
  24518. if (isset($attribs['gradientTransform'])) {
  24519. $this->svggradients[$this->svggradientid]['gradientTransform'] = $this->getSVGTransformMatrix($attribs['gradientTransform']);
  24520. }
  24521. $this->svggradients[$this->svggradientid]['coords'] = array($x1, $y1, $x2, $y2);
  24522. if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
  24523. // gradient is defined on another place
  24524. $this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
  24525. }
  24526. break;
  24527. }
  24528. case 'radialGradient': {
  24529. if (!isset($attribs['id'])) {
  24530. $attribs['id'] = 'GR_'.(count($this->svggradients) + 1);
  24531. }
  24532. $this->svggradientid = $attribs['id'];
  24533. $this->svggradients[$this->svggradientid] = array();
  24534. $this->svggradients[$this->svggradientid]['type'] = 3;
  24535. $this->svggradients[$this->svggradientid]['stops'] = array();
  24536. if (isset($attribs['gradientUnits'])) {
  24537. $this->svggradients[$this->svggradientid]['gradientUnits'] = $attribs['gradientUnits'];
  24538. } else {
  24539. $this->svggradients[$this->svggradientid]['gradientUnits'] = 'objectBoundingBox';
  24540. }
  24541. //$attribs['spreadMethod']
  24542. $cx = (isset($attribs['cx'])?$attribs['cx']:0.5);
  24543. $cy = (isset($attribs['cy'])?$attribs['cy']:0.5);
  24544. $fx = (isset($attribs['fx'])?$attribs['fx']:$cx);
  24545. $fy = (isset($attribs['fy'])?$attribs['fy']:$cy);
  24546. $r = (isset($attribs['r'])?$attribs['r']:0.5);
  24547. if (isset($attribs['cx']) AND (substr($attribs['cx'], -1) != '%')) {
  24548. $this->svggradients[$this->svggradientid]['mode'] = 'measure';
  24549. } else {
  24550. $this->svggradients[$this->svggradientid]['mode'] = 'percentage';
  24551. }
  24552. if (isset($attribs['gradientTransform'])) {
  24553. $this->svggradients[$this->svggradientid]['gradientTransform'] = $this->getSVGTransformMatrix($attribs['gradientTransform']);
  24554. }
  24555. $this->svggradients[$this->svggradientid]['coords'] = array($cx, $cy, $fx, $fy, $r);
  24556. if (isset($attribs['xlink:href']) AND !empty($attribs['xlink:href'])) {
  24557. // gradient is defined on another place
  24558. $this->svggradients[$this->svggradientid]['xref'] = substr($attribs['xlink:href'], 1);
  24559. }
  24560. break;
  24561. }
  24562. case 'stop': {
  24563. // gradient stops
  24564. if (substr($attribs['offset'], -1) == '%') {
  24565. $offset = floatval(substr($attribs['offset'], -1)) / 100;
  24566. } else {
  24567. $offset = floatval($attribs['offset']);
  24568. if ($offset > 1) {
  24569. $offset /= 100;
  24570. }
  24571. }
  24572. $stop_color = isset($svgstyle['stop-color'])?$this->convertHTMLColorToDec($svgstyle['stop-color']):'black';
  24573. $opacity = isset($svgstyle['stop-opacity'])?$svgstyle['stop-opacity']:1;
  24574. $this->svggradients[$this->svggradientid]['stops'][] = array('offset' => $offset, 'color' => $stop_color, 'opacity' => $opacity);
  24575. break;
  24576. }
  24577. // paths
  24578. case 'path': {
  24579. if ($invisible) {
  24580. break;
  24581. }
  24582. $d = trim($attribs['d']);
  24583. if ($clipping) {
  24584. $this->SVGTransform($tm);
  24585. $this->SVGPath($d, 'CNZ');
  24586. } else {
  24587. $this->StartTransform();
  24588. $this->SVGTransform($tm);
  24589. $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, 0, 0, 1, 1, 'SVGPath', array($d, 'CNZ'));
  24590. if (!empty($obstyle)) {
  24591. $this->SVGPath($d, $obstyle);
  24592. }
  24593. $this->StopTransform();
  24594. }
  24595. break;
  24596. }
  24597. // shapes
  24598. case 'rect': {
  24599. if ($invisible) {
  24600. break;
  24601. }
  24602. $x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
  24603. $y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
  24604. $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
  24605. $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
  24606. $rx = (isset($attribs['rx'])?$this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false):0);
  24607. $ry = (isset($attribs['ry'])?$this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false):$rx);
  24608. if ($clipping) {
  24609. $this->SVGTransform($tm);
  24610. $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ', array(), array());
  24611. } else {
  24612. $this->StartTransform();
  24613. $this->SVGTransform($tm);
  24614. $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'RoundedRectXY', array($x, $y, $w, $h, $rx, $ry, '1111', 'CNZ'));
  24615. if (!empty($obstyle)) {
  24616. $this->RoundedRectXY($x, $y, $w, $h, $rx, $ry, '1111', $obstyle, array(), array());
  24617. }
  24618. $this->StopTransform();
  24619. }
  24620. break;
  24621. }
  24622. case 'circle': {
  24623. if ($invisible) {
  24624. break;
  24625. }
  24626. $cx = (isset($attribs['cx'])?$this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false):0);
  24627. $cy = (isset($attribs['cy'])?$this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false):0);
  24628. $r = (isset($attribs['r'])?$this->getHTMLUnitToUnits($attribs['r'], 0, $this->svgunit, false):0);
  24629. $x = $cx - $r;
  24630. $y = $cy - $r;
  24631. $w = 2 * $r;
  24632. $h = $w;
  24633. if ($clipping) {
  24634. $this->SVGTransform($tm);
  24635. $this->Circle($cx, $cy, $r, 0, 360, 'CNZ', array(), array(), 8);
  24636. } else {
  24637. $this->StartTransform();
  24638. $this->SVGTransform($tm);
  24639. $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Circle', array($cx, $cy, $r, 0, 360, 'CNZ'));
  24640. if (!empty($obstyle)) {
  24641. $this->Circle($cx, $cy, $r, 0, 360, $obstyle, array(), array(), 8);
  24642. }
  24643. $this->StopTransform();
  24644. }
  24645. break;
  24646. }
  24647. case 'ellipse': {
  24648. if ($invisible) {
  24649. break;
  24650. }
  24651. $cx = (isset($attribs['cx'])?$this->getHTMLUnitToUnits($attribs['cx'], 0, $this->svgunit, false):0);
  24652. $cy = (isset($attribs['cy'])?$this->getHTMLUnitToUnits($attribs['cy'], 0, $this->svgunit, false):0);
  24653. $rx = (isset($attribs['rx'])?$this->getHTMLUnitToUnits($attribs['rx'], 0, $this->svgunit, false):0);
  24654. $ry = (isset($attribs['ry'])?$this->getHTMLUnitToUnits($attribs['ry'], 0, $this->svgunit, false):0);
  24655. $x = $cx - $rx;
  24656. $y = $cy - $ry;
  24657. $w = 2 * $rx;
  24658. $h = 2 * $ry;
  24659. if ($clipping) {
  24660. $this->SVGTransform($tm);
  24661. $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ', array(), array(), 8);
  24662. } else {
  24663. $this->StartTransform();
  24664. $this->SVGTransform($tm);
  24665. $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Ellipse', array($cx, $cy, $rx, $ry, 0, 0, 360, 'CNZ'));
  24666. if (!empty($obstyle)) {
  24667. $this->Ellipse($cx, $cy, $rx, $ry, 0, 0, 360, $obstyle, array(), array(), 8);
  24668. }
  24669. $this->StopTransform();
  24670. }
  24671. break;
  24672. }
  24673. case 'line': {
  24674. if ($invisible) {
  24675. break;
  24676. }
  24677. $x1 = (isset($attribs['x1'])?$this->getHTMLUnitToUnits($attribs['x1'], 0, $this->svgunit, false):0);
  24678. $y1 = (isset($attribs['y1'])?$this->getHTMLUnitToUnits($attribs['y1'], 0, $this->svgunit, false):0);
  24679. $x2 = (isset($attribs['x2'])?$this->getHTMLUnitToUnits($attribs['x2'], 0, $this->svgunit, false):0);
  24680. $y2 = (isset($attribs['y2'])?$this->getHTMLUnitToUnits($attribs['y2'], 0, $this->svgunit, false):0);
  24681. $x = $x1;
  24682. $y = $y1;
  24683. $w = abs($x2 - $x1);
  24684. $h = abs($y2 - $y1);
  24685. if (!$clipping) {
  24686. $this->StartTransform();
  24687. $this->SVGTransform($tm);
  24688. $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Line', array($x1, $y1, $x2, $y2));
  24689. $this->Line($x1, $y1, $x2, $y2);
  24690. $this->StopTransform();
  24691. }
  24692. break;
  24693. }
  24694. case 'polyline':
  24695. case 'polygon': {
  24696. if ($invisible) {
  24697. break;
  24698. }
  24699. $points = (isset($attribs['points'])?$attribs['points']:'0 0');
  24700. $points = trim($points);
  24701. // note that point may use a complex syntax not covered here
  24702. $points = preg_split('/[\,\s]+/si', $points);
  24703. if (count($points) < 4) {
  24704. break;
  24705. }
  24706. $p = array();
  24707. $xmin = 2147483647;
  24708. $xmax = 0;
  24709. $ymin = 2147483647;
  24710. $ymax = 0;
  24711. foreach ($points as $key => $val) {
  24712. $p[$key] = $this->getHTMLUnitToUnits($val, 0, $this->svgunit, false);
  24713. if (($key % 2) == 0) {
  24714. // X coordinate
  24715. $xmin = min($xmin, $p[$key]);
  24716. $xmax = max($xmax, $p[$key]);
  24717. } else {
  24718. // Y coordinate
  24719. $ymin = min($ymin, $p[$key]);
  24720. $ymax = max($ymax, $p[$key]);
  24721. }
  24722. }
  24723. $x = $xmin;
  24724. $y = $ymin;
  24725. $w = ($xmax - $xmin);
  24726. $h = ($ymax - $ymin);
  24727. if ($name == 'polyline') {
  24728. $this->StartTransform();
  24729. $this->SVGTransform($tm);
  24730. $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'PolyLine', array($p, 'CNZ'));
  24731. $this->PolyLine($p, 'D', array(), array());
  24732. $this->StopTransform();
  24733. } else { // polygon
  24734. if ($clipping) {
  24735. $this->SVGTransform($tm);
  24736. $this->Polygon($p, 'CNZ', array(), array(), true);
  24737. } else {
  24738. $this->StartTransform();
  24739. $this->SVGTransform($tm);
  24740. $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h, 'Polygon', array($p, 'CNZ'));
  24741. if (!empty($obstyle)) {
  24742. $this->Polygon($p, $obstyle, array(), array(), true);
  24743. }
  24744. $this->StopTransform();
  24745. }
  24746. }
  24747. break;
  24748. }
  24749. // image
  24750. case 'image': {
  24751. if ($invisible) {
  24752. break;
  24753. }
  24754. if (!isset($attribs['xlink:href']) OR empty($attribs['xlink:href'])) {
  24755. break;
  24756. }
  24757. $x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):0);
  24758. $y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):0);
  24759. $w = (isset($attribs['width'])?$this->getHTMLUnitToUnits($attribs['width'], 0, $this->svgunit, false):0);
  24760. $h = (isset($attribs['height'])?$this->getHTMLUnitToUnits($attribs['height'], 0, $this->svgunit, false):0);
  24761. $img = $attribs['xlink:href'];
  24762. if (!$clipping) {
  24763. $this->StartTransform();
  24764. $this->SVGTransform($tm);
  24765. $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, $w, $h);
  24766. // fix image path
  24767. if (!$this->empty_string($this->svgdir) AND (($img{0} == '.') OR (basename($img) == $img))) {
  24768. // replace relative path with full server path
  24769. $img = $this->svgdir.'/'.$img;
  24770. }
  24771. if (($img{0} == '/') AND ($_SERVER['DOCUMENT_ROOT'] != '/')) {
  24772. $findroot = strpos($img, $_SERVER['DOCUMENT_ROOT']);
  24773. if (($findroot === false) OR ($findroot > 1)) {
  24774. // replace relative path with full server path
  24775. $img = $_SERVER['DOCUMENT_ROOT'].$img;
  24776. }
  24777. }
  24778. $img = urldecode($img);
  24779. $testscrtype = @parse_url($img);
  24780. if (!isset($testscrtype['query']) OR empty($testscrtype['query'])) {
  24781. // convert URL to server path
  24782. $img = str_replace(K_PATH_URL, K_PATH_MAIN, $img);
  24783. }
  24784. $this->Image($img, $x, $y, $w, $h);
  24785. $this->StopTransform();
  24786. }
  24787. break;
  24788. }
  24789. // text
  24790. case 'text':
  24791. case 'tspan': {
  24792. $this->svgtextmode['invisible'] = $invisible;
  24793. if ($invisible) {
  24794. break;
  24795. }
  24796. array_push($this->svgstyles, $svgstyle);
  24797. // only basic support - advanced features must be implemented
  24798. $x = (isset($attribs['x'])?$this->getHTMLUnitToUnits($attribs['x'], 0, $this->svgunit, false):$this->x);
  24799. $y = (isset($attribs['y'])?$this->getHTMLUnitToUnits($attribs['y'], 0, $this->svgunit, false):$this->y);
  24800. $svgstyle['text-color'] = $svgstyle['fill'];
  24801. $this->svgtext = '';
  24802. if (isset($svgstyle['text-anchor'])) {
  24803. $this->svgtextmode['text-anchor'] = $svgstyle['text-anchor'];
  24804. } else {
  24805. $this->svgtextmode['text-anchor'] = 'start';
  24806. }
  24807. if (isset($svgstyle['direction'])) {
  24808. if ($svgstyle['direction'] == 'rtl') {
  24809. $this->svgtextmode['rtl'] = true;
  24810. } else {
  24811. $this->svgtextmode['rtl'] = false;
  24812. }
  24813. } else {
  24814. $this->svgtextmode['rtl'] = false;
  24815. }
  24816. if (isset($svgstyle['stroke']) AND ($svgstyle['stroke'] != 'none') AND isset($svgstyle['stroke-width']) AND ($svgstyle['stroke-width'] > 0)) {
  24817. $this->svgtextmode['stroke'] = $this->getHTMLUnitToUnits($svgstyle['stroke-width'], 0, $this->svgunit, false);
  24818. } else {
  24819. $this->svgtextmode['stroke'] = false;
  24820. }
  24821. $this->StartTransform();
  24822. $this->SVGTransform($tm);
  24823. $obstyle = $this->setSVGStyles($svgstyle, $prev_svgstyle, $x, $y, 1, 1);
  24824. $this->x = $x;
  24825. $this->y = $y;
  24826. break;
  24827. }
  24828. // use
  24829. case 'use': {
  24830. if (isset($attribs['xlink:href'])) {
  24831. $use = $this->svgdefs[substr($attribs['xlink:href'], 1)];
  24832. if (isset($attribs['xlink:href'])) {
  24833. unset($attribs['xlink:href']);
  24834. }
  24835. if (isset($attribs['id'])) {
  24836. unset($attribs['id']);
  24837. }
  24838. $attribs = array_merge($use['attribs'], $attribs);
  24839. $this->startSVGElementHandler($parser, $use['name'], $use['attribs']);
  24840. }
  24841. break;
  24842. }
  24843. default: {
  24844. break;
  24845. }
  24846. } // end of switch
  24847. }
  24848. /**
  24849. * Sets the closing SVG element handler function for the XML parser.
  24850. * @param resource $parser The first parameter, parser, is a reference to the XML parser calling the handler.
  24851. * @param string $name The second parameter, name, contains the name of the element for which this handler is called. If case-folding is in effect for this parser, the element name will be in uppercase letters.
  24852. * @author Nicola Asuni
  24853. * @since 5.0.000 (2010-05-02)
  24854. * @access protected
  24855. */
  24856. protected function endSVGElementHandler($parser, $name) {
  24857. switch($name) {
  24858. case 'defs': {
  24859. $this->svgdefsmode = false;
  24860. break;
  24861. }
  24862. // clipPath
  24863. case 'clipPath': {
  24864. $this->svgclipmode = false;
  24865. break;
  24866. }
  24867. case 'g': {
  24868. // ungroup: remove last style from array
  24869. array_pop($this->svgstyles);
  24870. $this->StopTransform();
  24871. break;
  24872. }
  24873. case 'text':
  24874. case 'tspan': {
  24875. if ($this->svgtextmode['invisible']) {
  24876. // This implementation must be fixed to following the rule:
  24877. // 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.
  24878. break;
  24879. }
  24880. // print text
  24881. $text = $this->stringTrim($this->svgtext);
  24882. if ($this->svgtextmode['text-anchor'] != 'start') {
  24883. $textlen = $this->GetStringWidth($text);
  24884. // check if string is RTL text
  24885. if ($this->svgtextmode['text-anchor'] == 'end') {
  24886. if ($this->svgtextmode['rtl']) {
  24887. $this->x += $textlen;
  24888. } else {
  24889. $this->x -= $textlen;
  24890. }
  24891. } elseif ($this->svgtextmode['text-anchor'] == 'middle') {
  24892. if ($this->svgtextmode['rtl']) {
  24893. $this->x += ($textlen / 2);
  24894. } else {
  24895. $this->x -= ($textlen / 2);
  24896. }
  24897. }
  24898. }
  24899. $textrendermode = $this->textrendermode;
  24900. $textstrokewidth = $this->textstrokewidth;
  24901. $this->setTextRenderingMode($this->svgtextmode['stroke'], true, false);
  24902. $this->Cell(0, 0, $text, 0, 0, '', false, '', 0, false, 'L', 'T');
  24903. // restore previous rendering mode
  24904. $this->textrendermode = $textrendermode;
  24905. $this->textstrokewidth = $textstrokewidth;
  24906. $this->svgtext = '';
  24907. $this->StopTransform();
  24908. array_pop($this->svgstyles);
  24909. break;
  24910. }
  24911. default: {
  24912. break;
  24913. }
  24914. }
  24915. }
  24916. /**
  24917. * Sets the character data handler function for the XML parser.
  24918. * @param resource $parser The first parameter, parser, is a reference to the XML parser calling the handler.
  24919. * @param string $data The second parameter, data, contains the character data as a string.
  24920. * @author Nicola Asuni
  24921. * @since 5.0.000 (2010-05-02)
  24922. * @access protected
  24923. */
  24924. protected function segSVGContentHandler($parser, $data) {
  24925. $this->svgtext .= $data;
  24926. }
  24927. // --- END SVG METHODS -----------------------------
  24928. } // END OF TCPDF CLASS
  24929. //============================================================+
  24930. // END OF FILE
  24931. //============================================================+