dompdf.cls.php 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611
  1. <?php
  2. /**
  3. * DOMPDF - PHP5 HTML to PDF renderer
  4. *
  5. * File: $RCSfile: dompdf.cls.php,v $
  6. * Created on: 2004-06-09
  7. *
  8. * Copyright (c) 2004 - Benj Carson <benjcarson@digitaljunkies.ca>
  9. *
  10. * This library is free software; you can redistribute it and/or
  11. * modify it under the terms of the GNU Lesser General Public
  12. * License as published by the Free Software Foundation; either
  13. * version 2.1 of the License, or (at your option) any later version.
  14. *
  15. * This library is distributed in the hope that it will be useful,
  16. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  17. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  18. * Lesser General Public License for more details.
  19. *
  20. * You should have received a copy of the GNU Lesser General Public License
  21. * along with this library in the file LICENSE.LGPL; if not, write to the
  22. * Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
  23. * 02111-1307 USA
  24. *
  25. * Alternatively, you may distribute this software under the terms of the
  26. * PHP License, version 3.0 or later. A copy of this license should have
  27. * been distributed with this file in the file LICENSE.PHP . If this is not
  28. * the case, you can obtain a copy at http://www.php.net/license/3_0.txt.
  29. *
  30. * The latest version of DOMPDF might be available at:
  31. * http://www.dompdf.com/
  32. *
  33. * @link http://www.dompdf.com/
  34. * @copyright 2004 Benj Carson
  35. * @author Benj Carson <benjcarson@digitaljunkies.ca>
  36. * @package dompdf
  37. */
  38. /* $Id: dompdf.cls.php 216 2010-03-11 22:49:18Z ryan.masten $ */
  39. /**
  40. * DOMPDF - PHP5 HTML to PDF renderer
  41. *
  42. * DOMPDF loads HTML and does its best to render it as a PDF. It gets its
  43. * name from the new DomDocument PHP5 extension. Source HTML is first
  44. * parsed by a DomDocument object. DOMPDF takes the resulting DOM tree and
  45. * attaches a {@link Frame} object to each node. {@link Frame} objects store
  46. * positioning and layout information and each has a reference to a {@link
  47. * Style} object.
  48. *
  49. * Style information is loaded and parsed (see {@link Stylesheet}) and is
  50. * applied to the frames in the tree by using XPath. CSS selectors are
  51. * converted into XPath queries, and the computed {@link Style} objects are
  52. * applied to the {@link Frame}s.
  53. *
  54. * {@link Frame}s are then decorated (in the design pattern sense of the
  55. * word) based on their CSS display property ({@link
  56. * http://www.w3.org/TR/CSS21/visuren.html#propdef-display}).
  57. * Frame_Decorators augment the basic {@link Frame} class by adding
  58. * additional properties and methods specific to the particular type of
  59. * {@link Frame}. For example, in the CSS layout model, block frames
  60. * (display: block;) contain line boxes that are usually filled with text or
  61. * other inline frames. The Block_Frame_Decorator therefore adds a $lines
  62. * property as well as methods to add {@link Frame}s to lines and to add
  63. * additional lines. {@link Frame}s also are attached to specific
  64. * Positioner and {@link Frame_Reflower} objects that contain the
  65. * positioining and layout algorithm for a specific type of frame,
  66. * respectively. This is an application of the Strategy pattern.
  67. *
  68. * Layout, or reflow, proceeds recursively (post-order) starting at the root
  69. * of the document. Space constraints (containing block width & height) are
  70. * pushed down, and resolved positions and sizes bubble up. Thus, every
  71. * {@link Frame} in the document tree is traversed once (except for tables
  72. * which use a two-pass layout algorithm). If you are interested in the
  73. * details, see the reflow() method of the Reflower classes.
  74. *
  75. * Rendering is relatively straightforward once layout is complete. {@link
  76. * Frame}s are rendered using an adapted {@link Cpdf} class, originally
  77. * written by Wayne Munro, http://www.ros.co.nz/pdf/. (Some performance
  78. * related changes have been made to the original {@link Cpdf} class, and
  79. * the {@link CPDF_Adapter} class provides a simple, stateless interface to
  80. * PDF generation.) PDFLib support has now also been added, via the {@link
  81. * PDFLib_Adapter}.
  82. *
  83. *
  84. * @package dompdf
  85. */
  86. class DOMPDF {
  87. /**
  88. * DomDocument representing the HTML document
  89. *
  90. * @var DomDocument
  91. */
  92. protected $_xml;
  93. /**
  94. * Frame_Tree derived from the DOM tree
  95. *
  96. * @var Frame_Tree
  97. */
  98. protected $_tree;
  99. /**
  100. * Stylesheet for the document
  101. *
  102. * @var Stylesheet
  103. */
  104. protected $_css;
  105. /**
  106. * Actual PDF renderer
  107. *
  108. * @var Canvas
  109. */
  110. protected $_pdf;
  111. /**
  112. * Desired paper size ('letter', 'legal', 'A4', etc.)
  113. *
  114. * @var string
  115. */
  116. protected $_paper_size;
  117. /**
  118. * Paper orientation ('portrait' or 'landscape')
  119. *
  120. * @var string
  121. */
  122. protected $_paper_orientation;
  123. /**
  124. * Callbacks on new page and new element
  125. *
  126. * @var array
  127. */
  128. protected $_callbacks;
  129. /**
  130. * Experimental caching capability
  131. *
  132. * @var string
  133. */
  134. private $_cache_id;
  135. /**
  136. * Base hostname
  137. *
  138. * Used for relative paths/urls
  139. * @var string
  140. */
  141. protected $_base_host;
  142. /**
  143. * Absolute base path
  144. *
  145. * Used for relative paths/urls
  146. * @var string
  147. */
  148. protected $_base_path;
  149. /**
  150. * Protcol used to request file (file://, http://, etc)
  151. *
  152. * @var string
  153. */
  154. protected $_protocol;
  155. /**
  156. * Class constructor
  157. */
  158. function __construct() {
  159. $this->_messages = array();
  160. $this->_xml = new DOMDocument();
  161. $this->_xml->preserveWhiteSpace = true;
  162. $this->_tree = new Frame_Tree($this->_xml);
  163. $this->_css = new Stylesheet();
  164. $this->_pdf = null;
  165. $this->_paper_size = "letter";
  166. $this->_paper_orientation = "portrait";
  167. $this->_base_protocol = "";
  168. $this->_base_host = "";
  169. $this->_base_path = "";
  170. $this->_callbacks = array();
  171. $this->_cache_id = null;
  172. }
  173. /**
  174. * Returns the underlying {@link Frame_Tree} object
  175. *
  176. * @return Frame_Tree
  177. */
  178. function get_tree() { return $this->_tree; }
  179. //........................................................................
  180. /**
  181. * Sets the protocol to use
  182. *
  183. * @param string $proto
  184. */
  185. // FIXME: validate these
  186. function set_protocol($proto) { $this->_protocol = $proto; }
  187. /**
  188. * Sets the base hostname
  189. *
  190. * @param string $host
  191. */
  192. function set_host($host) { $this->_base_host = $host; }
  193. /**
  194. * Sets the base path
  195. *
  196. * @param string $path
  197. */
  198. function set_base_path($path) { $this->_base_path = $path; }
  199. /**
  200. * Returns the protocol in use
  201. *
  202. * @return string
  203. */
  204. function get_protocol() { return $this->_protocol; }
  205. /**
  206. * Returns the base hostname
  207. *
  208. * @return string
  209. */
  210. function get_host() { return $this->_base_host; }
  211. /**
  212. * Returns the base path
  213. *
  214. * @return string
  215. */
  216. function get_base_path() { return $this->_base_path; }
  217. /**
  218. * Return the underlying Canvas instance (e.g. CPDF_Adapter, GD_Adapter)
  219. *
  220. * @return Canvas
  221. */
  222. function get_canvas() { return $this->_pdf; }
  223. /**
  224. * Returns the callbacks array
  225. *
  226. * @return array
  227. */
  228. function get_callbacks() { return $this->_callbacks; }
  229. //........................................................................
  230. /**
  231. * Loads an HTML file
  232. *
  233. * Parse errors are stored in the global array _dompdf_warnings.
  234. *
  235. * @param string $file a filename or url to load
  236. */
  237. function load_html_file($file) {
  238. // Store parsing warnings as messages (this is to prevent output to the
  239. // browser if the html is ugly and the dom extension complains,
  240. // preventing the pdf from being streamed.)
  241. if ( !$this->_protocol && !$this->_base_host && !$this->_base_path )
  242. list($this->_protocol, $this->_base_host, $this->_base_path) = explode_url($file);
  243. if ( !DOMPDF_ENABLE_REMOTE &&
  244. ($this->_protocol != "" && $this->_protocol !== "file://" ) )
  245. throw new DOMPDF_Exception("Remote file requested, but DOMPDF_ENABLE_REMOTE is false.");
  246. if ($this->_protocol == "" || $this->_protocol === "file://") {
  247. $realfile = realpath($file);
  248. if ( !$file )
  249. throw new DOMPDF_Exception("File '$file' not found.");
  250. if ( strpos($realfile, DOMPDF_CHROOT) !== 0 )
  251. throw new DOMPDF_Exception("Permission denied on $file.");
  252. // Exclude dot files (e.g. .htaccess)
  253. if ( substr(basename($realfile),0,1) === "." )
  254. throw new DOMPDF_Exception("Permission denied on $file.");
  255. $file = $realfile;
  256. }
  257. $this->load_html(file_get_contents($file));
  258. }
  259. /**
  260. * Loads an HTML string
  261. *
  262. * Parse errors are stored in the global array _dompdf_warnings.
  263. *
  264. * @param string $str HTML text to load
  265. */
  266. function load_html($str) {
  267. // FIXME: Determine character encoding, switch to UTF8, update meta tag. Need better http/file stream encoding detection, currently relies on text or meta tag.
  268. mb_detect_order('auto');
  269. if (mb_detect_encoding($str) != 'UTF-8') {
  270. if (mb_detect_encoding($str) == '') {
  271. if (preg_match('@<meta\s+http-equiv="Content-Type"\s+content="([\w/]+)(;\s*?charset=([^\s"]+))?@i',$str,$matches)) {
  272. $encoding = strtoupper($matches[3]);
  273. } else {
  274. $encoding = 'UTF-8';
  275. }
  276. } else {
  277. if (preg_match('@<meta\s+http-equiv="Content-Type"\s+content="([\w/]+)(;\s*?charset=([^\s"]+))?@i',$str,$matches)) {
  278. $encoding = strtoupper($matches[3]);
  279. } else {
  280. $encoding = 'auto';
  281. }
  282. }
  283. if ($encoding != 'UTF-8') { $str = mb_convert_encoding($str, 'UTF-8', $encoding); }
  284. if (preg_match('@<meta\s+http-equiv="Content-Type"\s+content="([\w/]+)(;\s+charset=([^\s"]+))?@i',$str,$matches)) {
  285. $str = preg_replace('/charset=([^\s"]+)/i','charset=UTF-8',$str);
  286. } else {
  287. $str = str_replace('<head>', '<head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8">', $str);
  288. }
  289. }
  290. // Parse embedded php, first-pass
  291. if ( DOMPDF_ENABLE_PHP ) {
  292. ob_start();
  293. eval("?" . ">$str");
  294. $str = ob_get_contents();
  295. ob_end_clean();
  296. }
  297. // Store parsing warnings as messages
  298. set_error_handler("record_warnings");
  299. $this->_xml->loadHTML($str);
  300. restore_error_handler();
  301. }
  302. /**
  303. * Builds the {@link Frame_Tree}, loads any CSS and applies the styles to
  304. * the {@link Frame_Tree}
  305. */
  306. protected function _process_html() {
  307. $this->_tree->build_tree();
  308. $this->_css->load_css_file(Stylesheet::DEFAULT_STYLESHEET);
  309. $acceptedmedia = Stylesheet::$ACCEPTED_GENERIC_MEDIA_TYPES;
  310. if ( defined("DOMPDF_DEFAULT_MEDIA_TYPE") ) {
  311. $acceptedmedia[] = DOMPDF_DEFAULT_MEDIA_TYPE;
  312. } else {
  313. $acceptedmedia[] = Stylesheet::$ACCEPTED_DEFAULT_MEDIA_TYPE;
  314. }
  315. // load <link rel="STYLESHEET" ... /> tags
  316. $links = $this->_xml->getElementsByTagName("link");
  317. foreach ($links as $link) {
  318. if ( mb_strtolower($link->getAttribute("rel")) === "stylesheet" ||
  319. mb_strtolower($link->getAttribute("type")) === "text/css" ) {
  320. //Check if the css file is for an accepted media type
  321. //media not given then always valid
  322. $formedialist = preg_split("/[\s\n,]/", $link->getAttribute("media"),-1, PREG_SPLIT_NO_EMPTY);
  323. if ( count($formedialist) > 0 ) {
  324. $accept = false;
  325. foreach ( $formedialist as $type ) {
  326. if ( in_array(mb_strtolower(trim($type)), $acceptedmedia) ) {
  327. $accept = true;
  328. break;
  329. }
  330. }
  331. if (!$accept) {
  332. //found at least one mediatype, but none of the accepted ones
  333. //Skip this css file.
  334. continue;
  335. }
  336. }
  337. $url = $link->getAttribute("href");
  338. $url = build_url($this->_protocol, $this->_base_host, $this->_base_path, $url);
  339. $this->_css->load_css_file($url);
  340. }
  341. }
  342. // load <style> tags
  343. $styles = $this->_xml->getElementsByTagName("style");
  344. foreach ($styles as $style) {
  345. // Accept all <style> tags by default (note this is contrary to W3C
  346. // HTML 4.0 spec:
  347. // http://www.w3.org/TR/REC-html40/present/styles.html#adef-media
  348. // which states that the default media type is 'screen'
  349. if ( $style->hasAttributes() &&
  350. ($media = $style->getAttribute("media")) &&
  351. !in_array($media, $acceptedmedia) )
  352. continue;
  353. $css = "";
  354. if ( $style->hasChildNodes() ) {
  355. $child = $style->firstChild;
  356. while ( $child ) {
  357. $css .= $child->nodeValue; // Handle <style><!-- blah --></style>
  358. $child = $child->nextSibling;
  359. }
  360. } else
  361. $css = $style->nodeValue;
  362. // Set the base path of the Stylesheet to that of the file being processed
  363. $this->_css->set_protocol($this->_protocol);
  364. $this->_css->set_host($this->_base_host);
  365. $this->_css->set_base_path($this->_base_path);
  366. $this->_css->load_css($css);
  367. }
  368. }
  369. //........................................................................
  370. /**
  371. * Sets the paper size & orientation
  372. *
  373. * @param string $size 'letter', 'legal', 'A4', etc. {@link CPDF_Adapter::$PAPER_SIZES}
  374. * @param string $orientation 'portrait' or 'landscape'
  375. */
  376. function set_paper($size, $orientation = "portrait") {
  377. $this->_paper_size = $size;
  378. $this->_paper_orientation = $orientation;
  379. }
  380. //........................................................................
  381. /**
  382. * Enable experimental caching capability
  383. * @access private
  384. */
  385. function enable_caching($cache_id) {
  386. $this->_cache_id = $cache_id;
  387. }
  388. //........................................................................
  389. /**
  390. * Sets callbacks for events like rendering of pages and elements.
  391. * The callbacks array contains arrays with 'event' set to 'begin_page',
  392. * 'end_page', 'begin_frame', or 'end_frame' and 'f' set to a function or
  393. * object plus method to be called.
  394. *
  395. * The function 'f' must take an array as argument, which contains info
  396. * about the event.
  397. *
  398. * @param array $callbacks the set of callbacks to set
  399. */
  400. function set_callbacks($callbacks) {
  401. if (is_array($callbacks)) {
  402. $this->_callbacks = array();
  403. foreach ($callbacks as $c) {
  404. if (is_array($c) && isset($c['event']) && isset($c['f'])) {
  405. $event = $c['event'];
  406. $f = $c['f'];
  407. if (is_callable($f) && is_string($event)) {
  408. $this->_callbacks[$event][] = $f;
  409. }
  410. }
  411. }
  412. }
  413. }
  414. //........................................................................
  415. /**
  416. * Renders the HTML to PDF
  417. */
  418. function render() {
  419. //enable_mem_profile();
  420. $this->_process_html();
  421. $this->_css->apply_styles($this->_tree);
  422. $root = null;
  423. foreach ($this->_tree->get_frames() as $frame) {
  424. // Set up the root frame
  425. if ( is_null($root) ) {
  426. $root = Frame_Factory::decorate_root( $this->_tree->get_root(), $this );
  427. continue;
  428. }
  429. // Create the appropriate decorators, reflowers & positioners.
  430. $deco = Frame_Factory::decorate_frame($frame, $this);
  431. $deco->set_root($root);
  432. // FIXME: handle generated content
  433. if ( $frame->get_style()->display === "list-item" ) {
  434. // Insert a list-bullet frame
  435. $node = $this->_xml->createElement("bullet"); // arbitrary choice
  436. $b_f = new Frame($node);
  437. $style = $this->_css->create_style();
  438. $style->display = "-dompdf-list-bullet";
  439. $style->inherit($frame->get_style());
  440. $b_f->set_style($style);
  441. $deco->prepend_child( Frame_Factory::decorate_frame($b_f, $this) );
  442. }
  443. }
  444. $this->_pdf = Canvas_Factory::get_instance($this->_paper_size, $this->_paper_orientation);
  445. $root->set_containing_block(0, 0, $this->_pdf->get_width(), $this->_pdf->get_height());
  446. $root->set_renderer(new Renderer($this));
  447. // This is where the magic happens:
  448. $root->reflow();
  449. // Clean up cached images
  450. Image_Cache::clear();
  451. global $_dompdf_warnings, $_dompdf_show_warnings;
  452. if ( $_dompdf_show_warnings ) {
  453. echo '<b>DOMPDF Warnings</b><br><pre>';
  454. foreach ($_dompdf_warnings as $msg)
  455. echo $msg . "\n";
  456. echo $this->get_canvas()->get_cpdf()->messages;
  457. echo '</pre>';
  458. flush();
  459. }
  460. }
  461. //........................................................................
  462. /**
  463. * Add meta information to the PDF after rendering
  464. */
  465. function add_info($label, $value) {
  466. if (!is_null($this->_pdf))
  467. $this->_pdf->add_info($label, $value);
  468. }
  469. //........................................................................
  470. /**
  471. * Streams the PDF to the client
  472. *
  473. * The file will open a download dialog by default. The options
  474. * parameter controls the output. Accepted options are:
  475. *
  476. * 'Accept-Ranges' => 1 or 0 - if this is not set to 1, then this
  477. * header is not included, off by default this header seems to
  478. * have caused some problems despite the fact that it is supposed
  479. * to solve them, so I am leaving it off by default.
  480. *
  481. * 'compress' = > 1 or 0 - apply content stream compression, this is
  482. * on (1) by default
  483. *
  484. * 'Attachment' => 1 or 0 - if 1, force the browser to open a
  485. * download dialog, on (1) by default
  486. *
  487. * @param string $filename the name of the streamed file
  488. * @param array $options header options (see above)
  489. */
  490. function stream($filename, $options = null) {
  491. if (!is_null($this->_pdf))
  492. $this->_pdf->stream($filename, $options);
  493. }
  494. /**
  495. * Returns the PDF as a string
  496. *
  497. * The file will open a download dialog by default. The options
  498. * parameter controls the output. Accepted options are:
  499. *
  500. *
  501. * 'compress' = > 1 or 0 - apply content stream compression, this is
  502. * on (1) by default
  503. *
  504. *
  505. * @param array $options options (see above)
  506. * @return string
  507. */
  508. function output($options = null) {
  509. if ( is_null($this->_pdf) )
  510. return null;
  511. return $this->_pdf->output( $options );
  512. }
  513. /**
  514. * Returns the underlying HTML document as a string
  515. *
  516. * @return string
  517. */
  518. function output_html() {
  519. return $this->_xml->saveHTML();
  520. }
  521. //........................................................................
  522. }