PluginProbe
Taskbuilder – Project Management & Task Management Tool With Kanban Board / 6.0.6
Taskbuilder – Project Management & Task Management Tool With Kanban Board v6.0.6
6.0.6 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 All 58 releases
← All changes | asset/lib/fpdf/Dompdf.php +1470 -1470 5.0.76.0.6 View file →
@@ -1,1470 +1,1470 @@
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 -}
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 +}