/html2pdf.class.php

https://github.com/aotd1/html2pdf · PHP · 6610 lines · 3927 code · 984 blank · 1699 comment · 752 complexity · cf9fafffcd82c2501c9376be98100fcf MD5 · raw file

Large files are truncated click here to view the full file

  1. <?php
  2. /**
  3. * HTML2PDF Librairy - main class
  4. *
  5. * HTML => PDF convertor
  6. * distributed under the LGPL License
  7. *
  8. * @author Laurent MINGUET <webmaster@html2pdf.fr>
  9. * @version 4.03
  10. */
  11. if (!defined('__CLASS_HTML2PDF__')) {
  12. define('__CLASS_HTML2PDF__', '4.03');
  13. define('HTML2PDF_USED_TCPDF_VERSION', '5.9.206');
  14. require_once(dirname(__FILE__).'/_class/exception.class.php');
  15. require_once(dirname(__FILE__).'/_class/locale.class.php');
  16. require_once(dirname(__FILE__).'/_class/myPdf.class.php');
  17. require_once(dirname(__FILE__).'/_class/parsingHtml.class.php');
  18. require_once(dirname(__FILE__).'/_class/parsingCss.class.php');
  19. class HTML2PDF
  20. {
  21. /**
  22. * HTML2PDF_myPdf object, extends from TCPDF
  23. * @var HTML2PDF_myPdf
  24. */
  25. public $pdf = null;
  26. /**
  27. * CSS parsing
  28. * @var HTML2PDF_parsingCss
  29. */
  30. public $parsingCss = null;
  31. /**
  32. * HTML parsing
  33. * @var HTML2PDF_parsingHtml
  34. */
  35. public $parsingHtml = null;
  36. protected $_langue = 'fr'; // locale of the messages
  37. protected $_orientation = 'P'; // page orientation : Portrait ou Landscape
  38. protected $_format = 'A4'; // page format : A4, A3, ...
  39. protected $_encoding = ''; // charset encoding
  40. protected $_unicode = true; // means that the input text is unicode (default = true)
  41. protected $_testTdInOnepage = true; // test of TD that can not take more than one page
  42. protected $_testIsImage = true; // test if the images exist or not
  43. protected $_testIsDeprecated = false; // test the deprecated functions
  44. protected $_parsePos = 0; // position in the parsing
  45. protected $_tempPos = 0; // temporary position for complex table
  46. protected $_page = 0; // current page number
  47. protected $_subHtml = null; // sub html
  48. protected $_subPart = false; // sub HTML2PDF
  49. protected $_subHEADER = array(); // sub action to make the header
  50. protected $_subFOOTER = array(); // sub action to make the footer
  51. protected $_subSTATES = array(); // array to save some parameters
  52. protected $_isSubPart = false; // flag : in a sub html2pdf
  53. protected $_isInThead = false; // flag : in a thead
  54. protected $_isInTfoot = false; // flag : in a tfoot
  55. protected $_isInOverflow = false; // flag : in a overflow
  56. protected $_isInFooter = false; // flag : in a footer
  57. protected $_isInDraw = null; // flag : in a draw (svg)
  58. protected $_isAfterFloat = false; // flag : is just after a float
  59. protected $_isInForm = false; // flag : is in a float. false / action of the form
  60. protected $_isInLink = ''; // flag : is in a link. empty / href of the link
  61. protected $_isInParagraph = false; // flag : is in a paragraph
  62. protected $_isForOneLine = false; // flag : in a specific sub html2pdf to have the height of the next line
  63. protected $_maxX = 0; // maximum X of the current zone
  64. protected $_maxY = 0; // maximum Y of the current zone
  65. protected $_maxE = 0; // number of elements in the current zone
  66. protected $_maxH = 0; // maximum height of the line in the current zone
  67. protected $_maxSave = array(); // save the maximums of the current zone
  68. protected $_currentH = 0; // height of the current line
  69. protected $_defaultLeft = 0; // default marges of the page
  70. protected $_defaultTop = 0;
  71. protected $_defaultRight = 0;
  72. protected $_defaultBottom = 0;
  73. protected $_defaultFont = null; // default font to use, is the asked font does not exist
  74. protected $_margeLeft = 0; // current marges of the page
  75. protected $_margeTop = 0;
  76. protected $_margeRight = 0;
  77. protected $_margeBottom = 0;
  78. protected $_marges = array(); // save the different marges of the current page
  79. protected $_pageMarges = array(); // float marges of the current page
  80. protected $_background = array(); // background informations
  81. protected $_firstPage = true; // flag : first page
  82. protected $_defList = array(); // table to save the stats of the tags UL and OL
  83. protected $_lstAnchor = array(); // list of the anchors
  84. protected $_lstField = array(); // list of the fields
  85. protected $_lstSelect = array(); // list of the options of the current select
  86. protected $_previousCall = null; // last action called
  87. protected $_debugActif = false; // flag : mode debug is active
  88. protected $_debugOkUsage = false; // flag : the function memory_get_usage exist
  89. protected $_debugOkPeak = false; // flag : the function memory_get_peak_usage exist
  90. protected $_debugLevel = 0; // level in the debug
  91. protected $_debugStartTime = 0; // debug start time
  92. protected $_debugLastTime = 0; // debug stop time
  93. static protected $_subobj = null; // object html2pdf prepared in order to accelerate the creation of sub html2pdf
  94. static protected $_tables = array(); // static table to prepare the nested html tables
  95. /**
  96. * class constructor
  97. *
  98. * @access public
  99. * @param string $orientation page orientation, same as TCPDF
  100. * @param mixed $format The format used for pages, same as TCPDF
  101. * @param $tring $langue Langue : fr, en, it...
  102. * @param boolean $unicode TRUE means that the input text is unicode (default = true)
  103. * @param String $encoding charset encoding; default is UTF-8
  104. * @param array $marges Default marges (left, top, right, bottom)
  105. * @return HTML2PDF $this
  106. */
  107. public function __construct($orientation = 'P', $format = 'A4', $langue='fr', $unicode=true, $encoding='UTF-8', $marges = array(5, 5, 5, 8))
  108. {
  109. // init the page number
  110. $this->_page = 0;
  111. $this->_firstPage = true;
  112. $this->_firstPageInSet = array();
  113. $this->_isCalculationPass = true;
  114. $this->_rootPathForURLs = '';
  115. // save the parameters
  116. $this->_orientation = $orientation;
  117. $this->_format = $format;
  118. $this->_langue = strtolower($langue);
  119. $this->_unicode = $unicode;
  120. $this->_encoding = $encoding;
  121. // load the Local
  122. HTML2PDF_locale::load($this->_langue);
  123. // create the HTML2PDF_myPdf object
  124. $this->pdf = new HTML2PDF_myPdf($orientation, 'mm', $format, $unicode, $encoding);
  125. // init the CSS parsing object
  126. $this->parsingCss = new HTML2PDF_parsingCss($this->pdf);
  127. $this->parsingCss->fontSet();
  128. $this->_defList = array();
  129. // init some tests
  130. $this->setTestTdInOnePage(true);
  131. $this->setTestIsImage(true);
  132. $this->setTestIsDeprecated(true);
  133. // init the default font
  134. $this->setDefaultFont(null);
  135. // init the HTML parsing object
  136. $this->parsingHtml = new HTML2PDF_parsingHtml($this->_encoding);
  137. $this->_subHtml = null;
  138. $this->_subPart = false;
  139. // init the marges of the page
  140. if (!is_array($marges)) $marges = array($marges, $marges, $marges, $marges);
  141. $this->_setDefaultMargins($marges[0], $marges[1], $marges[2], $marges[3]);
  142. $this->_setMargins();
  143. $this->_marges = array();
  144. // init the form's fields
  145. $this->_lstField = array();
  146. return $this;
  147. }
  148. /**
  149. * Destructor
  150. *
  151. * @access public
  152. * @return null
  153. */
  154. public function __destruct()
  155. {
  156. }
  157. /**
  158. * Clone to create a sub HTML2PDF from HTML2PDF::$_subobj
  159. *
  160. * @access public
  161. */
  162. public function __clone()
  163. {
  164. $this->pdf = clone $this->pdf;
  165. $this->parsingHtml = clone $this->parsingHtml;
  166. $this->parsingCss = clone $this->parsingCss;
  167. $this->parsingCss->setPdfParent($this->pdf);
  168. }
  169. /**
  170. * set the debug mode to On
  171. *
  172. * @access public
  173. * @return HTML2PDF $this
  174. */
  175. public function setModeDebug()
  176. {
  177. $time = microtime(true);
  178. $this->_debugActif = true;
  179. $this->_debugOkUsage = function_exists('memory_get_usage');
  180. $this->_debugOkPeak = function_exists('memory_get_peak_usage');
  181. $this->_debugStartTime = $time;
  182. $this->_debugLastTime = $time;
  183. $this->_DEBUG_stepline('step', 'time', 'delta', 'memory', 'peak');
  184. $this->_DEBUG_add('Init debug');
  185. return $this;
  186. }
  187. /**
  188. * Set the test of TD thdat can not take more than one page
  189. *
  190. * @access public
  191. * @param boolean $mode
  192. * @return HTML2PDF $this
  193. */
  194. public function setTestTdInOnePage($mode = true)
  195. {
  196. $this->_testTdInOnepage = $mode ? true : false;
  197. return $this;
  198. }
  199. /**
  200. * Set the test if the images exist or not
  201. *
  202. * @access public
  203. * @param boolean $mode
  204. * @return HTML2PDF $this
  205. */
  206. public function setTestIsImage($mode = true)
  207. {
  208. $this->_testIsImage = $mode ? true : false;
  209. return $this;
  210. }
  211. /**
  212. * Set the test on deprecated functions
  213. *
  214. * @access public
  215. * @param boolean $mode
  216. * @return HTML2PDF $this
  217. */
  218. public function setTestIsDeprecated($mode = true)
  219. {
  220. $this->_testIsDeprecated = $mode ? true : false;
  221. return $this;
  222. }
  223. /**
  224. * Set the default font to use, if no font is specify, or if the asked font does not exist
  225. *
  226. * @access public
  227. * @param string $default name of the default font to use. If null : Arial is no font is specify, and error if the asked font does not exist
  228. * @return HTML2PDF $this
  229. */
  230. public function setDefaultFont($default = null)
  231. {
  232. $this->_defaultFont = $default;
  233. $this->parsingCss->setDefaultFont($default);
  234. return $this;
  235. }
  236. /**
  237. * add a font, see TCPDF function addFont
  238. *
  239. * @access public
  240. * @param string $family Font family. The name can be chosen arbitrarily. If it is a standard family name, it will override the corresponding font.
  241. * @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>
  242. * @param string $fontfile The font definition file. By default, the name is built from the family and style, in lower case with no spaces.
  243. * @return HTML2PDF $this
  244. * @see TCPDF::addFont
  245. */
  246. public function addFont($family, $style='', $file='')
  247. {
  248. $this->pdf->AddFont($family, $style, $file);
  249. return $this;
  250. }
  251. /**
  252. * display a automatic index, from the bookmarks
  253. *
  254. * @access public
  255. * @param string $titre index title
  256. * @param int $sizeTitle font size of the index title, in mm
  257. * @param int $sizeBookmark font size of the index, in mm
  258. * @param boolean $bookmarkTitle add a bookmark for the index, at his beginning
  259. * @param boolean $displayPage display the page numbers
  260. * @param int $onPage if null : at the end of the document on a new page, else on the $onPage page
  261. * @param string $fontName font name to use
  262. * @return null
  263. */
  264. public function createIndex($titre = 'Index', $sizeTitle = 20, $sizeBookmark = 15, $bookmarkTitle = true, $displayPage = true, $onPage = null, $fontName = 'helvetica')
  265. {
  266. $oldPage = $this->_INDEX_NewPage($onPage);
  267. $this->pdf->createIndex($this, $titre, $sizeTitle, $sizeBookmark, $bookmarkTitle, $displayPage, $onPage, $fontName);
  268. if ($oldPage) $this->pdf->setPage($oldPage);
  269. }
  270. /**
  271. * clean up the objects
  272. *
  273. * @access protected
  274. */
  275. protected function _cleanUp()
  276. {
  277. HTML2PDF::$_subobj = null;
  278. HTML2PDF::$_tables = array();
  279. }
  280. /**
  281. * Send the document to a given destination: string, local file or browser.
  282. * Dest can be :
  283. * 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.
  284. * D : send to the browser and force a file download with the name given by name.
  285. * F : save to a local server file with the name given by name.
  286. * S : return the document as a string. name is ignored.
  287. * FI: equivalent to F + I option
  288. * FD: equivalent to F + D option
  289. * true => I
  290. * false => S
  291. *
  292. * @param string $name The name of the file when saved.
  293. * @param string $dest Destination where to send the document.
  294. * @return string content of the PDF, if $dest=S
  295. * @see TCPDF::close
  296. * @access public
  297. */
  298. public function Output($name = '', $dest = false)
  299. {
  300. // close the pdf and clean up
  301. $this->_cleanUp();
  302. // if on debug mode
  303. if ($this->_debugActif) {
  304. $this->_DEBUG_add('Before output');
  305. $this->pdf->Close();
  306. exit;
  307. }
  308. // complete parameters
  309. if ($dest===false) $dest = 'I';
  310. if ($dest===true) $dest = 'S';
  311. if ($dest==='') $dest = 'I';
  312. if ($name=='') $name='document.pdf';
  313. // clean up the destination
  314. $dest = strtoupper($dest);
  315. if (!in_array($dest, array('I', 'D', 'F', 'S', 'FI','FD'))) $dest = 'I';
  316. // the name must be a PDF name
  317. if (strtolower(substr($name, -4))!='.pdf') {
  318. throw new HTML2PDF_exception(0, 'The output document name "'.$name.'" is not a PDF name');
  319. }
  320. // call the output of TCPDF
  321. return $this->pdf->Output($name, $dest);
  322. }
  323. /**
  324. * convert HTML to PDF
  325. *
  326. * @access public
  327. * @param string $html
  328. * @param boolean $debugVue enable the HTML debug vue
  329. * @return null
  330. */
  331. public function writeHTML($html, $debugVue = false)
  332. {
  333. // if it is a real html page, we have to convert it
  334. if (preg_match('/<body/isU', $html))
  335. $html = $this->getHtmlFromPage($html);
  336. $html = str_replace('[[date_y]]', date('Y'), $html);
  337. $html = str_replace('[[date_m]]', date('m'), $html);
  338. $html = str_replace('[[date_d]]', date('d'), $html);
  339. $html = str_replace('[[date_h]]', date('H'), $html);
  340. $html = str_replace('[[date_i]]', date('i'), $html);
  341. $html = str_replace('[[date_s]]', date('s'), $html);
  342. // If we are in HTML debug vue : display the HTML
  343. if ($debugVue) {
  344. return $this->_vueHTML($html);
  345. }
  346. // convert HTMl to PDF
  347. $this->parsingCss->readStyle($html);
  348. $this->parsingHtml->setHTML($html);
  349. $this->parsingHtml->parse();
  350. $this->_makeHTMLcode();
  351. }
  352. /**
  353. * convert the HTML of a real page, to a code adapted to HTML2PDF
  354. *
  355. * @access public
  356. * @param string HTML of a real page
  357. * @return string HTML adapted to HTML2PDF
  358. */
  359. public function getHtmlFromPage($html)
  360. {
  361. $html = str_replace('<BODY', '<body', $html);
  362. $html = str_replace('</BODY', '</body', $html);
  363. // extract the content
  364. $res = explode('<body', $html);
  365. if (count($res)<2) return $html;
  366. $content = '<page'.$res[1];
  367. $content = explode('</body', $content);
  368. $content = $content[0].'</page>';
  369. // extract the link tags
  370. preg_match_all('/<link([^>]*)>/isU', $html, $match);
  371. foreach ($match[0] as $src)
  372. $content = $src.'</link>'.$content;
  373. // extract the css style tags
  374. preg_match_all('/<style[^>]*>(.*)<\/style[^>]*>/isU', $html, $match);
  375. foreach ($match[0] as $src)
  376. $content = $src.$content;
  377. return $content;
  378. }
  379. /**
  380. * init a sub HTML2PDF. does not use it directly. Only the method createSubHTML must use it
  381. *
  382. * @access public
  383. * @param string $format
  384. * @param string $orientation
  385. * @param array $marge
  386. * @param integer $page
  387. * @param array $defLIST
  388. * @param integer $myLastPageGroup
  389. * @param integer $myLastPageGroupNb
  390. */
  391. public function initSubHtml($format, $orientation, $marge, $page, $defLIST, $myLastPageGroup, $myLastPageGroupNb)
  392. {
  393. $this->_isSubPart = true;
  394. $this->parsingCss->setOnlyLeft();
  395. $this->_setNewPage($format, $orientation, null, null, ($myLastPageGroup!==null));
  396. $this->_saveMargin(0, 0, $marge);
  397. $this->_defList = $defLIST;
  398. $this->_page = $page;
  399. $this->pdf->setMyLastPageGroup($myLastPageGroup);
  400. $this->pdf->setMyLastPageGroupNb($myLastPageGroupNb);
  401. $this->pdf->setXY(0, 0);
  402. $this->parsingCss->fontSet();
  403. }
  404. /**
  405. * display the content in HTML moden for debug
  406. *
  407. * @access protected
  408. * @param string $contenu
  409. */
  410. protected function _vueHTML($content)
  411. {
  412. $content = preg_replace('/<page_header([^>]*)>/isU', '<hr>'.HTML2PDF_locale::get('vue01').' : $1<hr><div$1>', $content);
  413. $content = preg_replace('/<page_footer([^>]*)>/isU', '<hr>'.HTML2PDF_locale::get('vue02').' : $1<hr><div$1>', $content);
  414. $content = preg_replace('/<page([^>]*)>/isU', '<hr>'.HTML2PDF_locale::get('vue03').' : $1<hr><div$1>', $content);
  415. $content = preg_replace('/<\/page([^>]*)>/isU', '</div><hr>', $content);
  416. $content = preg_replace('/<bookmark([^>]*)>/isU', '<hr>bookmark : $1<hr>', $content);
  417. $content = preg_replace('/<\/bookmark([^>]*)>/isU', '', $content);
  418. $content = preg_replace('/<barcode([^>]*)>/isU', '<hr>barcode : $1<hr>', $content);
  419. $content = preg_replace('/<\/barcode([^>]*)>/isU', '', $content);
  420. $content = preg_replace('/<qrcode([^>]*)>/isU', '<hr>qrcode : $1<hr>', $content);
  421. $content = preg_replace('/<\/qrcode([^>]*)>/isU', '', $content);
  422. echo '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
  423. <html>
  424. <head>
  425. <title>'.HTML2PDF_locale::get('vue04').' HTML</title>
  426. <meta http-equiv="Content-Type" content="text/html; charset='.$this->_encoding.'" >
  427. </head>
  428. <body style="padding: 10px; font-size: 10pt;font-family: Verdana;">
  429. '.$content.'
  430. </body>
  431. </html>';
  432. exit;
  433. }
  434. /**
  435. * set the default margins of the page
  436. *
  437. * @access protected
  438. * @param int $left (mm, left margin)
  439. * @param int $top (mm, top margin)
  440. * @param int $right (mm, right margin, if null => left=right)
  441. * @param int $bottom (mm, bottom margin, if null => bottom=8mm)
  442. */
  443. protected function _setDefaultMargins($left, $top, $right = null, $bottom = null)
  444. {
  445. if ($right===null) $right = $left;
  446. if ($bottom===null) $bottom = 8;
  447. $this->_defaultLeft = $this->parsingCss->ConvertToMM($left.'mm');
  448. $this->_defaultTop = $this->parsingCss->ConvertToMM($top.'mm');
  449. $this->_defaultRight = $this->parsingCss->ConvertToMM($right.'mm');
  450. $this->_defaultBottom = $this->parsingCss->ConvertToMM($bottom.'mm');
  451. }
  452. /**
  453. * create a new page
  454. *
  455. * @access protected
  456. * @param mixed $format
  457. * @param string $orientation
  458. * @param array $background background information
  459. * @param integer $curr real position in the html parseur (if break line in the write of a text)
  460. * @param boolean $resetPageNumber
  461. */
  462. protected function _setNewPage($format = null, $orientation = '', $background = null, $curr = null, $resetPageNumber=false)
  463. {
  464. $this->_firstPage = false;
  465. $this->_format = $format ? $format : $this->_format;
  466. $this->_orientation = $orientation ? $orientation : $this->_orientation;
  467. $this->_background = $background!==null ? $background : $this->_background;
  468. $this->_maxY = 0;
  469. $this->_maxX = 0;
  470. $this->_maxH = 0;
  471. $this->_maxE = 0;
  472. $this->pdf->SetMargins($this->_defaultLeft, $this->_defaultTop, $this->_defaultRight);
  473. if ($resetPageNumber) {
  474. $this->pdf->startPageGroup();
  475. }
  476. $this->pdf->AddPage($this->_orientation, $this->_format);
  477. if ($resetPageNumber) {
  478. $this->pdf->myStartPageGroup();
  479. }
  480. $this->_page++;
  481. if (!$this->_isSubPart) {
  482. if ($this->_isCalculationPass) {
  483. if ($resetPageNumber || $this->_page == 1) {
  484. $this->_firstPageInSet []= $this->_page;
  485. }
  486. $this->_maxPage = $this->_page;
  487. }
  488. }
  489. if (!$this->_subPart && !$this->_isSubPart) {
  490. if (is_array($this->_background)) {
  491. if (isset($this->_background['color']) && $this->_background['color']) {
  492. $this->pdf->setFillColorArray($this->_background['color']);
  493. $this->pdf->Rect(0, 0, $this->pdf->getW(), $this->pdf->getH(), 'F');
  494. }
  495. if (isset($this->_background['img']) && $this->_background['img'])
  496. $this->pdf->Image($this->_background['img'], $this->_background['posX'], $this->_background['posY'], $this->_background['width']);
  497. }
  498. $this->_setPageHeader();
  499. $this->_setPageFooter();
  500. }
  501. $this->_setMargins();
  502. $this->pdf->setY($this->_margeTop);
  503. $this->_setNewPositionForNewLine($curr);
  504. $this->_maxH = 0;
  505. }
  506. /**
  507. * set the real margin, using the default margins and the page margins
  508. *
  509. * @access protected
  510. */
  511. protected function _setMargins()
  512. {
  513. if (!$this->_isSubPart) {
  514. $first = in_array($this->_page, $this->_firstPageInSet);
  515. // optimized to calculate/set page margins only once per page
  516. $suffix = $first ? '-first' : '';
  517. // prepare the margins
  518. $this->_margeLeft = $this->_defaultLeft + (isset($this->_background['left'.$suffix]) ? $this->_background['left'.$suffix] : 0);
  519. $this->_margeRight = $this->_defaultRight + (isset($this->_background['right'.$suffix]) ? $this->_background['right'.$suffix] : 0);
  520. $this->_margeTop = $this->_defaultTop + (isset($this->_background['top'.$suffix]) ? $this->_background['top'.$suffix] : 0);
  521. $this->_margeBottom = $this->_defaultBottom + (isset($this->_background['bottom'.$suffix]) ? $this->_background['bottom'.$suffix] : 0);
  522. // set the PDF margins
  523. $this->pdf->SetMargins($this->_margeLeft, $this->_margeTop, $this->_margeRight);
  524. $this->pdf->SetAutoPageBreak(false, $this->_margeBottom);
  525. }
  526. // set the float Margins
  527. $this->_pageMarges = array();
  528. if ($this->_isInParagraph!==false) {
  529. $this->_pageMarges[floor($this->_margeTop*100)] = array($this->_isInParagraph[0], $this->pdf->getW()-$this->_isInParagraph[1]);
  530. } else {
  531. $this->_pageMarges[floor($this->_margeTop*100)] = array($this->_margeLeft, $this->pdf->getW()-$this->_margeRight);
  532. }
  533. }
  534. /**
  535. * add a debug step
  536. *
  537. * @access protected
  538. * @param string $name step name
  539. * @param boolean $level (true=up, false=down, null=nothing to do)
  540. * @return $this
  541. */
  542. protected function _DEBUG_add($name, $level=null)
  543. {
  544. // if true : UP
  545. if ($level===true) $this->_debugLevel++;
  546. $name = str_repeat(' ', $this->_debugLevel). $name.($level===true ? ' Begin' : ($level===false ? ' End' : ''));
  547. $time = microtime(true);
  548. $usage = ($this->_debugOkUsage ? memory_get_usage() : 0);
  549. $peak = ($this->_debugOkPeak ? memory_get_peak_usage() : 0);
  550. $this->_DEBUG_stepline(
  551. $name,
  552. number_format(($time - $this->_debugStartTime)*1000, 1, '.', ' ').' ms',
  553. number_format(($time - $this->_debugLastTime)*1000, 1, '.', ' ').' ms',
  554. number_format($usage/1024, 1, '.', ' ').' Ko',
  555. number_format($peak/1024, 1, '.', ' ').' Ko'
  556. );
  557. $this->_debugLastTime = $time;
  558. // it false : DOWN
  559. if ($level===false) $this->_debugLevel--;
  560. return $this;
  561. }
  562. /**
  563. * display a debug line
  564. *
  565. *
  566. * @access protected
  567. * @param string $name
  568. * @param string $timeTotal
  569. * @param string $timeStep
  570. * @param string $memoryUsage
  571. * @param string $memoryPeak
  572. */
  573. protected function _DEBUG_stepline($name, $timeTotal, $timeStep, $memoryUsage, $memoryPeak)
  574. {
  575. $txt = str_pad($name, 30, ' ', STR_PAD_RIGHT).
  576. str_pad($timeTotal, 12, ' ', STR_PAD_LEFT).
  577. str_pad($timeStep, 12, ' ', STR_PAD_LEFT).
  578. str_pad($memoryUsage, 15, ' ', STR_PAD_LEFT).
  579. str_pad($memoryPeak, 15, ' ', STR_PAD_LEFT);
  580. echo '<pre style="padding:0; margin:0">'.$txt.'</pre>';
  581. }
  582. /**
  583. * get the Min and Max X, for Y (use the float margins)
  584. *
  585. * @access protected
  586. * @param float $y
  587. * @return array(float, float)
  588. */
  589. protected function _getMargins($y)
  590. {
  591. $y = floor($y*100);
  592. $x = array($this->pdf->getlMargin(), $this->pdf->getW()-$this->pdf->getrMargin());
  593. foreach ($this->_pageMarges as $mY => $mX)
  594. if ($mY<=$y) $x = $mX;
  595. return $x;
  596. }
  597. /**
  598. * Add margins, for a float
  599. *
  600. * @access protected
  601. * @param string $float (left / right)
  602. * @param float $xLeft
  603. * @param float $yTop
  604. * @param float $xRight
  605. * @param float $yBottom
  606. */
  607. protected function _addMargins($float, $xLeft, $yTop, $xRight, $yBottom)
  608. {
  609. // get the current float margins, for top and bottom
  610. $oldTop = $this->_getMargins($yTop);
  611. $oldBottom = $this->_getMargins($yBottom);
  612. // update the top float margin
  613. if ($float=='left' && $oldTop[0]<$xRight) $oldTop[0] = $xRight;
  614. if ($float=='right' && $oldTop[1]>$xLeft) $oldTop[1] = $xLeft;
  615. $yTop = floor($yTop*100);
  616. $yBottom = floor($yBottom*100);
  617. // erase all the float margins that are smaller than the new one
  618. foreach ($this->_pageMarges as $mY => $mX) {
  619. if ($mY<$yTop) continue;
  620. if ($mY>$yBottom) break;
  621. if ($float=='left' && $this->_pageMarges[$mY][0]<$xRight) unset($this->_pageMarges[$mY]);
  622. if ($float=='right' && $this->_pageMarges[$mY][1]>$xLeft) unset($this->_pageMarges[$mY]);
  623. }
  624. // save the new Top and Bottom margins
  625. $this->_pageMarges[$yTop] = $oldTop;
  626. $this->_pageMarges[$yBottom] = $oldBottom;
  627. // sort the margins
  628. ksort($this->_pageMarges);
  629. // we are just after float
  630. $this->_isAfterFloat = true;
  631. }
  632. /**
  633. * Save old margins (push), and set new ones
  634. *
  635. * @access protected
  636. * @param float $ml left margin
  637. * @param float $mt top margin
  638. * @param float $mr right margin
  639. */
  640. protected function _saveMargin($ml, $mt, $mr)
  641. {
  642. // save old margins
  643. $this->_marges[] = array('l' => $this->pdf->getlMargin(), 't' => $this->pdf->gettMargin(), 'r' => $this->pdf->getrMargin(), 'page' => $this->_pageMarges);
  644. // set new ones
  645. $this->pdf->SetMargins($ml, $mt, $mr);
  646. // prepare for float margins
  647. $this->_pageMarges = array();
  648. $this->_pageMarges[floor($mt*100)] = array($ml, $this->pdf->getW()-$mr);
  649. }
  650. /**
  651. * load the last saved margins (pop)
  652. *
  653. * @access protected
  654. */
  655. protected function _loadMargin()
  656. {
  657. $old = array_pop($this->_marges);
  658. if ($old) {
  659. $ml = $old['l'];
  660. $mt = $old['t'];
  661. $mr = $old['r'];
  662. $mP = $old['page'];
  663. } else {
  664. $ml = $this->_margeLeft;
  665. $mt = 0;
  666. $mr = $this->_margeRight;
  667. $mP = array($mt => array($ml, $this->pdf->getW()-$mr));
  668. }
  669. $this->pdf->SetMargins($ml, $mt, $mr);
  670. $this->_pageMarges = $mP;
  671. }
  672. /**
  673. * save the current maxs (push)
  674. *
  675. * @access protected
  676. */
  677. protected function _saveMax()
  678. {
  679. $this->_maxSave[] = array($this->_maxX, $this->_maxY, $this->_maxH, $this->_maxE);
  680. }
  681. /**
  682. * load the last saved current maxs (pop)
  683. *
  684. * @access protected
  685. */
  686. protected function _loadMax()
  687. {
  688. $old = array_pop($this->_maxSave);
  689. if ($old) {
  690. $this->_maxX = $old[0];
  691. $this->_maxY = $old[1];
  692. $this->_maxH = $old[2];
  693. $this->_maxE = $old[3];
  694. } else {
  695. $this->_maxX = 0;
  696. $this->_maxY = 0;
  697. $this->_maxH = 0;
  698. $this->_maxE = 0;
  699. }
  700. }
  701. /**
  702. * draw the PDF header with the HTML in page_header
  703. *
  704. * @access protected
  705. */
  706. protected function _setPageHeader()
  707. {
  708. $first = in_array($this->_page, $this->_firstPageInSet);
  709. // if there's no 'first' header defined, fallback to normal header
  710. if ($first && !count($this->{'_subFIRST_HEADER'})) {
  711. $first = false;
  712. }
  713. $prop = $first ? '_subFIRST_HEADER' : '_subHEADER';
  714. if (!count($this->{$prop})) return false;
  715. $oldParsePos = $this->_parsePos;
  716. $oldParseCode = $this->parsingHtml->code;
  717. $this->_parsePos = 0;
  718. $this->parsingHtml->code = $this->{$prop};
  719. $this->_makeHTMLcode();
  720. $this->_parsePos = $oldParsePos;
  721. $this->parsingHtml->code = $oldParseCode;
  722. }
  723. /**
  724. * draw the PDF footer with the HTML in page_footer
  725. *
  726. * @access protected
  727. */
  728. protected function _setPageFooter()
  729. {
  730. $last = (!$this->_isCalculationPass && $this->_page == $this->_maxPage);
  731. // if there's no 'last' footer defined, fallback to normal footer
  732. if ($last && !count($this->{'_subLAST_FOOTER'})) {
  733. $last = false;
  734. }
  735. $prop = $last ? '_subLAST_FOOTER' : '_subFOOTER';
  736. if (!count($this->{$prop})) return false;
  737. $oldParsePos = $this->_parsePos;
  738. $oldParseCode = $this->parsingHtml->code;
  739. $this->_parsePos = 0;
  740. $this->parsingHtml->code = $this->{$prop};
  741. $this->_isInFooter = true;
  742. $this->_makeHTMLcode();
  743. $this->_isInFooter = false;
  744. $this->_parsePos = $oldParsePos;
  745. $this->parsingHtml->code = $oldParseCode;
  746. }
  747. /**
  748. * new line, with a specific height
  749. *
  750. * @access protected
  751. * @param float $h
  752. * @param integer $curr real current position in the text, if new line in the write of a text
  753. */
  754. protected function _setNewLine($h, $curr = null)
  755. {
  756. $this->pdf->Ln($h);
  757. $this->_setNewPositionForNewLine($curr);
  758. }
  759. /**
  760. * calculate the start position of the next line, depending on the text-align
  761. *
  762. * @access protected
  763. * @param integer $curr real current position in the text, if new line in the write of a text
  764. */
  765. protected function _setNewPositionForNewLine($curr = null)
  766. {
  767. // get the margins for the current line
  768. list($lx, $rx) = $this->_getMargins($this->pdf->getY());
  769. $this->pdf->setX($lx);
  770. $wMax = $rx-$lx;
  771. $this->_currentH = 0;
  772. // if subPart => return because align left
  773. if ($this->_subPart || $this->_isSubPart || $this->_isForOneLine) {
  774. $this->pdf->setWordSpacing(0);
  775. return null;
  776. }
  777. // create the sub object
  778. $sub = null;
  779. $this->_createSubHTML($sub);
  780. $sub->_saveMargin(0, 0, $sub->pdf->getW()-$wMax);
  781. $sub->_isForOneLine = true;
  782. $sub->_parsePos = $this->_parsePos;
  783. $sub->parsingHtml->code = $this->parsingHtml->code;
  784. // if $curr => adapt the current position of the parsing
  785. if ($curr!==null && $sub->parsingHtml->code[$this->_parsePos]['name']=='write') {
  786. $txt = $sub->parsingHtml->code[$this->_parsePos]['param']['txt'];
  787. $txt = str_replace('[[page_cu]]', $sub->pdf->getMyNumPage($this->_page), $txt);
  788. $sub->parsingHtml->code[$this->_parsePos]['param']['txt'] = substr($txt, $curr+1);
  789. } else
  790. $sub->_parsePos++;
  791. // for each element of the parsing => load the action
  792. $res = null;
  793. for ($sub->_parsePos; $sub->_parsePos<count($sub->parsingHtml->code); $sub->_parsePos++) {
  794. $action = $sub->parsingHtml->code[$sub->_parsePos];
  795. $res = $sub->_executeAction($action);
  796. if (!$res) break;
  797. }
  798. $w = $sub->_maxX; // max width
  799. $h = $sub->_maxH; // max height
  800. $e = ($res===null ? $sub->_maxE : 0); // maxnumber of elemets on the line
  801. // destroy the sub HTML
  802. $this->_destroySubHTML($sub);
  803. // adapt the start of the line, depending on the text-align
  804. if ($this->parsingCss->value['text-align']=='center')
  805. $this->pdf->setX(($rx+$this->pdf->getX()-$w)*0.5-0.01);
  806. else if ($this->parsingCss->value['text-align']=='right')
  807. $this->pdf->setX($rx-$w-0.01);
  808. else
  809. $this->pdf->setX($lx);
  810. // set the height of the line
  811. $this->_currentH = $h;
  812. // if justify => set the word spacing
  813. if ($this->parsingCss->value['text-align']=='justify' && $e>1) {
  814. $this->pdf->setWordSpacing(($wMax-$w)/($e-1));
  815. } else {
  816. $this->pdf->setWordSpacing(0);
  817. }
  818. }
  819. /**
  820. * prepare HTML2PDF::$_subobj (used for create the sub HTML2PDF objects
  821. *
  822. * @access protected
  823. */
  824. protected function _prepareSubObj()
  825. {
  826. $pdf = null;
  827. // create the sub object
  828. HTML2PDF::$_subobj = new HTML2PDF(
  829. $this->_orientation,
  830. $this->_format,
  831. $this->_langue,
  832. $this->_unicode,
  833. $this->_encoding,
  834. array($this->_defaultLeft,$this->_defaultTop,$this->_defaultRight,$this->_defaultBottom)
  835. );
  836. // init
  837. HTML2PDF::$_subobj->setTestTdInOnePage($this->_testTdInOnepage);
  838. HTML2PDF::$_subobj->setTestIsImage($this->_testIsImage);
  839. HTML2PDF::$_subobj->setTestIsDeprecated($this->_testIsDeprecated);
  840. HTML2PDF::$_subobj->setDefaultFont($this->_defaultFont);
  841. HTML2PDF::$_subobj->parsingCss->css = &$this->parsingCss->css;
  842. HTML2PDF::$_subobj->parsingCss->cssKeys = &$this->parsingCss->cssKeys;
  843. // copy global parameters
  844. HTML2PDF::$_subobj->_isCalculationPass = $this->_isCalculationPass;
  845. HTML2PDF::$_subobj->_rootPathForURLs = $this->_rootPathForURLs;
  846. // clone font from the original PDF
  847. HTML2PDF::$_subobj->pdf->cloneFontFrom($this->pdf);
  848. // remove the link to the parent
  849. HTML2PDF::$_subobj->parsingCss->setPdfParent($pdf);
  850. }
  851. /**
  852. * create a sub HTML2PDF, to calculate the multi-tables
  853. *
  854. * @access protected
  855. * @param &HTML2PDF $subHtml sub HTML2PDF to create
  856. * @param integer $cellmargin if in a TD : cellmargin of this td
  857. */
  858. protected function _createSubHTML(&$subHtml, $cellmargin=0)
  859. {
  860. // prepare the subObject, if never prepare before
  861. if (HTML2PDF::$_subobj===null) {
  862. $this->_prepareSubObj();
  863. }
  864. // calculate the width to use
  865. if ($this->parsingCss->value['width']) {
  866. $marge = $cellmargin*2;
  867. $marge+= $this->parsingCss->value['padding']['l'] + $this->parsingCss->value['padding']['r'];
  868. $marge+= $this->parsingCss->value['border']['l']['width'] + $this->parsingCss->value['border']['r']['width'];
  869. $marge = $this->pdf->getW() - $this->parsingCss->value['width'] + $marge;
  870. } else {
  871. $marge = $this->_margeLeft+$this->_margeRight;
  872. }
  873. // BUGFIX : we have to call the method, because of a bug in php 5.1.6
  874. HTML2PDF::$_subobj->pdf->getPage();
  875. // clone the sub oject
  876. $subHtml = clone HTML2PDF::$_subobj;
  877. $subHtml->parsingCss->table = $this->parsingCss->table;
  878. $subHtml->parsingCss->value = $this->parsingCss->value;
  879. $subHtml->initSubHtml(
  880. $this->_format,
  881. $this->_orientation,
  882. $marge,
  883. $this->_page,
  884. $this->_defList,
  885. $this->pdf->getMyLastPageGroup(),
  886. $this->pdf->getMyLastPageGroupNb()
  887. );
  888. }
  889. /**
  890. * destroy a subHTML2PDF
  891. *
  892. * @access protected
  893. */
  894. protected function _destroySubHTML(&$subHtml)
  895. {
  896. unset($subHtml);
  897. $subHtml = null;
  898. }
  899. /**
  900. * Convert a arabic number in roman number
  901. *
  902. * @access protected
  903. * @param integer $nbArabic
  904. * @return string $nbRoman
  905. */
  906. protected function _listeArab2Rom($nbArabic)
  907. {
  908. $nbBaseTen = array('I','X','C','M');
  909. $nbBaseFive = array('V','L','D');
  910. $nbRoman = '';
  911. if ($nbArabic<1) return $nbArabic;
  912. if ($nbArabic>3999) return $nbArabic;
  913. for ($i=3; $i>=0 ; $i--) {
  914. $chiffre=floor($nbArabic/pow(10, $i));
  915. if ($chiffre>=1) {
  916. $nbArabic=$nbArabic-$chiffre*pow(10, $i);
  917. if ($chiffre<=3) {
  918. for ($j=$chiffre; $j>=1; $j--) {
  919. $nbRoman=$nbRoman.$nbBaseTen[$i];
  920. }
  921. } else if ($chiffre==9) {
  922. $nbRoman=$nbRoman.$nbBaseTen[$i].$nbBaseTen[$i+1];
  923. } else if ($chiffre==4) {
  924. $nbRoman=$nbRoman.$nbBaseTen[$i].$nbBaseFive[$i];
  925. } else {
  926. $nbRoman=$nbRoman.$nbBaseFive[$i];
  927. for ($j=$chiffre-5; $j>=1; $j--) {
  928. $nbRoman=$nbRoman.$nbBaseTen[$i];
  929. }
  930. }
  931. }
  932. }
  933. return $nbRoman;
  934. }
  935. /**
  936. * add a LI to the current level
  937. *
  938. * @access protected
  939. */
  940. protected function _listeAddLi()
  941. {
  942. $this->_defList[count($this->_defList)-1]['nb']++;
  943. }
  944. /**
  945. * get the width to use for the column of the list
  946. *
  947. * @access protected
  948. * @return string $width
  949. */
  950. protected function _listeGetWidth()
  951. {
  952. return '7mm';
  953. }
  954. /**
  955. * get the padding to use for the column of the list
  956. *
  957. * @access protected
  958. * @return string $padding
  959. */
  960. protected function _listeGetPadding()
  961. {
  962. return '1mm';
  963. }
  964. /**
  965. * get the information of the li on the current level
  966. *
  967. * @access protected
  968. * @return array(fontName, small size, string)
  969. */
  970. protected function _listeGetLi()
  971. {
  972. $im = $this->_defList[count($this->_defList)-1]['img'];
  973. $st = $this->_defList[count($this->_defList)-1]['style'];
  974. $nb = $this->_defList[count($this->_defList)-1]['nb'];
  975. $up = (substr($st, 0, 6)=='upper-');
  976. if ($im) return array(false, false, $im);
  977. switch($st)
  978. {
  979. case 'none':
  980. return array('helvetica', true, ' ');
  981. case 'upper-alpha':
  982. case 'lower-alpha':
  983. $str = '';
  984. while ($nb>26) {
  985. $str = chr(96+$nb%26).$str;
  986. $nb = floor($nb/26);
  987. }
  988. $str = chr(96+$nb).$str;
  989. return array('helvetica', false, ($up ? strtoupper($str) : $str).'.');
  990. case 'upper-roman':
  991. case 'lower-roman':
  992. $str = $this->_listeArab2Rom($nb);
  993. return array('helvetica', false, ($up ? strtoupper($str) : $str).'.');
  994. case 'decimal':
  995. return array($this->parsingCss->value['font-family'], false, $nb.'.');
  996. case 'square':
  997. return array('zapfdingbats', true, chr(110));
  998. case 'circle':
  999. return array('zapfdingbats', true, chr(109));
  1000. case 'disc':
  1001. default:
  1002. return array('zapfdingbats', true, chr(108));
  1003. }
  1004. }
  1005. /**
  1006. * add a level to the list
  1007. *
  1008. * @access protected
  1009. * @param string $type : ul, ol
  1010. * @param string $style : lower-alpha, ...
  1011. * @param string $img
  1012. */
  1013. protected function _listeAddLevel($type = 'ul', $style = '', $img = null)
  1014. {
  1015. // get the url of the image, if we want to use a image
  1016. if ($img) {
  1017. if (preg_match('/^url\(([^)]+)\)$/isU', trim($img), $match)) {
  1018. $img = $match[1];
  1019. } else {
  1020. $img = null;
  1021. }
  1022. } else {
  1023. $img = null;
  1024. }
  1025. // prepare the datas
  1026. if (!in_array($type, array('ul', 'ol'))) $type = 'ul';
  1027. if (!in_array($style, array('lower-alpha', 'upper-alpha', 'upper-roman', 'lower-roman', 'decimal', 'square', 'circle', 'disc', 'none'))) $style = '';
  1028. if (!$style) {
  1029. if ($type=='ul') $style = 'disc';
  1030. else $style = 'decimal';
  1031. }
  1032. // add the new level
  1033. $this->_defList[count($this->_defList)] = array('style' => $style, 'nb' => 0, 'img' => $img);
  1034. }
  1035. /**
  1036. * remove a level to the list
  1037. *
  1038. * @access protected
  1039. */
  1040. protected function _listeDelLevel()
  1041. {
  1042. if (count($this->_defList)) {
  1043. unset($this->_defList[count($this->_defList)-1]);
  1044. $this->_defList = array_values($this->_defList);
  1045. }
  1046. }
  1047. /**
  1048. * execute the actions to convert the html
  1049. *
  1050. * @access protected
  1051. */
  1052. protected function _makeHTMLcode()
  1053. {
  1054. // foreach elements of the parsing
  1055. for ($this->_parsePos=0; $this->_parsePos<count($this->parsingHtml->code); $this->_parsePos++) {
  1056. // get the action to do
  1057. $action = $this->parsingHtml->code[$this->_parsePos];
  1058. // if it is a opening of table / ul / ol
  1059. if (in_array($action['name'], array('table', 'ul', 'ol')) && !$action['close']) {
  1060. // we will work as a sub HTML to calculate the size of the element
  1061. $this->_subPart = true;
  1062. // get the name of the opening tag
  1063. $tagOpen = $action['name'];
  1064. // save the actual pos on the parsing
  1065. $this->_tempPos = $this->_parsePos;
  1066. // foreach elements, while we are in the opened tag
  1067. while (isset($this->parsingHtml->code[$this->_tempPos]) && !($this->parsingHtml->code[$this->_tempPos]['name']==$tagOpen && $this->parsingHtml->code[$this->_tempPos]['close'])) {
  1068. // make the action
  1069. $this->_executeAction($this->parsingHtml->code[$this->_tempPos]);
  1070. $this->_tempPos++;
  1071. }
  1072. // execute the closure of the tag
  1073. if (isset($this->parsingHtml->code[$this->_tempPos])) {
  1074. $this->_executeAction($this->parsingHtml->code[$this->_tempPos]);
  1075. }
  1076. // end of the sub part
  1077. $this->_subPart = false;
  1078. }
  1079. // execute the action
  1080. $this->_executeAction($action);
  1081. }
  1082. }
  1083. /**
  1084. * execute the action from the parsing
  1085. *
  1086. * @access protected
  1087. * @param array $action
  1088. */
  1089. protected function _executeAction($action)
  1090. {
  1091. // name of the action
  1092. $fnc = ($action['close'] ? '_tag_close_' : '_tag_open_').strtoupper($action['name']);
  1093. // parameters of the action
  1094. $param = $action['param'];
  1095. // if it the first action of the first page, and if it is not a open tag of PAGE => create the new page
  1096. if ($fnc!='_tag_open_PAGE' && $this->_firstPage) {
  1097. $this->_setNewPage();
  1098. }
  1099. // the action must exist
  1100. if (!is_callable(array(&$this, $fnc))) {
  1101. throw new HTML2PDF_exception(1, strtoupper($action['name']), $this->parsingHtml->getHtmlErrorCode($action['html_pos']));
  1102. }
  1103. // lauch the action
  1104. $res = $this->{$fnc}($param);
  1105. // save the name of the action
  1106. $this->_previousCall = $fnc;
  1107. // return the result
  1108. return $res;
  1109. }
  1110. /**
  1111. * get the position of the element on the current line, depending on his height
  1112. *
  1113. * @access protected
  1114. * @param float $h
  1115. * @return float
  1116. */
  1117. protected function _getElementY($h)
  1118. {
  1119. if ($this->_subPart || $this->_isSubPart || !$this->_currentH || $this->_currentH<$h)
  1120. return 0;
  1121. return ($this->_curr