Completed
Branch development (b1b115)
by Johannes
10:28
created

Dompdf   F

Complexity

Total Complexity 182

Size/Duplication

Total Lines 1439
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 182
c 0
b 0
f 0
dl 0
loc 1439
rs 0.8

How to fix   Complexity   

Complex Class

Complex classes like Dompdf often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Dompdf, and based on these observations, apply Extract Interface, too.

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