PluginProbe
Taskbuilder – Project Management & Task Management Tool With Kanban Board / 6.0.4
Taskbuilder – Project Management & Task Management Tool With Kanban Board v6.0.4
6.0.5 6.0.2 6.0.3 6.0.4 6.0.1 6.0.0 5.0.8 5.0.9 4.0.9 5.0.0 5.0.1 5.0.2 5.0.3 5.0.4 5.0.5 5.0.6 5.0.7 trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.0.4 1.0.5 1.0.6 All 57 releases
taskbuilder / asset / lib / fpdf / Dompdf.php

Dompdf.php in Taskbuilder – Project Management & Task Management Tool With Kanban Board 6.0.4, at asset/lib/fpdf/Dompdf.php

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