Dompdf.php 41 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507
  1. <?php
  2. /**
  3. * @package dompdf
  4. * @link http://dompdf.github.com/
  5. * @author Benj Carson <benjcarson@digitaljunkies.ca>
  6. * @author Fabien Ménager <fabien.menager@gmail.com>
  7. * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
  8. */
  9. namespace Dompdf;
  10. use DOMDocument;
  11. use DOMNode;
  12. use Dompdf\Adapter\CPDF;
  13. use DOMXPath;
  14. use Dompdf\Frame\Factory;
  15. use Dompdf\Frame\FrameTree;
  16. use HTML5_Tokenizer;
  17. use HTML5_TreeBuilder;
  18. use Dompdf\Image\Cache;
  19. use Dompdf\Renderer\ListBullet;
  20. use Dompdf\Css\Stylesheet;
  21. use Dompdf\Helpers;
  22. /**
  23. * Dompdf - PHP5 HTML to PDF renderer
  24. *
  25. * Dompdf loads HTML and does its best to render it as a PDF. It gets its
  26. * name from the new DomDocument PHP5 extension. Source HTML is first
  27. * parsed by a DomDocument object. Dompdf takes the resulting DOM tree and
  28. * attaches a {@link Frame} object to each node. {@link Frame} objects store
  29. * positioning and layout information and each has a reference to a {@link
  30. * Style} object.
  31. *
  32. * Style information is loaded and parsed (see {@link Stylesheet}) and is
  33. * applied to the frames in the tree by using XPath. CSS selectors are
  34. * converted into XPath queries, and the computed {@link Style} objects are
  35. * applied to the {@link Frame}s.
  36. *
  37. * {@link Frame}s are then decorated (in the design pattern sense of the
  38. * word) based on their CSS display property ({@link
  39. * http://www.w3.org/TR/CSS21/visuren.html#propdef-display}).
  40. * Frame_Decorators augment the basic {@link Frame} class by adding
  41. * additional properties and methods specific to the particular type of
  42. * {@link Frame}. For example, in the CSS layout model, block frames
  43. * (display: block;) contain line boxes that are usually filled with text or
  44. * other inline frames. The Block therefore adds a $lines
  45. * property as well as methods to add {@link Frame}s to lines and to add
  46. * additional lines. {@link Frame}s also are attached to specific
  47. * AbstractPositioner and {@link AbstractFrameReflower} objects that contain the
  48. * positioining and layout algorithm for a specific type of frame,
  49. * respectively. This is an application of the Strategy pattern.
  50. *
  51. * Layout, or reflow, proceeds recursively (post-order) starting at the root
  52. * of the document. Space constraints (containing block width & height) are
  53. * pushed down, and resolved positions and sizes bubble up. Thus, every
  54. * {@link Frame} in the document tree is traversed once (except for tables
  55. * which use a two-pass layout algorithm). If you are interested in the
  56. * details, see the reflow() method of the Reflower classes.
  57. *
  58. * Rendering is relatively straightforward once layout is complete. {@link
  59. * Frame}s are rendered using an adapted {@link Cpdf} class, originally
  60. * written by Wayne Munro, http://www.ros.co.nz/pdf/. (Some performance
  61. * related changes have been made to the original {@link Cpdf} class, and
  62. * the {@link Dompdf\Adapter\CPDF} class provides a simple, stateless interface to
  63. * PDF generation.) PDFLib support has now also been added, via the {@link
  64. * Dompdf\Adapter\PDFLib}.
  65. *
  66. *
  67. * @package dompdf
  68. */
  69. class Dompdf
  70. {
  71. /**
  72. * Version string for dompdf
  73. *
  74. * @var string
  75. */
  76. private $version = 'dompdf';
  77. /**
  78. * DomDocument representing the HTML document
  79. *
  80. * @var DOMDocument
  81. */
  82. private $dom;
  83. /**
  84. * FrameTree derived from the DOM tree
  85. *
  86. * @var FrameTree
  87. */
  88. private $tree;
  89. /**
  90. * Stylesheet for the document
  91. *
  92. * @var Stylesheet
  93. */
  94. private $css;
  95. /**
  96. * Actual PDF renderer
  97. *
  98. * @var Canvas
  99. */
  100. private $canvas;
  101. /**
  102. * Desired paper size ('letter', 'legal', 'A4', etc.)
  103. *
  104. * @var string|array
  105. */
  106. private $paperSize;
  107. /**
  108. * Paper orientation ('portrait' or 'landscape')
  109. *
  110. * @var string
  111. */
  112. private $paperOrientation = "portrait";
  113. /**
  114. * Callbacks on new page and new element
  115. *
  116. * @var array
  117. */
  118. private $callbacks = [];
  119. /**
  120. * Experimental caching capability
  121. *
  122. * @var string
  123. */
  124. private $cacheId;
  125. /**
  126. * Base hostname
  127. *
  128. * Used for relative paths/urls
  129. * @var string
  130. */
  131. private $baseHost = "";
  132. /**
  133. * Absolute base path
  134. *
  135. * Used for relative paths/urls
  136. * @var string
  137. */
  138. private $basePath = "";
  139. /**
  140. * Protocol used to request file (file://, http://, etc)
  141. *
  142. * @var string
  143. */
  144. private $protocol;
  145. /**
  146. * HTTP context created with stream_context_create()
  147. * Will be used for file_get_contents
  148. *
  149. * @var resource
  150. */
  151. private $httpContext;
  152. /**
  153. * Timestamp of the script start time
  154. *
  155. * @var int
  156. */
  157. private $startTime = null;
  158. /**
  159. * The system's locale
  160. *
  161. * @var string
  162. */
  163. private $systemLocale = null;
  164. /**
  165. * The system's mbstring internal encoding
  166. *
  167. * @var string
  168. */
  169. private $mbstringEncoding = null;
  170. /**
  171. * The system's PCRE JIT configuration
  172. *
  173. * @var string
  174. */
  175. private $pcreJit = null;
  176. /**
  177. * The default view of the PDF in the viewer
  178. *
  179. * @var string
  180. */
  181. private $defaultView = "Fit";
  182. /**
  183. * The default view options of the PDF in the viewer
  184. *
  185. * @var array
  186. */
  187. private $defaultViewOptions = [];
  188. /**
  189. * Tells whether the DOM document is in quirksmode (experimental)
  190. *
  191. * @var bool
  192. */
  193. private $quirksmode = false;
  194. /**
  195. * Protocol whitelist
  196. *
  197. * Protocols and PHP wrappers allowed in URLs. Full support is not
  198. * guaranteed for the protocols/wrappers contained in this array.
  199. *
  200. * @var array
  201. */
  202. private $allowedProtocols = [null, "", "file://", "http://", "https://"];
  203. /**
  204. * Local file extension whitelist
  205. *
  206. * File extensions supported by dompdf for local files.
  207. *
  208. * @var array
  209. */
  210. private $allowedLocalFileExtensions = ["htm", "html"];
  211. /**
  212. * @var array
  213. */
  214. private $messages = [];
  215. /**
  216. * @var Options
  217. */
  218. private $options;
  219. /**
  220. * @var FontMetrics
  221. */
  222. private $fontMetrics;
  223. /**
  224. * The list of built-in fonts
  225. *
  226. * @var array
  227. * @deprecated
  228. */
  229. public static $native_fonts = [
  230. "courier", "courier-bold", "courier-oblique", "courier-boldoblique",
  231. "helvetica", "helvetica-bold", "helvetica-oblique", "helvetica-boldoblique",
  232. "times-roman", "times-bold", "times-italic", "times-bolditalic",
  233. "symbol", "zapfdinbats"
  234. ];
  235. /**
  236. * The list of built-in fonts
  237. *
  238. * @var array
  239. */
  240. public static $nativeFonts = [
  241. "courier", "courier-bold", "courier-oblique", "courier-boldoblique",
  242. "helvetica", "helvetica-bold", "helvetica-oblique", "helvetica-boldoblique",
  243. "times-roman", "times-bold", "times-italic", "times-bolditalic",
  244. "symbol", "zapfdinbats"
  245. ];
  246. /**
  247. * Class constructor
  248. *
  249. * @param array|Options $options
  250. */
  251. public function __construct($options = null)
  252. {
  253. if (isset($options) && $options instanceof Options) {
  254. $this->setOptions($options);
  255. } elseif (is_array($options)) {
  256. $this->setOptions(new Options($options));
  257. } else {
  258. $this->setOptions(new Options());
  259. }
  260. $versionFile = realpath(__DIR__ . '/../VERSION');
  261. if (file_exists($versionFile) && ($version = trim(file_get_contents($versionFile))) !== false && $version !== '$Format:<%h>$') {
  262. $this->version = sprintf('dompdf %s', $version);
  263. }
  264. $this->setPhpConfig();
  265. $this->paperSize = $this->options->getDefaultPaperSize();
  266. $this->paperOrientation = $this->options->getDefaultPaperOrientation();
  267. $this->setCanvas(CanvasFactory::get_instance($this, $this->paperSize, $this->paperOrientation));
  268. $this->setFontMetrics(new FontMetrics($this->getCanvas(), $this->getOptions()));
  269. $this->css = new Stylesheet($this);
  270. $this->restorePhpConfig();
  271. }
  272. /**
  273. * Save the system's existing locale, PCRE JIT, and MBString encoding
  274. * configuration and configure the system for Dompdf processing
  275. */
  276. private function setPhpConfig()
  277. {
  278. if (sprintf('%.1f', 1.0) !== '1.0') {
  279. $this->systemLocale = setlocale(LC_NUMERIC, "0");
  280. setlocale(LC_NUMERIC, "C");
  281. }
  282. if (version_compare(PHP_VERSION, '7.0.0') >= 0) {
  283. $this->pcreJit = @ini_get('pcre.jit');
  284. @ini_set('pcre.jit', '0');
  285. }
  286. $this->mbstringEncoding = mb_internal_encoding();
  287. mb_internal_encoding('UTF-8');
  288. }
  289. /**
  290. * Restore the system's locale configuration
  291. */
  292. private function restorePhpConfig()
  293. {
  294. if (!empty($this->systemLocale)) {
  295. setlocale(LC_NUMERIC, $this->systemLocale);
  296. $this->systemLocale = null;
  297. }
  298. if (!empty($this->pcreJit)) {
  299. @ini_set('pcre.jit', $this->pcreJit);
  300. $this->pcreJit = null;
  301. }
  302. if (!empty($this->mbstringEncoding)) {
  303. mb_internal_encoding($this->mbstringEncoding);
  304. $this->mbstringEncoding = null;
  305. }
  306. }
  307. /**
  308. * @param $file
  309. * @deprecated
  310. */
  311. public function load_html_file($file)
  312. {
  313. $this->loadHtmlFile($file);
  314. }
  315. /**
  316. * Loads an HTML file
  317. * Parse errors are stored in the global array _dompdf_warnings.
  318. *
  319. * @param string $file a filename or url to load
  320. * @param string $encoding Encoding of $file
  321. *
  322. * @throws Exception
  323. */
  324. public function loadHtmlFile($file, $encoding = null)
  325. {
  326. $this->setPhpConfig();
  327. if (!$this->protocol && !$this->baseHost && !$this->basePath) {
  328. [$this->protocol, $this->baseHost, $this->basePath] = Helpers::explode_url($file);
  329. }
  330. $protocol = strtolower($this->protocol);
  331. $uri = Helpers::build_url($this->protocol, $this->baseHost, $this->basePath, $file);
  332. if ( !in_array($protocol, $this->allowedProtocols) ) {
  333. throw new Exception("Permission denied on $file. The communication protocol is not supported.");
  334. }
  335. if (!$this->options->isRemoteEnabled() && ($protocol != "" && $protocol !== "file://")) {
  336. throw new Exception("Remote file requested, but remote file download is disabled.");
  337. }
  338. if ($protocol == "" || $protocol === "file://") {
  339. $realfile = realpath($uri);
  340. $chroot = $this->options->getChroot();
  341. $chrootValid = false;
  342. foreach($chroot as $chrootPath) {
  343. $chrootPath = realpath($chrootPath);
  344. if ($chrootPath !== false && strpos($realfile, $chrootPath) === 0) {
  345. $chrootValid = true;
  346. break;
  347. }
  348. }
  349. if ($chrootValid !== true) {
  350. throw new Exception("Permission denied on $file. The file could not be found under the paths specified by Options::chroot.");
  351. }
  352. $ext = strtolower(pathinfo($realfile, PATHINFO_EXTENSION));
  353. if (!in_array($ext, $this->allowedLocalFileExtensions)) {
  354. throw new Exception("Permission denied on $file. This file extension is forbidden");
  355. }
  356. if (!$realfile) {
  357. throw new Exception("File '$file' not found.");
  358. }
  359. $uri = $realfile;
  360. }
  361. [$contents, $http_response_header] = Helpers::getFileContent($uri, $this->httpContext);
  362. if (empty($contents)) {
  363. throw new Exception("File '$file' not found.");
  364. }
  365. // See http://the-stickman.com/web-development/php/getting-http-response-headers-when-using-file_get_contents/
  366. if (isset($http_response_header)) {
  367. foreach ($http_response_header as $_header) {
  368. if (preg_match("@Content-Type:\s*[\w/]+;\s*?charset=([^\s]+)@i", $_header, $matches)) {
  369. $encoding = strtoupper($matches[1]);
  370. break;
  371. }
  372. }
  373. }
  374. $this->restorePhpConfig();
  375. $this->loadHtml($contents, $encoding);
  376. }
  377. /**
  378. * @param string $str
  379. * @param string $encoding
  380. * @deprecated
  381. */
  382. public function load_html($str, $encoding = null)
  383. {
  384. $this->loadHtml($str, $encoding);
  385. }
  386. public function loadDOM($doc, $quirksmode = false) {
  387. // Remove #text children nodes in nodes that shouldn't have
  388. $tag_names = ["html", "head", "table", "tbody", "thead", "tfoot", "tr"];
  389. foreach ($tag_names as $tag_name) {
  390. $nodes = $doc->getElementsByTagName($tag_name);
  391. foreach ($nodes as $node) {
  392. self::removeTextNodes($node);
  393. }
  394. }
  395. $this->dom = $doc;
  396. $this->quirksmode = $quirksmode;
  397. $this->tree = new FrameTree($this->dom);
  398. }
  399. /**
  400. * Loads an HTML string
  401. * Parse errors are stored in the global array _dompdf_warnings.
  402. *
  403. * @param string $str HTML text to load
  404. * @param string $encoding Encoding of $str
  405. */
  406. public function loadHtml($str, $encoding = null)
  407. {
  408. $this->setPhpConfig();
  409. // Determine character encoding when $encoding parameter not used
  410. if ($encoding === null) {
  411. mb_detect_order('auto');
  412. if (($encoding = mb_detect_encoding($str, null, true)) === false) {
  413. //"auto" is expanded to "ASCII,JIS,UTF-8,EUC-JP,SJIS"
  414. $encoding = "auto";
  415. }
  416. }
  417. if (in_array(strtoupper($encoding), array('UTF-8','UTF8')) === false) {
  418. $str = mb_convert_encoding($str, 'UTF-8', $encoding);
  419. //Update encoding after converting
  420. $encoding = 'UTF-8';
  421. }
  422. $metatags = [
  423. '@<meta\s+http-equiv="Content-Type"\s+content="(?:[\w/]+)(?:;\s*?charset=([^\s"]+))?@i',
  424. '@<meta\s+content="(?:[\w/]+)(?:;\s*?charset=([^\s"]+))"?\s+http-equiv="Content-Type"@i',
  425. '@<meta [^>]*charset\s*=\s*["\']?\s*([^"\' ]+)@i',
  426. ];
  427. foreach ($metatags as $metatag) {
  428. if (preg_match($metatag, $str, $matches)) {
  429. if (isset($matches[1]) && in_array($matches[1], mb_list_encodings())) {
  430. $document_encoding = $matches[1];
  431. break;
  432. }
  433. }
  434. }
  435. if (isset($document_encoding) && in_array(strtoupper($document_encoding), ['UTF-8','UTF8']) === false) {
  436. $str = preg_replace('/charset=([^\s"]+)/i', 'charset=UTF-8', $str);
  437. } elseif (isset($document_encoding) === false && strpos($str, '<head>') !== false) {
  438. $str = str_replace('<head>', '<head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8">', $str);
  439. } elseif (isset($document_encoding) === false) {
  440. $str = '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">' . $str;
  441. }
  442. // remove BOM mark from UTF-8, it's treated as document text by DOMDocument
  443. // FIXME: roll this into the encoding detection using UTF-8/16/32 BOM (http://us2.php.net/manual/en/function.mb-detect-encoding.php#91051)?
  444. if (substr($str, 0, 3) == chr(0xEF) . chr(0xBB) . chr(0xBF)) {
  445. $str = substr($str, 3);
  446. }
  447. // Store parsing warnings as messages
  448. set_error_handler([Helpers::class, 'record_warnings']);
  449. try {
  450. // @todo Take the quirksmode into account
  451. // http://hsivonen.iki.fi/doctype/
  452. // https://developer.mozilla.org/en/mozilla's_quirks_mode
  453. $quirksmode = false;
  454. if ($this->options->isHtml5ParserEnabled() && class_exists(HTML5_Tokenizer::class)) {
  455. $tokenizer = new HTML5_Tokenizer($str);
  456. $tokenizer->parse();
  457. $doc = $tokenizer->save();
  458. $quirksmode = ($tokenizer->getTree()->getQuirksMode() > HTML5_TreeBuilder::NO_QUIRKS);
  459. } else {
  460. // loadHTML assumes ISO-8859-1 unless otherwise specified on the HTML document header.
  461. // http://devzone.zend.com/1538/php-dom-xml-extension-encoding-processing/ (see #4)
  462. // http://stackoverflow.com/a/11310258/264628
  463. $doc = new DOMDocument("1.0", $encoding);
  464. $doc->preserveWhiteSpace = true;
  465. $doc->loadHTML($str);
  466. $doc->encoding = $encoding;
  467. // If some text is before the doctype, we are in quirksmode
  468. if (preg_match("/^(.+)<!doctype/i", ltrim($str), $matches)) {
  469. $quirksmode = true;
  470. } // If no doctype is provided, we are in quirksmode
  471. elseif (!preg_match("/^<!doctype/i", ltrim($str), $matches)) {
  472. $quirksmode = true;
  473. } else {
  474. // HTML5 <!DOCTYPE html>
  475. if (!$doc->doctype->publicId && !$doc->doctype->systemId) {
  476. $quirksmode = false;
  477. }
  478. // not XHTML
  479. if (!preg_match("/xhtml/i", $doc->doctype->publicId)) {
  480. $quirksmode = true;
  481. }
  482. }
  483. }
  484. $this->loadDOM($doc, $quirksmode);
  485. } finally {
  486. restore_error_handler();
  487. $this->restorePhpConfig();
  488. }
  489. }
  490. /**
  491. * @param DOMNode $node
  492. * @deprecated
  493. */
  494. public static function remove_text_nodes(DOMNode $node)
  495. {
  496. self::removeTextNodes($node);
  497. }
  498. /**
  499. * @param DOMNode $node
  500. */
  501. public static function removeTextNodes(DOMNode $node)
  502. {
  503. $children = [];
  504. for ($i = 0; $i < $node->childNodes->length; $i++) {
  505. $child = $node->childNodes->item($i);
  506. if ($child->nodeName === "#text") {
  507. $children[] = $child;
  508. }
  509. }
  510. foreach ($children as $child) {
  511. $node->removeChild($child);
  512. }
  513. }
  514. /**
  515. * Builds the {@link FrameTree}, loads any CSS and applies the styles to
  516. * the {@link FrameTree}
  517. */
  518. private function processHtml()
  519. {
  520. $this->tree->build_tree();
  521. $this->css->load_css_file($this->css->getDefaultStylesheet(), Stylesheet::ORIG_UA);
  522. $acceptedmedia = Stylesheet::$ACCEPTED_GENERIC_MEDIA_TYPES;
  523. $acceptedmedia[] = $this->options->getDefaultMediaType();
  524. // <base href="" />
  525. $base_nodes = $this->dom->getElementsByTagName("base");
  526. if ($base_nodes->length && ($href = $base_nodes->item(0)->getAttribute("href"))) {
  527. [$this->protocol, $this->baseHost, $this->basePath] = Helpers::explode_url($href);
  528. }
  529. // Set the base path of the Stylesheet to that of the file being processed
  530. $this->css->set_protocol($this->protocol);
  531. $this->css->set_host($this->baseHost);
  532. $this->css->set_base_path($this->basePath);
  533. // Get all the stylesheets so that they are processed in document order
  534. $xpath = new DOMXPath($this->dom);
  535. $stylesheets = $xpath->query("//*[name() = 'link' or name() = 'style']");
  536. /** @var \DOMElement $tag */
  537. foreach ($stylesheets as $tag) {
  538. switch (strtolower($tag->nodeName)) {
  539. // load <link rel="STYLESHEET" ... /> tags
  540. case "link":
  541. if (mb_strtolower(stripos($tag->getAttribute("rel"), "stylesheet") !== false) || // may be "appendix stylesheet"
  542. mb_strtolower($tag->getAttribute("type")) === "text/css"
  543. ) {
  544. //Check if the css file is for an accepted media type
  545. //media not given then always valid
  546. $formedialist = preg_split("/[\s\n,]/", $tag->getAttribute("media"), -1, PREG_SPLIT_NO_EMPTY);
  547. if (count($formedialist) > 0) {
  548. $accept = false;
  549. foreach ($formedialist as $type) {
  550. if (in_array(mb_strtolower(trim($type)), $acceptedmedia)) {
  551. $accept = true;
  552. break;
  553. }
  554. }
  555. if (!$accept) {
  556. //found at least one mediatype, but none of the accepted ones
  557. //Skip this css file.
  558. break;
  559. }
  560. }
  561. $url = $tag->getAttribute("href");
  562. $url = Helpers::build_url($this->protocol, $this->baseHost, $this->basePath, $url);
  563. $this->css->load_css_file($url, Stylesheet::ORIG_AUTHOR);
  564. }
  565. break;
  566. // load <style> tags
  567. case "style":
  568. // Accept all <style> tags by default (note this is contrary to W3C
  569. // HTML 4.0 spec:
  570. // http://www.w3.org/TR/REC-html40/present/styles.html#adef-media
  571. // which states that the default media type is 'screen'
  572. if ($tag->hasAttributes() &&
  573. ($media = $tag->getAttribute("media")) &&
  574. !in_array($media, $acceptedmedia)
  575. ) {
  576. break;
  577. }
  578. $css = "";
  579. if ($tag->hasChildNodes()) {
  580. $child = $tag->firstChild;
  581. while ($child) {
  582. $css .= $child->nodeValue; // Handle <style><!-- blah --></style>
  583. $child = $child->nextSibling;
  584. }
  585. } else {
  586. $css = $tag->nodeValue;
  587. }
  588. // Set the base path of the Stylesheet to that of the file being processed
  589. $this->css->set_protocol($this->protocol);
  590. $this->css->set_host($this->baseHost);
  591. $this->css->set_base_path($this->basePath);
  592. $this->css->load_css($css, Stylesheet::ORIG_AUTHOR);
  593. break;
  594. }
  595. // Set the base path of the Stylesheet to that of the file being processed
  596. $this->css->set_protocol($this->protocol);
  597. $this->css->set_host($this->baseHost);
  598. $this->css->set_base_path($this->basePath);
  599. }
  600. }
  601. /**
  602. * @param string $cacheId
  603. * @deprecated
  604. */
  605. public function enable_caching($cacheId)
  606. {
  607. $this->enableCaching($cacheId);
  608. }
  609. /**
  610. * Enable experimental caching capability
  611. *
  612. * @param string $cacheId
  613. */
  614. public function enableCaching($cacheId)
  615. {
  616. $this->cacheId = $cacheId;
  617. }
  618. /**
  619. * @param string $value
  620. * @return bool
  621. * @deprecated
  622. */
  623. public function parse_default_view($value)
  624. {
  625. return $this->parseDefaultView($value);
  626. }
  627. /**
  628. * @param string $value
  629. * @return bool
  630. */
  631. public function parseDefaultView($value)
  632. {
  633. $valid = ["XYZ", "Fit", "FitH", "FitV", "FitR", "FitB", "FitBH", "FitBV"];
  634. $options = preg_split("/\s*,\s*/", trim($value));
  635. $defaultView = array_shift($options);
  636. if (!in_array($defaultView, $valid)) {
  637. return false;
  638. }
  639. $this->setDefaultView($defaultView, $options);
  640. return true;
  641. }
  642. /**
  643. * Renders the HTML to PDF
  644. */
  645. public function render()
  646. {
  647. $this->setPhpConfig();
  648. $options = $this->options;
  649. $logOutputFile = $options->getLogOutputFile();
  650. if ($logOutputFile) {
  651. if (!file_exists($logOutputFile) && is_writable(dirname($logOutputFile))) {
  652. touch($logOutputFile);
  653. }
  654. $this->startTime = microtime(true);
  655. if (is_writable($logOutputFile)) {
  656. ob_start();
  657. }
  658. }
  659. $this->processHtml();
  660. $this->css->apply_styles($this->tree);
  661. // @page style rules : size, margins
  662. $pageStyles = $this->css->get_page_styles();
  663. $basePageStyle = $pageStyles["base"];
  664. unset($pageStyles["base"]);
  665. foreach ($pageStyles as $pageStyle) {
  666. $pageStyle->inherit($basePageStyle);
  667. }
  668. $defaultOptionPaperSize = $this->getPaperSize($options->getDefaultPaperSize());
  669. // If there is a CSS defined paper size compare to the paper size used to create the canvas to determine a
  670. // recreation need
  671. if (is_array($basePageStyle->size)) {
  672. $basePageStyleSize = $basePageStyle->size;
  673. $this->setPaper([0, 0, $basePageStyleSize[0], $basePageStyleSize[1]]);
  674. }
  675. $paperSize = $this->getPaperSize();
  676. if (
  677. $defaultOptionPaperSize[2] !== $paperSize[2] ||
  678. $defaultOptionPaperSize[3] !== $paperSize[3] ||
  679. $options->getDefaultPaperOrientation() !== $this->paperOrientation
  680. ) {
  681. $this->setCanvas(CanvasFactory::get_instance($this, $this->paperSize, $this->paperOrientation));
  682. $this->fontMetrics->setCanvas($this->getCanvas());
  683. }
  684. $canvas = $this->getCanvas();
  685. $root = null;
  686. foreach ($this->tree->get_frames() as $frame) {
  687. // Set up the root frame
  688. if (is_null($root)) {
  689. $root = Factory::decorate_root($this->tree->get_root(), $this);
  690. continue;
  691. }
  692. // Create the appropriate decorators, reflowers & positioners.
  693. Factory::decorate_frame($frame, $this, $root);
  694. }
  695. // Add meta information
  696. $title = $this->dom->getElementsByTagName("title");
  697. if ($title->length) {
  698. $canvas->add_info("Title", trim($title->item(0)->nodeValue));
  699. }
  700. $metas = $this->dom->getElementsByTagName("meta");
  701. $labels = [
  702. "author" => "Author",
  703. "keywords" => "Keywords",
  704. "description" => "Subject",
  705. ];
  706. /** @var \DOMElement $meta */
  707. foreach ($metas as $meta) {
  708. $name = mb_strtolower($meta->getAttribute("name"));
  709. $value = trim($meta->getAttribute("content"));
  710. if (isset($labels[$name])) {
  711. $canvas->add_info($labels[$name], $value);
  712. continue;
  713. }
  714. if ($name === "dompdf.view" && $this->parseDefaultView($value)) {
  715. $canvas->set_default_view($this->defaultView, $this->defaultViewOptions);
  716. }
  717. }
  718. $root->set_containing_block(0, 0, $canvas->get_width(), $canvas->get_height());
  719. $root->set_renderer(new Renderer($this));
  720. // This is where the magic happens:
  721. $root->reflow();
  722. // Clean up cached images
  723. Cache::clear();
  724. global $_dompdf_warnings, $_dompdf_show_warnings;
  725. if ($_dompdf_show_warnings && isset($_dompdf_warnings)) {
  726. echo '<b>Dompdf Warnings</b><br><pre>';
  727. foreach ($_dompdf_warnings as $msg) {
  728. echo $msg . "\n";
  729. }
  730. if ($canvas instanceof CPDF) {
  731. echo $canvas->get_cpdf()->messages;
  732. }
  733. echo '</pre>';
  734. flush();
  735. }
  736. if ($logOutputFile && is_writable($logOutputFile)) {
  737. $this->write_log();
  738. ob_end_clean();
  739. }
  740. $this->restorePhpConfig();
  741. }
  742. /**
  743. * Add meta information to the PDF after rendering
  744. */
  745. public function add_info($label, $value)
  746. {
  747. $canvas = $this->getCanvas();
  748. if (!is_null($canvas)) {
  749. $canvas->add_info($label, $value);
  750. }
  751. }
  752. /**
  753. * Writes the output buffer in the log file
  754. *
  755. * @return void
  756. */
  757. private function write_log()
  758. {
  759. $log_output_file = $this->getOptions()->getLogOutputFile();
  760. if (!$log_output_file || !is_writable($log_output_file)) {
  761. return;
  762. }
  763. $frames = Frame::$ID_COUNTER;
  764. $memory = memory_get_peak_usage(true) / 1024;
  765. $time = (microtime(true) - $this->startTime) * 1000;
  766. $out = sprintf(
  767. "<span style='color: #000' title='Frames'>%6d</span>" .
  768. "<span style='color: #009' title='Memory'>%10.2f KB</span>" .
  769. "<span style='color: #900' title='Time'>%10.2f ms</span>" .
  770. "<span title='Quirksmode'> " .
  771. ($this->quirksmode ? "<span style='color: #d00'> ON</span>" : "<span style='color: #0d0'>OFF</span>") .
  772. "</span><br />", $frames, $memory, $time);
  773. $out .= ob_get_contents();
  774. ob_clean();
  775. file_put_contents($log_output_file, $out);
  776. }
  777. /**
  778. * Streams the PDF to the client.
  779. *
  780. * The file will open a download dialog by default. The options
  781. * parameter controls the output. Accepted options (array keys) are:
  782. *
  783. * 'compress' = > 1 (=default) or 0:
  784. * Apply content stream compression
  785. *
  786. * 'Attachment' => 1 (=default) or 0:
  787. * Set the 'Content-Disposition:' HTTP header to 'attachment'
  788. * (thereby causing the browser to open a download dialog)
  789. *
  790. * @param string $filename the name of the streamed file
  791. * @param array $options header options (see above)
  792. */
  793. public function stream($filename = "document.pdf", $options = [])
  794. {
  795. $this->setPhpConfig();
  796. $canvas = $this->getCanvas();
  797. if (!is_null($canvas)) {
  798. $canvas->stream($filename, $options);
  799. }
  800. $this->restorePhpConfig();
  801. }
  802. /**
  803. * Returns the PDF as a string.
  804. *
  805. * The options parameter controls the output. Accepted options are:
  806. *
  807. * 'compress' = > 1 or 0 - apply content stream compression, this is
  808. * on (1) by default
  809. *
  810. * @param array $options options (see above)
  811. *
  812. * @return string|null
  813. */
  814. public function output($options = [])
  815. {
  816. $this->setPhpConfig();
  817. $canvas = $this->getCanvas();
  818. if (is_null($canvas)) {
  819. return null;
  820. }
  821. $output = $canvas->output($options);
  822. $this->restorePhpConfig();
  823. return $output;
  824. }
  825. /**
  826. * @return string
  827. * @deprecated
  828. */
  829. public function output_html()
  830. {
  831. return $this->outputHtml();
  832. }
  833. /**
  834. * Returns the underlying HTML document as a string
  835. *
  836. * @return string
  837. */
  838. public function outputHtml()
  839. {
  840. return $this->dom->saveHTML();
  841. }
  842. /**
  843. * Get the dompdf option value
  844. *
  845. * @param string $key
  846. * @return mixed
  847. * @deprecated
  848. */
  849. public function get_option($key)
  850. {
  851. return $this->options->get($key);
  852. }
  853. /**
  854. * @param string $key
  855. * @param mixed $value
  856. * @return $this
  857. * @deprecated
  858. */
  859. public function set_option($key, $value)
  860. {
  861. $this->options->set($key, $value);
  862. return $this;
  863. }
  864. /**
  865. * @param array $options
  866. * @return $this
  867. * @deprecated
  868. */
  869. public function set_options(array $options)
  870. {
  871. $this->options->set($options);
  872. return $this;
  873. }
  874. /**
  875. * @param string $size
  876. * @param string $orientation
  877. * @deprecated
  878. */
  879. public function set_paper($size, $orientation = "portrait")
  880. {
  881. $this->setPaper($size, $orientation);
  882. }
  883. /**
  884. * Sets the paper size & orientation
  885. *
  886. * @param string|array $size 'letter', 'legal', 'A4', etc. {@link Dompdf\Adapter\CPDF::$PAPER_SIZES}
  887. * @param string $orientation 'portrait' or 'landscape'
  888. * @return $this
  889. */
  890. public function setPaper($size, $orientation = "portrait")
  891. {
  892. $this->paperSize = $size;
  893. $this->paperOrientation = $orientation;
  894. return $this;
  895. }
  896. /**
  897. * Gets the paper size
  898. *
  899. * @param null|string|array $paperSize
  900. * @return int[] A four-element integer array
  901. */
  902. public function getPaperSize($paperSize = null)
  903. {
  904. $size = $paperSize !== null ? $paperSize : $this->paperSize;
  905. if (is_array($size)) {
  906. return $size;
  907. } else if (isset(Adapter\CPDF::$PAPER_SIZES[mb_strtolower($size)])) {
  908. return Adapter\CPDF::$PAPER_SIZES[mb_strtolower($size)];
  909. } else {
  910. return Adapter\CPDF::$PAPER_SIZES["letter"];
  911. }
  912. }
  913. /**
  914. * Gets the paper orientation
  915. *
  916. * @return string Either "portrait" or "landscape"
  917. */
  918. public function getPaperOrientation()
  919. {
  920. return $this->paperOrientation;
  921. }
  922. /**
  923. * @param FrameTree $tree
  924. * @return $this
  925. */
  926. public function setTree(FrameTree $tree)
  927. {
  928. $this->tree = $tree;
  929. return $this;
  930. }
  931. /**
  932. * @return FrameTree
  933. * @deprecated
  934. */
  935. public function get_tree()
  936. {
  937. return $this->getTree();
  938. }
  939. /**
  940. * Returns the underlying {@link FrameTree} object
  941. *
  942. * @return FrameTree
  943. */
  944. public function getTree()
  945. {
  946. return $this->tree;
  947. }
  948. /**
  949. * @param string $protocol
  950. * @return $this
  951. * @deprecated
  952. */
  953. public function set_protocol($protocol)
  954. {
  955. return $this->setProtocol($protocol);
  956. }
  957. /**
  958. * Sets the protocol to use
  959. * FIXME validate these
  960. *
  961. * @param string $protocol
  962. * @return $this
  963. */
  964. public function setProtocol($protocol)
  965. {
  966. $this->protocol = $protocol;
  967. return $this;
  968. }
  969. /**
  970. * @return string
  971. * @deprecated
  972. */
  973. public function get_protocol()
  974. {
  975. return $this->getProtocol();
  976. }
  977. /**
  978. * Returns the protocol in use
  979. *
  980. * @return string
  981. */
  982. public function getProtocol()
  983. {
  984. return $this->protocol;
  985. }
  986. /**
  987. * @param string $host
  988. * @deprecated
  989. */
  990. public function set_host($host)
  991. {
  992. $this->setBaseHost($host);
  993. }
  994. /**
  995. * Sets the base hostname
  996. *
  997. * @param string $baseHost
  998. * @return $this
  999. */
  1000. public function setBaseHost($baseHost)
  1001. {
  1002. $this->baseHost = $baseHost;
  1003. return $this;
  1004. }
  1005. /**
  1006. * @return string
  1007. * @deprecated
  1008. */
  1009. public function get_host()
  1010. {
  1011. return $this->getBaseHost();
  1012. }
  1013. /**
  1014. * Returns the base hostname
  1015. *
  1016. * @return string
  1017. */
  1018. public function getBaseHost()
  1019. {
  1020. return $this->baseHost;
  1021. }
  1022. /**
  1023. * Sets the base path
  1024. *
  1025. * @param string $path
  1026. * @deprecated
  1027. */
  1028. public function set_base_path($path)
  1029. {
  1030. $this->setBasePath($path);
  1031. }
  1032. /**
  1033. * Sets the base path
  1034. *
  1035. * @param string $basePath
  1036. * @return $this
  1037. */
  1038. public function setBasePath($basePath)
  1039. {
  1040. $this->basePath = $basePath;
  1041. return $this;
  1042. }
  1043. /**
  1044. * @return string
  1045. * @deprecated
  1046. */
  1047. public function get_base_path()
  1048. {
  1049. return $this->getBasePath();
  1050. }
  1051. /**
  1052. * Returns the base path
  1053. *
  1054. * @return string
  1055. */
  1056. public function getBasePath()
  1057. {
  1058. return $this->basePath;
  1059. }
  1060. /**
  1061. * @param string $default_view The default document view
  1062. * @param array $options The view's options
  1063. * @return $this
  1064. * @deprecated
  1065. */
  1066. public function set_default_view($default_view, $options)
  1067. {
  1068. return $this->setDefaultView($default_view, $options);
  1069. }
  1070. /**
  1071. * Sets the default view
  1072. *
  1073. * @param string $defaultView The default document view
  1074. * @param array $options The view's options
  1075. * @return $this
  1076. */
  1077. public function setDefaultView($defaultView, $options)
  1078. {
  1079. $this->defaultView = $defaultView;
  1080. $this->defaultViewOptions = $options;
  1081. return $this;
  1082. }
  1083. /**
  1084. * @param resource $http_context
  1085. * @return $this
  1086. * @deprecated
  1087. */
  1088. public function set_http_context($http_context)
  1089. {
  1090. return $this->setHttpContext($http_context);
  1091. }
  1092. /**
  1093. * Sets the HTTP context
  1094. *
  1095. * @param resource $httpContext
  1096. * @return $this
  1097. */
  1098. public function setHttpContext($httpContext)
  1099. {
  1100. $this->httpContext = $httpContext;
  1101. return $this;
  1102. }
  1103. /**
  1104. * @return resource
  1105. * @deprecated
  1106. */
  1107. public function get_http_context()
  1108. {
  1109. return $this->getHttpContext();
  1110. }
  1111. /**
  1112. * Returns the HTTP context
  1113. *
  1114. * @return resource
  1115. */
  1116. public function getHttpContext()
  1117. {
  1118. return $this->httpContext;
  1119. }
  1120. /**
  1121. * @param Canvas $canvas
  1122. * @return $this
  1123. */
  1124. public function setCanvas(Canvas $canvas)
  1125. {
  1126. $this->canvas = $canvas;
  1127. return $this;
  1128. }
  1129. /**
  1130. * @return Canvas
  1131. * @deprecated
  1132. */
  1133. public function get_canvas()
  1134. {
  1135. return $this->getCanvas();
  1136. }
  1137. /**
  1138. * Return the underlying Canvas instance (e.g. Dompdf\Adapter\CPDF, Dompdf\Adapter\GD)
  1139. *
  1140. * @return Canvas
  1141. */
  1142. public function getCanvas()
  1143. {
  1144. return $this->canvas;
  1145. }
  1146. /**
  1147. * @param Stylesheet $css
  1148. * @return $this
  1149. */
  1150. public function setCss(Stylesheet $css)
  1151. {
  1152. $this->css = $css;
  1153. return $this;
  1154. }
  1155. /**
  1156. * @return Stylesheet
  1157. * @deprecated
  1158. */
  1159. public function get_css()
  1160. {
  1161. return $this->getCss();
  1162. }
  1163. /**
  1164. * Returns the stylesheet
  1165. *
  1166. * @return Stylesheet
  1167. */
  1168. public function getCss()
  1169. {
  1170. return $this->css;
  1171. }
  1172. /**
  1173. * @param DOMDocument $dom
  1174. * @return $this
  1175. */
  1176. public function setDom(DOMDocument $dom)
  1177. {
  1178. $this->dom = $dom;
  1179. return $this;
  1180. }
  1181. /**
  1182. * @return DOMDocument
  1183. * @deprecated
  1184. */
  1185. public function get_dom()
  1186. {
  1187. return $this->getDom();
  1188. }
  1189. /**
  1190. * @return DOMDocument
  1191. */
  1192. public function getDom()
  1193. {
  1194. return $this->dom;
  1195. }
  1196. /**
  1197. * @param Options $options
  1198. * @return $this
  1199. */
  1200. public function setOptions(Options $options)
  1201. {
  1202. $this->options = $options;
  1203. $fontMetrics = $this->getFontMetrics();
  1204. if (isset($fontMetrics)) {
  1205. $fontMetrics->setOptions($options);
  1206. }
  1207. return $this;
  1208. }
  1209. /**
  1210. * @return Options
  1211. */
  1212. public function getOptions()
  1213. {
  1214. return $this->options;
  1215. }
  1216. /**
  1217. * @return array
  1218. * @deprecated
  1219. */
  1220. public function get_callbacks()
  1221. {
  1222. return $this->getCallbacks();
  1223. }
  1224. /**
  1225. * Returns the callbacks array
  1226. *
  1227. * @return array
  1228. */
  1229. public function getCallbacks()
  1230. {
  1231. return $this->callbacks;
  1232. }
  1233. /**
  1234. * @param array $callbacks the set of callbacks to set
  1235. * @deprecated
  1236. */
  1237. public function set_callbacks($callbacks)
  1238. {
  1239. $this->setCallbacks($callbacks);
  1240. }
  1241. /**
  1242. * Sets callbacks for events like rendering of pages and elements.
  1243. * The callbacks array contains arrays with 'event' set to 'begin_page',
  1244. * 'end_page', 'begin_frame', or 'end_frame' and 'f' set to a function or
  1245. * object plus method to be called.
  1246. *
  1247. * The function 'f' must take an array as argument, which contains info
  1248. * about the event.
  1249. *
  1250. * @param array $callbacks the set of callbacks to set
  1251. */
  1252. public function setCallbacks($callbacks)
  1253. {
  1254. if (is_array($callbacks)) {
  1255. $this->callbacks = [];
  1256. foreach ($callbacks as $c) {
  1257. if (is_array($c) && isset($c['event']) && isset($c['f'])) {
  1258. $event = $c['event'];
  1259. $f = $c['f'];
  1260. if (is_callable($f) && is_string($event)) {
  1261. $this->callbacks[$event][] = $f;
  1262. }
  1263. }
  1264. }
  1265. }
  1266. }
  1267. /**
  1268. * @return boolean
  1269. * @deprecated
  1270. */
  1271. public function get_quirksmode()
  1272. {
  1273. return $this->getQuirksmode();
  1274. }
  1275. /**
  1276. * Get the quirks mode
  1277. *
  1278. * @return boolean true if quirks mode is active
  1279. */
  1280. public function getQuirksmode()
  1281. {
  1282. return $this->quirksmode;
  1283. }
  1284. /**
  1285. * @param FontMetrics $fontMetrics
  1286. * @return $this
  1287. */
  1288. public function setFontMetrics(FontMetrics $fontMetrics)
  1289. {
  1290. $this->fontMetrics = $fontMetrics;
  1291. return $this;
  1292. }
  1293. /**
  1294. * @return FontMetrics
  1295. */
  1296. public function getFontMetrics()
  1297. {
  1298. return $this->fontMetrics;
  1299. }
  1300. /**
  1301. * PHP5 overloaded getter
  1302. * Along with {@link Dompdf::__set()} __get() provides access to all
  1303. * properties directly. Typically __get() is not called directly outside
  1304. * of this class.
  1305. *
  1306. * @param string $prop
  1307. *
  1308. * @throws Exception
  1309. * @return mixed
  1310. */
  1311. function __get($prop)
  1312. {
  1313. switch ($prop)
  1314. {
  1315. case 'version' :
  1316. return $this->version;
  1317. default:
  1318. throw new Exception( 'Invalid property: ' . $prop );
  1319. }
  1320. }
  1321. }