Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
35.41% covered (warning)
35.41%
205 / 579
63.83% covered (warning)
63.83%
30 / 47
CRAP
0.00% covered (danger)
0.00%
0 / 1
Parser
35.41% covered (warning)
35.41%
205 / 579
63.83% covered (warning)
63.83%
30 / 47
14991.43
0.00% covered (danger)
0.00%
0 / 1
 __construct
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 parseString
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 parseFile
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
1
 parseUri
0.00% covered (danger)
0.00%
0 / 3
0.00% covered (danger)
0.00%
0 / 1
2
 setDocument
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 parseHtml
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 parseHtmlFile
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 parseHtmlUri
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 parseCss
75.00% covered (success)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 parseCssFile
75.00% covered (success)
75.00%
3 / 4
0.00% covered (danger)
0.00%
0 / 1
2.06
 parseCssUri
0.00% covered (danger)
0.00%
0 / 4
0.00% covered (danger)
0.00%
0 / 1
6
 getDocument
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 document
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setPageSize
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getPageSize
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setPageMargins
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 setPageTopMargin
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setPageRightMargin
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setPageBottomMargin
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setPageLeftMargin
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setDefaultStyle
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setX
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 setY
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 getPageMargins
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPageTopMargin
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPageRightMargin
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPageBottomMargin
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getPageLeftMargin
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getX
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getY
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getDefaultStyle
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 getDefaultStyles
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCss
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getHtml
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 prepare
31.25% covered (danger)
31.25%
5 / 16
0.00% covered (danger)
0.00%
0 / 1
58.79
 process
71.43% covered (success)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
4.37
 addNodeToDocument
17.83% covered (danger)
17.83%
28 / 157
0.00% covered (danger)
0.00%
0 / 1
1436.79
 addNodeStreamToDocument
0.00% covered (danger)
0.00%
0 / 39
0.00% covered (danger)
0.00%
0 / 1
90
 createDefaultStyles
98.11% covered (success)
98.11%
52 / 53
0.00% covered (danger)
0.00%
0 / 1
9
 prepareNodeStyles
22.96% covered (danger)
22.96%
45 / 196
0.00% covered (danger)
0.00%
0 / 1
3710.95
 getCurrentX
100.00% covered (success)
100.00%
3 / 3
100.00% covered (success)
100.00%
1 / 1
2
 resetX
0.00% covered (danger)
0.00%
0 / 2
0.00% covered (danger)
0.00%
0 / 1
2
 getCurrentY
66.67% covered (warning)
66.67%
8 / 12
0.00% covered (danger)
0.00%
0 / 1
5.93
 resetY
0.00% covered (danger)
0.00%
0 / 7
0.00% covered (danger)
0.00%
0 / 1
12
 newPage
0.00% covered (danger)
0.00%
0 / 5
0.00% covered (danger)
0.00%
0 / 1
6
 goToNextLine
0.00% covered (danger)
0.00%
0 / 1
0.00% covered (danger)
0.00%
0 / 1
2
 getStringLines
0.00% covered (danger)
0.00%
0 / 12
0.00% covered (danger)
0.00%
0 / 1
30
1<?php
2/**
3 * Pop PHP Framework (http://www.popphp.org/)
4 *
5 * @link       https://github.com/popphp/popphp-framework
6 * @author     Nick Sagona, III <dev@nolainteractive.com>
7 * @copyright  Copyright (c) 2009-2023 NOLA Interactive, LLC. (http://www.nolainteractive.com)
8 * @license    http://www.popphp.org/license     New BSD License
9 */
10
11/**
12 * @namespace
13 */
14namespace Pop\Pdf\Build\Html;
15
16use Pop\Css;
17use Pop\Dom\Child;
18use Pop\Pdf\Build\Compiler;
19use Pop\Pdf\Document;
20
21/**
22 * Pdf HTML parser class
23 *
24 * @category   Pop
25 * @package    Pop\Pdf
26 * @author     Nick Sagona, III <dev@nolainteractive.com>
27 * @copyright  Copyright (c) 2009-2023 NOLA Interactive, LLC. (http://www.nolainteractive.com)
28 * @license    http://www.popphp.org/license     New BSD License
29 * @version    4.2.0
30 */
31class Parser
32{
33
34    /**
35     * PDF document
36     * @var Document
37     */
38    protected $document = null;
39
40    /**
41     * HTML object or array of HTML objects
42     * @var Child|array
43     */
44    protected $html = null;
45
46    /**
47     * CSS object
48     * @var Css\Css
49     */
50    protected $css = null;
51
52    /**
53     * Page size
54     * @var string
55     */
56    protected $pageSize = 'LETTER';
57
58    /**
59     * Page margins
60     * @var array
61     */
62    protected $pageMargins = [
63        'top'    => 80,
64        'right'  => 60,
65        'bottom' => 60,
66        'left'   => 60
67    ];
68
69    /**
70     * Default styles
71     * @var array
72     */
73    protected $defaultStyles = [
74        'font-family' => 'Arial',
75        'font-size'   => 10,
76        'font-weight' => 'normal',
77        'color'       => [0, 0, 0],
78        'line-height' => 14
79    ];
80
81    /**
82     * Current x-position
83     * @var int
84     */
85    protected $x = 0;
86
87    /**
88     * Current y-position
89     * @var int
90     */
91    protected $y = 0;
92
93    /**
94     * Current page object
95     * @var Document\Page
96     */
97    protected $page = null;
98
99    /**
100     * HTML file directory
101     * @var string
102     */
103    protected $fileDir = null;
104
105    /**
106     * Text wrap object
107     * @var Document\Page\Text\Wrap
108     */
109    protected $textWrap = null;
110
111    /**
112     * Y-override
113     * @var int
114     */
115    protected $yOverride = null;
116
117    /**
118     * Constructor
119     *
120     * Instantiate the HTML parser object
121     *
122     * @param  Document $document
123     */
124    public function __construct(Document $document = null)
125    {
126        if (null !== $document) {
127            $this->setDocument($document);
128        }
129        $this->createDefaultStyles();
130    }
131
132    /**
133     * Parse HTML string
134     *
135     * @param  string   $htmlString
136     * @param  Document $document
137     * @return self
138     */
139    public static function parseString($htmlString, Document $document = null)
140    {
141        $html = new self($document);
142        $html->parseHtml($htmlString);
143
144        return $html;
145    }
146
147    /**
148     * Parse $html from file
149     *
150     * @param  string   $htmlFile
151     * @param  Document $document
152     * @return self
153     */
154    public static function parseFile($htmlFile, Document $document = null)
155    {
156        $html = new self($document);
157        $html->parseHtmlFile($htmlFile);
158
159        return $html;
160    }
161
162    /**
163     * Parse $html from URI
164     *
165     * @param  string   $htmlUri
166     * @param  Document $document
167     * @return self
168     */
169    public static function parseUri($htmlUri, Document $document = null)
170    {
171        $html = new self($document);
172        $html->parseHtmlUri($htmlUri);
173
174        return $html;
175    }
176
177    /**
178     * Set document
179     *
180     * @param  Document $document
181     * @return self
182     */
183    public function setDocument(Document $document)
184    {
185        $this->document = $document;
186        return $this;
187    }
188
189    /**
190     * Parse HTML string
191     *
192     * @param  string $htmlString
193     * @param  string $basePath
194     * @return self
195     */
196    public function parseHtml($htmlString, $basePath = null)
197    {
198        if (null !== $basePath) {
199            $this->fileDir = $basePath;
200        }
201        $this->html = Child::parseString($htmlString);
202        return $this;
203    }
204
205    /**
206     * Parse HTML string from file
207     *
208     * @param  string $htmlFile
209     * @throws Exception
210     * @return self
211     */
212    public function parseHtmlFile($htmlFile)
213    {
214        if (!file_exists($htmlFile)) {
215            throw new Exception('Error: That file does not exist.');
216        }
217        return $this->parseHtml(file_get_contents($htmlFile), dirname(realpath($htmlFile)));
218    }
219
220    /**
221     * Parse HTML string from URI
222     *
223     * @param  string $htmlUri
224     * @throws Exception
225     * @return self
226     */
227    public function parseHtmlUri($htmlUri)
228    {
229        return $this->parseHtml(file_get_contents($htmlUri));
230    }
231
232    /**
233     * Parse CSS string
234     *
235     * @param  string $cssString
236     * @return self
237     */
238    public function parseCss($cssString)
239    {
240        if (null === $this->css) {
241            $this->css = Css\Css::parseString($cssString);
242        } else {
243            $this->css->parseCss($cssString);
244        }
245        return $this;
246    }
247
248    /**
249     * Parse CSS file
250     *
251     * @param  string $cssFile
252     * @return self
253     */
254    public function parseCssFile($cssFile)
255    {
256        if (null === $this->css) {
257            $this->css = Css\Css::parseFile($cssFile);
258        } else {
259            $this->css->parseCssFile($cssFile);
260        }
261        return $this;
262    }
263
264    /**
265     * Parse CSS URI
266     *
267     * @param  string $cssUri
268     * @return self
269     */
270    public function parseCssUri($cssUri)
271    {
272        if (null === $this->css) {
273            $this->css = Css\Css::parseUri($cssUri);
274        } else {
275            $this->css->parseCssUri($cssUri);
276        }
277        return $this;
278    }
279
280    /**
281     * Get document
282     *
283     * @return Document
284     */
285    public function getDocument()
286    {
287        return $this->document;
288    }
289
290    /**
291     * Get document (alias)
292     *
293     * @return Document
294     */
295    public function document()
296    {
297        return $this->document;
298    }
299
300    /**
301     * Set page size
302     *
303     * @param  mixed $size
304     * @param  mixed $height
305     * @return self
306     */
307    public function setPageSize($size, $height = null)
308    {
309        $this->pageSize = (null !== $height) ? ['width' => $size, 'height' => $height] : $size;
310
311        return $this;
312    }
313
314    /**
315     * Get page size
316     *
317     * @return string|array
318     */
319    public function getPageSize()
320    {
321        return $this->pageSize;
322    }
323
324    /**
325     * Set page margins
326     *
327     * @param  int $top
328     * @param  int $right
329     * @param  int $bottom
330     * @param  int $left
331     * @return self
332     */
333    public function setPageMargins($top, $right, $bottom, $left)
334    {
335        $this->pageMargins['top']    = $top;
336        $this->pageMargins['right']  = $right;
337        $this->pageMargins['bottom'] = $bottom;
338        $this->pageMargins['left']   = $left;
339        return $this;
340    }
341
342    /**
343     * Set page top margin
344     *
345     * @param  int $margin
346     * @return self
347     */
348    public function setPageTopMargin($margin)
349    {
350        $this->pageMargins['top'] = $margin;
351        return $this;
352    }
353
354    /**
355     * Set page right margin
356     *
357     * @param  int $margin
358     * @return self
359     */
360    public function setPageRightMargin($margin)
361    {
362        $this->pageMargins['right'] = $margin;
363        return $this;
364    }
365
366    /**
367     * Set page bottom margin
368     *
369     * @param  int $margin
370     * @return self
371     */
372    public function setPageBottomMargin($margin)
373    {
374        $this->pageMargins['bottom'] = $margin;
375        return $this;
376    }
377
378    /**
379     * Set page left margin
380     *
381     * @param  int $margin
382     * @return self
383     */
384    public function setPageLeftMargin($margin)
385    {
386        $this->pageMargins['left'] = $margin;
387        return $this;
388    }
389
390    /**
391     * Set a default style
392     *
393     * @param  string $property
394     * @param  string $value
395     * @return self
396     */
397    public function setDefaultStyle($property, $value)
398    {
399        $this->defaultStyles[$property] = $value;
400        return $this;
401    }
402
403    /**
404     * Set x-position
405     *
406     * @param  int $x
407     * @return self
408     */
409    public function setX($x)
410    {
411        $this->x = $x;
412        return $this;
413    }
414
415    /**
416     * Set y-position
417     *
418     * @param  int $y
419     * @return self
420     */
421    public function setY($y)
422    {
423        $this->y = $y;
424        return $this;
425    }
426
427    /**
428     * Get page margins
429     *
430     * @return array
431     */
432    public function getPageMargins()
433    {
434        return $this->pageMargins;
435    }
436
437    /**
438     * Get page top margin
439     *
440     * @return int
441     */
442    public function getPageTopMargin()
443    {
444        return $this->pageMargins['top'];
445    }
446
447    /**
448     * Get page right margin
449     *
450     * @return int
451     */
452    public function getPageRightMargin()
453    {
454        return $this->pageMargins['right'];
455    }
456
457    /**
458     * Get page bottom margin
459     *
460     * @return int
461     */
462    public function getPageBottomMargin()
463    {
464        return $this->pageMargins['bottom'];
465    }
466
467    /**
468     * Get page left margin
469     *
470     * @return int
471     */
472    public function getPageLeftMargin()
473    {
474        return $this->pageMargins['left'];
475    }
476
477    /**
478     * Get x-position
479     *
480     * @return int
481     */
482    public function getX()
483    {
484        return $this->x;
485    }
486
487    /**
488     * Get y-position
489     *
490     * @return int
491     */
492    public function getY()
493    {
494        return $this->y;
495    }
496
497    /**
498     * Get a default style
499     *
500     * @param  string $property
501     * @return string
502     */
503    public function getDefaultStyle($property)
504    {
505        return (isset($this->defaultStyles[$property])) ? $this->defaultStyles[$property] : null;
506    }
507
508    /**
509     * Get default styles
510     *
511     * @return array
512     */
513    public function getDefaultStyles()
514    {
515        return $this->defaultStyles;
516    }
517
518    /**
519     * Get styles
520     *
521     * @return Css\Css
522     */
523    public function getCss()
524    {
525        return $this->css;
526    }
527
528    /**
529     * Get HTML nodes
530     *
531     * @return Child|array
532     */
533    public function getHtml()
534    {
535        return $this->html;
536    }
537
538    /**
539     * Prepare for conversion of HTML into PDF objects
540     *
541     * @return array|Child
542     */
543    public function prepare()
544    {
545        $htmlNodes = null;
546        if ($this->html instanceof Child) {
547            foreach ($this->html->getChildNodes() as $child) {
548                if ($child->getNodeName() == 'head') {
549                    foreach ($child->getChildNodes() as $c) {
550                        if (($c->getNodeName() == 'link') && ($c->hasAttribute('href')) &&
551                            ($c->hasAttribute('type')) && ($c->getAttribute('type') == 'text/css')) {
552                            $href = $c->getAttribute('href');
553                            if (null === $this->css) {
554                                $this->css = Css\Css::parseFile($this->fileDir . '/' . $href);
555                            } else {
556                                $this->css->parseCssFile($this->fileDir . '/' . $href);
557                            }
558                        }
559                    }
560                } else if ($child->getNodeName() == 'body') {
561                    $htmlNodes = $child;
562                }
563            }
564        }
565
566        if (null === $htmlNodes) {
567            $htmlNodes = $this->html;
568        }
569
570        return $htmlNodes;
571    }
572
573    /**
574     * Process conversion of HTML into PDF objects
575     *
576     * @return Document
577     */
578    public function process()
579    {
580        $htmlNodes = $this->prepare();
581
582        if ($htmlNodes instanceof Child) {
583            foreach ($htmlNodes->getChildNodes() as $child) {
584                $this->addNodeToDocument($child);
585            }
586        } else {
587            foreach ($htmlNodes as $child) {
588                $this->addNodeToDocument($child);
589            }
590        }
591
592        return $this->document;
593    }
594
595    /**
596     * Add node to document
597     *
598     * @param  Child   $child
599     * @throws Exception
600     * @return void
601     */
602    protected function addNodeToDocument(Child $child)
603    {
604        $styles   = $this->prepareNodeStyles($child->getNodeName(), $child->getAttributes());
605        $currentX = $this->getCurrentX();
606
607        if (null !== $this->yOverride) {
608            $currentY        = $this->yOverride;
609            $this->yOverride = null;
610        } else {
611            $currentY = $this->getCurrentY();
612        }
613
614        $wrapLength = ($this->x > $this->pageMargins['left']) ?
615            $this->page->getWidth() - $this->pageMargins['right'] - $this->x :
616            $this->page->getWidth() - $this->pageMargins['right'] - $this->pageMargins['left'];
617
618        // Image node
619        if ($child->getNodeName() == 'img') {
620            $image = Document\Page\Image::createImageFromFile($this->fileDir . '/' . $child->getAttribute('src'));
621            $width = null;
622            $height = null;
623            $align = null;
624
625            if ($child->hasAttribute('width')) {
626                $width = (strpos($child->getAttribute('width'), '%')) ?
627                    $this->page->getWidth() * ((int)$child->getAttribute('width') / 100) : (int)$child->getAttribute('width');
628            } else if ($child->hasAttribute('height')) {
629                $height = (strpos($child->getAttribute('height'), '%')) ?
630                    $this->page->getHeight() * ((int)$child->getAttribute('height') / 100) : (int)$child->getAttribute('height');
631            } else if (null !== $styles['width']) {
632                $width = (strpos($styles['width'], '%')) ?
633                    $this->page->getWidth() * ((int)$styles['width'] / 100) : (int)$styles['width'];
634            } else if (null !== $styles['height']) {
635                $height = (strpos($styles['height'], '%')) ?
636                    $this->page->getHeight() * ((int)$styles['height'] / 100) : (int)$styles['height'];
637            }
638
639            if (null !== $width) {
640                $image->resizeToWidth($width);
641            } else if (null !== $height) {
642                $image->resizeToHeight($height);
643            }
644
645            if (null === $height) {
646                $height = (null !== $image->getResizedHeight()) ? $image->getResizedHeight() : $image->getHeight();
647            }
648
649            if ($child->hasAttribute('align')) {
650                $align = strtoupper($child->getAttribute('align'));
651            } else if (isset($styles['float'])) {
652                $align = strtoupper($styles['float']);
653            }
654
655            if ($align == 'LEFT') {
656                $box = [
657                    'left' => $currentX,
658                    'right' => $currentX + $width + ($styles['marginRight'] ?? 0),
659                    'top' => $currentY,
660                    'bottom' => $currentY - $height - ($styles['marginBottom'] ?? 0)
661                ];
662                $this->textWrap = new Document\Page\Text\Wrap('RIGHT', $this->pageMargins['left'], $this->page->getWidth() - $this->pageMargins['right'], $box);
663            } else if ($align == 'RIGHT') {
664                $box = [
665                    'left' => $this->page->getWidth() - $this->pageMargins['right'] - $width - ($styles['marginLeft'] ?? 0),
666                    'right' => $this->page->getWidth() - $this->pageMargins['right'],
667                    'top' => $currentY,
668                    'bottom' => $currentY - $height - ($styles['marginBottom'] ?? 0)
669                ];
670                $this->textWrap = new Document\Page\Text\Wrap('LEFT', $this->pageMargins['left'], $this->page->getWidth() - $this->pageMargins['right'], $box);
671            }
672
673            if (null !== $this->textWrap) {
674                $newY = $currentY - ((null !== $image->getResizedHeight()) ? $image->getResizedHeight() : $image->getHeight());
675                if ($align == 'RIGHT') {
676                    $this->page->addImage($image, ($this->page->getWidth() - $this->pageMargins['right'] - $width), $newY);
677                } else {
678                    $this->page->addImage($image, $currentX, $newY);
679                }
680                $currentY -= $styles['lineHeight'];
681                $this->y += $styles['lineHeight'];
682            } else {
683                $currentY -= (null !== $image->getResizedHeight()) ? $image->getResizedHeight() : $image->getHeight();
684                $this->y += (null !== $image->getResizedHeight()) ? $image->getResizedHeight() : $image->getHeight();
685                $this->page->addImage($image, $currentX, $currentY);
686                $currentY -= $styles['lineHeight'];
687                $this->y += $styles['lineHeight'];
688            }
689        // Table node
690        } else if ($child->getNodeName() == 'table') {
691            $tableWidth  = $this->page->getWidth() - $this->pageMargins['left'] - $this->pageMargins['right'];
692            $columnCount = count($child->getChild(0)->getChildNodes());;
693            $rowCount    = 0;
694            $columnWidth = floor($tableWidth / $columnCount);
695            $fontObject  = $this->document->getFont($styles['currentFont']);
696            $currentRow  = 0;
697            $currentX    = $this->pageMargins['left'] + 10;
698            $thHeight    = 0;
699            $offset      = 0;
700            $startY      = $currentY;
701
702            foreach ($child->getChildNodes() as $childNode) {
703                if (($childNode->getNodeName() == 'tr') && ($childNode->hasChildNodes())) {
704                    foreach ($childNode->getChildNodes() as $grandChild) {
705                        if ($grandChild->getNodeName() == 'th') {
706                            $thString = $grandChild->getNodeValue();
707
708                            $thStringWidth = $fontObject->getStringWidth($thString, $styles['fontSize']);
709                            if ($thStringWidth > ($columnWidth - 20)) {
710                                $strings = $this->getStringLines($thString, $styles['fontSize'], $columnWidth - 20, $fontObject);
711                                foreach ($strings as $i => $string) {
712                                    $text = new Document\Page\Text($string, $styles['fontSize']);
713                                    $this->page->addText($text, $styles['currentFont'], $currentX, ($currentY - ($styles['fontSize'] * $i)));
714                                }
715                                $newThHeight = (count($strings) * $styles['fontSize']) + 20;
716                                if ($newThHeight > $thHeight) {
717                                    $thHeight = $newThHeight;
718                                    $offset   = $thHeight - 30;
719                                }
720                            } else {
721                                $text = new Document\Page\Text($thString, $styles['fontSize']);
722                                $this->page->addText($text, $styles['currentFont'], $currentX, $currentY);
723                            }
724                            $currentX += $columnWidth;
725                        }
726                    }
727                }
728            }
729
730            foreach ($child->getChildNodes() as $childNode) {
731                if (($childNode->getNodeName() == 'tr') && ($childNode->hasChildNodes())) {
732                    $rowCount++;
733                    $currentX  = $this->pageMargins['left'] + 10;
734                    foreach ($childNode->getChildNodes() as $grandChild) {
735                        if ($grandChild->getNodeName() == 'td') {
736                            $text = new Document\Page\Text($grandChild->getNodeValue(), $styles['fontSize']);
737                            $this->page->addText($text, $styles['currentFont'], $currentX, $currentY - $offset);
738                            $currentX += $columnWidth;
739                        }
740                    }
741                    $currentY -= 25;
742                }
743            }
744
745            $currentY += 15;
746            $x1 = $this->pageMargins['left'];
747/*
748
749            $finalHeight = (25 * $rowCount);
750            $path    = new Document\Page\Path();
751            $path->drawRectangle($x1, $currentY, $tableWidth, $finalHeight);
752            $this->page->addPath($path);
753
754            for ($i = 1; $i < $columnCount; $i++) {
755                $path = new Document\Page\Path();
756                $path->drawLine($x1 + ($i * $columnWidth), $currentY, $x1 + ($i * $columnWidth), $this->page->getHeight() - $finalHeight + 35);
757                $this->page->addPath($path);
758            }
759            */
760
761            for ($i = 0; $i < $rowCount; $i++) {
762                if (($i == 0) && ($thHeight > 0)) {
763                    $lineY = $startY - $thHeight + 20;
764                } else {
765                    $lineY = ($startY - ($i * 25) - 10) - $offset;
766                }
767                $path  = new Document\Page\Path();
768                $path->drawLine($x1, $lineY, $tableWidth + $this->pageMargins['left'], $lineY);
769                $this->page->addPath($path);
770                /*
771                if (($i == 1) && ($thHeight > 0)) {
772                    $lineY = ($currentY + ($i * 25));
773                } else {
774                    $lineY = ($currentY + ($i * 25));
775                }
776                $path  = new Document\Page\Path();
777                $path->drawLine($x1, $lineY, $tableWidth + $this->pageMargins['left'], $lineY);
778                $this->page->addPath($path);
779                */
780            }
781
782
783        // Text node
784        } else {
785            if (null !== $this->textWrap) {
786                $box = $this->textWrap->getBox();
787                if ($this->textWrap->isRight()) {
788                    $startX = $box['right'];
789                    $startY = $box['top'] - $styles['fontSize'];
790                    $edgeX  = $wrapLength;
791                    $edgeY  = $box['bottom'];
792                } else {
793                    $startX = $currentX;
794                    $startY = $box['top'] - $styles['fontSize'];
795                    $edgeX  = $box['left'] - 40;
796                    $edgeY  = $box['bottom'];
797                }
798            } else {
799                $startX = $currentX;
800                $startY = $currentY;
801                $edgeX  = $wrapLength;
802                $edgeY  = $this->pageMargins['bottom'];
803            }
804
805            $textStream = new Document\Page\Text\Stream($startX, $startY, $edgeX, $edgeY);
806            $textStream->setCurrentStyle(
807                $styles['currentFont'],
808                $styles['fontSize'],
809                new Document\Page\Color\Rgb($styles['color'][0], $styles['color'][1], $styles['color'][2])
810            );
811            $streamY = $styles['lineHeight'] ?? null;
812            $textStream->addText($child->getNodeValue(), $streamY);
813
814            if ($child->hasChildNodes()) {
815                foreach ($child->getChildNodes() as $grandChild) {
816                    $gcStyles = $this->prepareNodeStyles($grandChild->getNodeName(), $grandChild->getAttributes(), $styles);
817                    $textStream->setCurrentStyle(
818                        $gcStyles['currentFont'],
819                        $gcStyles['fontSize'],
820                        new Document\Page\Color\Rgb($gcStyles['color'][0], $gcStyles['color'][1], $gcStyles['color'][2])
821                    );
822                    $streamY = $gcStyles['lineHeight'] ?? null;
823                    $textStream->addText($grandChild->getNodeValue(), $streamY);
824                }
825            }
826
827            $this->page->addTextStream($textStream);
828
829            $orphanStream = clone $textStream;
830            $hasOrphans   = false;
831
832            while ($orphanStream->hasOrphans($this->document->getFonts())) {
833                $orphanStream = $orphanStream->getOrphanStream();
834                if ($orphanStream->getCurrentY() <= $this->pageMargins['bottom']) {
835                    $currentY = $this->newPage();
836                    $orphanStream->setCurrentY($currentY);
837                } else {
838                    $orphanStream->setStartX($this->pageMargins['left']);
839                    $orphanStream->setEdgeX($wrapLength);
840                    $orphanStream->setEdgeY($this->pageMargins['bottom']);
841                }
842
843                $orphanStream->setCurrentX($currentX);
844                $this->page->addTextStream($orphanStream);
845
846                $orphanStream = clone $orphanStream;
847                $hasOrphans = true;
848            }
849
850            if ($hasOrphans) {
851                $this->yOverride = $orphanStream->getCurrentY();
852            } else {
853                $this->yOverride = null;
854                $this->y  += (!empty($styles['marginBottom'])) ? $styles['marginBottom'] : 25;
855            }
856        }
857    }
858
859    /**
860     * Add node stream to document
861     *
862     * @param  Child $child
863     * @throws Exception
864     * @return void
865     */
866    protected function addNodeStreamToDocument(Child $child)
867    {
868        $styles     = $this->prepareNodeStyles($child->getNodeName(), $child->getAttributes());
869        $currentX   = $this->getCurrentX();
870        $currentY   = $this->getCurrentY();
871        $fontObject = $this->document->getFont($styles['currentFont']);
872        $wrapLength = ($this->x > $this->pageMargins['left']) ?
873            $this->page->getWidth() - $this->pageMargins['right'] - $this->x :
874            $this->page->getWidth() - $this->pageMargins['right'] - $this->pageMargins['left'];
875
876        $string      = $child->getNodeValue();
877        $stringWidth = $fontObject->getStringWidth($string, $styles['fontSize']);
878
879        if ($stringWidth > $wrapLength) {
880            $strings = $this->getStringLines($string, $styles['fontSize'], $wrapLength, $fontObject);
881            if ($this->x > $this->pageMargins['left']) {
882                $text = new Document\Page\Text($strings[0], $styles['fontSize']);
883                $text->setFillColor(new Document\Page\Color\Rgb($styles['color'][0], $styles['color'][1], $styles['color'][2]));
884                $this->page->addText($text, $styles['currentFont'], $currentX, $currentY);
885                if ($currentY <= $this->pageMargins['bottom']) {
886                    $currentY = $this->newPage();
887                } else {
888                    $currentY -= $styles['lineHeight'];
889                    $this->y  += $styles['lineHeight'];
890                }
891                $currentX = $this->resetX();
892                $wrapLength = $this->page->getWidth() - $this->pageMargins['right'] - $this->pageMargins['left'];
893                unset($strings[0]);
894                $strings = $this->getStringLines(implode(' ', $strings), $styles['fontSize'], $wrapLength, $fontObject);
895            }
896
897            foreach ($strings as $i => $string) {
898                $text = new Document\Page\Text($string, $styles['fontSize']);
899                $text->setFillColor(new Document\Page\Color\Rgb($styles['color'][0], $styles['color'][1], $styles['color'][2]));
900                $this->page->addText($text, $styles['currentFont'], $currentX, $currentY);
901                if ($i < (count($strings) - 1)) {
902                    if ($currentY <= $this->pageMargins['bottom']) {
903                        $currentY = $this->newPage();
904                    } else {
905                        $currentY -= $styles['lineHeight'];
906                        $this->y  += $styles['lineHeight'];
907                    }
908                }
909            }
910            $this->x += $fontObject->getStringWidth($string, $styles['fontSize']);
911        } else {
912            $text = new Document\Page\Text($string, $styles['fontSize']);
913            $text->setFillColor(new Document\Page\Color\Rgb($styles['color'][0], $styles['color'][1], $styles['color'][2]));
914            $this->page->addText($text, $styles['currentFont'], $currentX, $currentY);
915            $this->x += $fontObject->getStringWidth($string, $styles['fontSize']);
916        }
917
918        foreach ($child->getChildNodes() as $grandChild) {
919            $this->addNodeStreamToDocument($grandChild);
920        }
921    }
922
923    /**
924     * Create default styles
925     *
926     * @return void
927     */
928    protected function createDefaultStyles()
929    {
930        $h1 = new Css\Selector('h1');
931        $h1['margin-bottom'] = '18px';
932        $h1['font-size']     = '32px';
933        $h1['font-weight']   = 'bold';
934
935        $h2 = new Css\Selector('h2');
936        $h2['margin-bottom'] = '18px';
937        $h2['font-size']     = '28px';
938        $h2['font-weight']   = 'bold';
939
940        $h3 = new Css\Selector('h3');
941        $h3['margin-bottom'] = '16px';
942        $h3['font-size']     = '24px';
943        $h3['font-weight']   = 'bold';
944
945        $h4 = new Css\Selector('h4');
946        $h4['margin-bottom'] = '14px';
947        $h4['font-size']     = '20px';
948        $h4['font-weight']   = 'bold';
949
950        $h5 = new Css\Selector('h5');
951        $h5['margin-bottom'] = '12px';
952        $h5['font-size']     = '16px';
953        $h5['font-weight']   = 'bold';
954
955        $h6 = new Css\Selector('h6');
956        $h6['margin-bottom'] = '10px';
957        $h6['font-size']     = '14px';
958        $h6['font-weight']   = 'bold';
959
960        $p = new Css\Selector('p');
961        $p['margin-bottom'] = '24px';
962        $p['font-size']     = '12px';
963
964        $a = new Css\Selector('a');
965        $a['color'] = new Css\Color\Rgb(0, 0, 255);
966
967        $strong = new Css\Selector('strong');
968        $strong['font-size']   = '10px';
969        $strong['font-weight'] = 'bold';
970
971        $em = new Css\Selector('em');
972        $em['font-size']  = '10px';
973        $em['font-style'] = 'italic';
974
975        $this->css = new Css\Css();
976        $this->css->addSelectors([$h1, $h2, $h3, $h4, $h5, $h6, $p, $a, $strong, $em]);
977
978        if (!($this->document->hasFont('Arial'))) {
979            $this->document->addFont(new Document\Font('Arial'));
980        }
981        if (!($this->document->hasFont('Arial,Bold'))) {
982            $this->document->addFont(new Document\Font('Arial,Bold'));
983        }
984        if (!($this->document->hasFont('Arial,Italic'))) {
985            $this->document->addFont(new Document\Font('Arial,Italic'));
986        }
987        if (!($this->document->hasFont('Arial,BoldItalic'))) {
988            $this->document->addFont(new Document\Font('Arial,BoldItalic'));
989        }
990        if (!($this->document->hasFont('TimesNewRoman'))) {
991            $this->document->addFont(new Document\Font('TimesNewRoman'));
992        }
993        if (!($this->document->hasFont('TimesNewRoman,Bold'))) {
994            $this->document->addFont(new Document\Font('TimesNewRoman,Bold'));
995        }
996        if (!($this->document->hasFont('TimesNewRoman,Italic'))) {
997            $this->document->addFont(new Document\Font('TimesNewRoman,Italic'));
998        }
999        if (!($this->document->hasFont('TimesNewRoman,BoldItalic'))) {
1000            $this->document->addFont(new Document\Font('TimesNewRoman,BoldItalic'));
1001        }
1002    }
1003
1004    /**
1005     * Prepare node styles
1006     *
1007     * @param  string $name
1008     * @param  array  $attribs
1009     * @param  array  $currentStyles
1010     * @throws Exception
1011     * @return array
1012     */
1013    protected function prepareNodeStyles($name, array $attribs = [], $currentStyles = [])
1014    {
1015        $styles = [
1016            'currentFont'   => null,
1017            'fontFamily'    => $currentStyles['fontFamily'] ?? $this->defaultStyles['font-family'],
1018            'fontSize'      => $currentStyles['fontSize'] ?? $this->defaultStyles['font-size'],
1019            'fontWeight'    => $currentStyles['fontWeight'] ?? $this->defaultStyles['font-weight'],
1020            'float'         => null,
1021            'width'         => null,
1022            'height'        => null,
1023            'color'         => $currentStyles['color'] ?? $this->defaultStyles['color'],
1024            'lineHeight'    => $currentStyles['lineHeight'] ?? $this->defaultStyles['line-height'],
1025            'marginTop'     => 0,
1026            'paddingTop'    => 0,
1027            'marginRight'   => 0,
1028            'paddingRight'  => 0,
1029            'marginBottom'  => 0,
1030            'paddingBottom' => 0,
1031            'marginLeft'    => 0,
1032            'paddingLeft'   => 0
1033        ];
1034
1035        if (in_array($name, ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'])) {
1036            switch ($name) {
1037                case 'h1':
1038                    $styles['fontSize']   = round($styles['fontSize'] * 2.67); // 32
1039                    $styles['fontWeight'] = 'bold';
1040                    break;
1041                case 'h2':
1042                    $styles['fontSize']   = round($styles['fontSize'] * 2.33);  // 28
1043                    $styles['fontWeight'] = 'bold';
1044                    break;
1045                case 'h3':
1046                    $styles['fontSize']   = $styles['fontSize'] * 2;  // 24
1047                    $styles['fontWeight'] = 'bold';
1048                    break;
1049                case 'h4':
1050                    $styles['fontSize']   = round($styles['fontSize'] * 1.67); // 20
1051                    $styles['fontWeight'] = 'bold';
1052                    break;
1053                case 'h5':
1054                    $styles['fontSize']   = round($styles['fontSize'] * 1.33);  // 16
1055                    $styles['fontWeight'] = 'bold';
1056                    break;
1057                case 'h6':
1058                    $styles['fontWeight'] = 'bold';
1059                    break;
1060            }
1061        }
1062
1063        if ($this->css->hasSelector($name)) {
1064            if ($this->css[$name]->hasProperty('font-family')) {
1065                $styles['fontFamily'] = str_replace('"', '', $this->css[$name]['font-family']);
1066            }
1067            if ($this->css[$name]->hasProperty('font-size')) {
1068                //$styles['fontSize'] = (int)$this->css[$name]['font-size'];
1069            }
1070            if ($this->css[$name]->hasProperty('font-weight')) {
1071                $styles['fontWeight'] = $this->css[$name]['font-weight'];
1072            }
1073            if ($this->css[$name]->hasProperty('color')) {
1074                $styles['color'] = $this->css[$name]['color'];
1075                if (is_string($styles['color'])) {
1076                    $cssColor = Css\Color::parse($styles['color']);
1077                    $styles['color'] = $cssColor->toRgb()->toArray(false);
1078                }
1079            }
1080            if ($this->css[$name]->hasProperty('float')) {
1081                $styles['float'] = $this->css[$name]['float'];
1082            }
1083            if ($this->css[$name]->hasProperty('width')) {
1084                $styles['width'] = $this->css[$name]['width'];
1085            }
1086            if ($this->css[$name]->hasProperty('height')) {
1087                $styles['height'] = $this->css[$name]['height'];
1088            }
1089            if ($this->css[$name]->hasProperty('line-height')) {
1090                $styles['lineHeight'] = (int)$this->css[$name]['line-height'];
1091            }
1092            if ((int)$this->css[$name]['margin-top'] > 0) {
1093                $styles['marginTop'] = (int)$this->css[$name]['margin-top'];
1094            }
1095            if ((int)$this->css[$name]['padding-top'] > 0) {
1096                $styles['paddingTop'] = (int)$this->css[$name]['padding-top'];
1097            }
1098            if ((int)$this->css[$name]['margin-right'] > 0) {
1099                $styles['marginRight'] = (int)$this->css[$name]['margin-right'];
1100            }
1101            if ((int)$this->css[$name]['padding-right'] > 0) {
1102                $styles['paddingRight'] = (int)$this->css[$name]['padding-right'];
1103            }
1104            if ((int)$this->css[$name]['margin-bottom'] > 0) {
1105                $styles['marginBottom'] = (int)$this->css[$name]['margin-bottom'];
1106            }
1107            if ((int)$this->css[$name]['padding-bottom'] > 0) {
1108                $styles['paddingBottom'] = (int)$this->css[$name]['padding-bottom'];
1109            }
1110            if ((int)$this->css[$name]['margin-left'] > 0) {
1111                $styles['marginLeft'] = (int)$this->css[$name]['margin-left'];
1112            }
1113            if ((int)$this->css[$name]['padding-left'] > 0) {
1114                $styles['paddingLeft'] = (int)$this->css[$name]['padding-left'];
1115            }
1116        }
1117
1118        if (isset($attribs['id']) && $this->css->hasSelector('#' . $attribs['id'])) {
1119            if ($this->css['#' . $attribs['id']]->hasProperty('font-family')) {
1120                $styles['fontFamily'] = str_replace('"', '', $this->css['#' . $attribs['id']]['font-family']);
1121            }
1122            if ($this->css['#' . $attribs['id']]->hasProperty('font-size')) {
1123                $styles['fontSize'] = (int)$this->css['#' . $attribs['id']]['font-size'];
1124            }
1125            if ($this->css['#' . $attribs['id']]->hasProperty('font-weight')) {
1126                $styles['fontWeight'] = $this->css['#' . $attribs['id']]['font-weight'];
1127            }
1128            if ($this->css['#' . $attribs['id']]->hasProperty('color')) {
1129                $styles['color'] = $this->css['#' . $attribs['id']]['color'];
1130                if (is_string($styles['color'])) {
1131                    $cssColor = Css\Color::parse($styles['color']);
1132                    $styles['color'] = $cssColor->toRgb()->toArray(false);
1133                }
1134            }
1135            if ($this->css['#' . $attribs['id']]->hasProperty('float')) {
1136                $styles['float'] = $this->css['#' . $attribs['id']]['float'];
1137            }
1138            if ($this->css['#' . $attribs['id']]->hasProperty('width')) {
1139                $styles['width'] = $this->css['#' . $attribs['id']]['width'];
1140            }
1141            if ($this->css['#' . $attribs['id']]->hasProperty('height')) {
1142                $styles['height'] = $this->css['#' . $attribs['id']]['height'];
1143            }
1144            if ($this->css['#' . $attribs['id']]->hasProperty('line-height')) {
1145                $styles['lineHeight'] = (int)$this->css['#' . $attribs['id']]['line-height'];
1146            }
1147            if ((int)$this->css['#' . $attribs['id']]['margin-top'] > 0) {
1148                $styles['marginTop'] = (int)$this->css['#' . $attribs['id']]['margin-top'];
1149            }
1150            if ((int)$this->css['#' . $attribs['id']]['padding-top'] > 0) {
1151                $styles['paddingTop'] = (int)$this->css['#' . $attribs['id']]['padding-top'];
1152            }
1153            if ((int)$this->css['#' . $attribs['id']]['margin-right'] > 0) {
1154                $styles['marginRight'] = (int)$this->css['#' . $attribs['id']]['margin-right'];
1155            }
1156            if ((int)$this->css['#' . $attribs['id']]['padding-right'] > 0) {
1157                $styles['paddingRight'] = (int)$this->css['#' . $attribs['id']]['padding-right'];
1158            }
1159            if ((int)$this->css['#' . $attribs['id']]['margin-bottom'] > 0) {
1160                $styles['marginBottom'] = (int)$this->css['#' . $attribs['id']]['margin-bottom'];
1161            }
1162            if ((int)$this->css['#' . $attribs['id']]['padding-bottom'] > 0) {
1163                $styles['paddingBottom'] = (int)$this->css['#' . $attribs['id']]['padding-bottom'];
1164            }
1165            if ((int)$this->css['#' . $attribs['id']]['margin-left'] > 0) {
1166                $styles['marginLeft'] = (int)$this->css['#' . $attribs['id']]['margin-left'];
1167            }
1168            if ((int)$this->css['#' . $attribs['id']]['padding-left'] > 0) {
1169                $styles['paddingLeft'] = (int)$this->css['#' . $attribs['id']]['padding-left'];
1170            }
1171        }
1172
1173        if (isset($attribs['class']) && $this->css->hasSelector('.' . $attribs['class'])) {
1174            if ($this->css['.' . $attribs['class']]->hasProperty('font-family')) {
1175                $styles['fontFamily'] = str_replace('"', '', $this->css['.' . $attribs['class']]['font-family']);
1176            }
1177            if ($this->css['.' . $attribs['class']]->hasProperty('font-size')) {
1178                $styles['fontSize'] = (int)$this->css['.' . $attribs['class']]['font-size'];
1179            }
1180            if ($this->css['.' . $attribs['class']]->hasProperty('font-weight')) {
1181                $styles['fontWeight'] = $this->css['.' . $attribs['class']]['font-weight'];
1182            }
1183            if ($this->css['.' . $attribs['class']]->hasProperty('color')) {
1184                $styles['color'] = $this->css['.' . $attribs['class']]['color'];
1185                if (is_string($styles['color'])) {
1186                    $cssColor = Css\Color::parse($styles['color']);
1187                    $styles['color'] = $cssColor->toRgb()->toArray(false);
1188                }
1189            }
1190            if ($this->css['.' . $attribs['class']]->hasProperty('float')) {
1191                $styles['float'] = $this->css['.' . $attribs['class']]['float'];
1192            }
1193            if ($this->css['.' . $attribs['class']]->hasProperty('width')) {
1194                $styles['width'] = $this->css['.' . $attribs['class']]['width'];
1195            }
1196            if ($this->css['.' . $attribs['class']]->hasProperty('height')) {
1197                $styles['height'] = $this->css['.' . $attribs['class']]['height'];
1198            }
1199            if ($this->css['.' . $attribs['class']]->hasProperty('line-height')) {
1200                $styles['lineHeight'] = (int)$this->css['.' . $attribs['class']]['line-height'];
1201            }
1202            if ((int)$this->css['.' . $attribs['class']]['margin-top'] > 0) {
1203                $styles['marginTop'] = (int)$this->css['.' . $attribs['class']]['margin-top'];
1204            }
1205            if ((int)$this->css['.' . $attribs['class']]['padding-top'] > 0) {
1206                $styles['paddingTop'] = (int)$this->css['.' . $attribs['class']]['padding-top'];
1207            }
1208            if ((int)$this->css['.' . $attribs['class']]['margin-right'] > 0) {
1209                $styles['marginRight'] = (int)$this->css['.' . $attribs['class']]['margin-right'];
1210            }
1211            if ((int)$this->css['.' . $attribs['class']]['padding-right'] > 0) {
1212                $styles['paddingRight'] = (int)$this->css['.' . $attribs['class']]['padding-right'];
1213            }
1214            if ((int)$this->css['.' . $attribs['class']]['margin-bottom'] > 0) {
1215                $styles['marginBottom'] = (int)$this->css['.' . $attribs['class']]['margin-bottom'];
1216            }
1217            if ((int)$this->css['.' . $attribs['class']]['padding-bottom'] > 0) {
1218                $styles['paddingBottom'] = (int)$this->css['.' . $attribs['class']]['padding-bottom'];
1219            }
1220            if ((int)$this->css['.' . $attribs['class']]['margin-left'] > 0) {
1221                $styles['marginLeft'] = (int)$this->css['.' . $attribs['class']]['margin-left'];
1222            }
1223            if ((int)$this->css['.' . $attribs['class']]['padding-left'] > 0) {
1224                $styles['paddingLeft'] = (int)$this->css['.' . $attribs['class']]['padding-left'];
1225            }
1226        }
1227
1228        if (strpos($styles['fontFamily'], ',') !== false) {
1229            $fonts = explode(',', $styles['fontFamily']);
1230            foreach ($fonts as $font) {
1231                $font = trim($font);
1232                if ($this->document->hasFont($font)) {
1233                    $styles['currentFont'] = $font;
1234                    break;
1235                } else if ($this->document->hasFont(str_replace(' ', '-', $font))) {
1236                    $styles['currentFont'] = str_replace(' ', '-', $font);
1237                } else if ($this->document->hasFont(str_replace(' ', ',', $font))) {
1238                    $styles['currentFont'] = str_replace(' ', ',', $font);
1239                } else if ($this->document->hasFont(str_replace(' ', '', $font))) {
1240                    $styles['currentFont'] = str_replace(' ', '', $font);
1241                }
1242            }
1243        } else {
1244            $styles['currentFont'] = $styles['fontFamily'];
1245        }
1246
1247        if (null === $styles['currentFont']) {
1248            throw new Exception('Error: No available font has been detected.');
1249        } else if ($styles['currentFont'] == 'sans-serif') {
1250            $styles['currentFont'] = 'Arial';
1251        } else if ($styles['currentFont'] == 'serif') {
1252            $styles['currentFont'] = 'TimesNewRoman';
1253        }
1254
1255        if ($styles['fontWeight'] == 'bold') {
1256            if ($this->document->hasFont($styles['currentFont'] . 'Bold')) {
1257                $styles['currentFont'] .= 'Bold';
1258            } else if ($this->document->hasFont($styles['currentFont'] . '-Bold')) {
1259                $styles['currentFont'] .= '-Bold';
1260            } else if ($this->document->hasFont($styles['currentFont'] . ',Bold')) {
1261                $styles['currentFont'] .= ',Bold';
1262            }
1263        }
1264
1265        if (!($this->document->hasFont($styles['currentFont']))) {
1266            $standardFonts = Document\Font::standardFonts();
1267            if (in_array($styles['currentFont'], $standardFonts)) {
1268                $this->document->addFont(new Document\Font($styles['currentFont']));
1269            } else if (in_array(str_replace(' ', '-', $styles['currentFont']), $standardFonts)) {
1270                $styles['currentFont'] = str_replace(' ', '-', $styles['currentFont']);
1271                $this->document->addFont(new Document\Font($styles['currentFont']));
1272            } else if (in_array(str_replace(' ', ',', $styles['currentFont']), $standardFonts)) {
1273                $styles['currentFont'] = str_replace(' ', ',', $styles['currentFont']);
1274                $this->document->addFont(new Document\Font($styles['currentFont']));
1275            } else if (in_array(str_replace(' ', '', $styles['currentFont']), $standardFonts)) {
1276                $styles['currentFont'] = str_replace(' ', '', $styles['currentFont']);
1277                $this->document->addFont(new Document\Font($styles['currentFont']));
1278            } else {
1279                throw new Exception('Error: The current font has not been added to the document.');
1280            }
1281
1282            if ($styles['fontWeight'] == 'bold') {
1283                if ($this->document->hasFont($styles['currentFont'] . 'Bold')) {
1284                    $styles['currentFont'] .= 'Bold';
1285                } else if ($this->document->hasFont($styles['currentFont'] . '-Bold')) {
1286                    $styles['currentFont'] .= '-Bold';
1287                } else if ($this->document->hasFont($styles['currentFont'] . ',Bold')) {
1288                    $styles['currentFont'] .= ',Bold';
1289                } else if (in_array($styles['currentFont'] . 'Bold', $standardFonts)) {
1290                    $styles['currentFont'] .= 'Bold';
1291                    $this->document->addFont(new Document\Font($styles['currentFont']));
1292                } else if (in_array($styles['currentFont'] . '-Bold', $standardFonts)) {
1293                    $styles['currentFont'] .= '-Bold';
1294                    $this->document->addFont(new Document\Font($styles['currentFont']));
1295                } else if (in_array($styles['currentFont'] . ',Bold', $standardFonts)) {
1296                    $styles['currentFont'] .= ',Bold';
1297                    $this->document->addFont(new Document\Font($styles['currentFont']));
1298                }
1299            }
1300        }
1301
1302        return $styles;
1303    }
1304
1305    /**
1306     * Get current X-position
1307     *
1308     * @return int
1309     */
1310    protected function getCurrentX()
1311    {
1312        if ($this->x < $this->pageMargins['left']) {
1313            $this->x = $this->pageMargins['left'];
1314        }
1315        return $this->x;
1316    }
1317
1318    /**
1319     * Reset X-position
1320     *
1321     * @return int
1322     */
1323    protected function resetX()
1324    {
1325        $this->x = $this->pageMargins['left'];
1326        return $this->x;
1327    }
1328
1329    /**
1330     * Get current Y-position
1331     *
1332     * @return int
1333     */
1334    protected function getCurrentY()
1335    {
1336        if (!($this->document->hasPages())) {
1337            $this->page = (is_array($this->pageSize)) ?
1338                new Document\Page($this->pageSize['width'], $this->pageSize['height']) : new Document\Page($this->pageSize);
1339            $this->document->addPage($this->page);
1340        } else {
1341            $this->page = $this->document->getPage($this->document->getCurrentPage());
1342        }
1343
1344        $currentY = $this->page->getHeight() - $this->pageMargins['top'] - $this->y;
1345
1346        if ($currentY <= $this->pageMargins['bottom']) {
1347            $this->page = (is_array($this->pageSize)) ?
1348                new Document\Page($this->pageSize['width'], $this->pageSize['height']) : new Document\Page($this->pageSize);
1349            $this->document->addPage($this->page);
1350            $currentY = $this->resetY();
1351        }
1352
1353        return $currentY;
1354    }
1355
1356    /**
1357     * Reset Y-position
1358     *
1359     * @return int
1360     */
1361    protected function resetY()
1362    {
1363        if (!($this->document->hasPages())) {
1364            $this->page = (is_array($this->pageSize)) ?
1365                new Document\Page($this->pageSize['width'], $this->pageSize['height']) : new Document\Page($this->pageSize);
1366            $this->document->addPage($this->page);
1367        } else {
1368            $this->page = $this->document->getPage($this->document->getCurrentPage());
1369        }
1370
1371        $this->y  = 0;
1372
1373        return $this->page->getHeight() - $this->pageMargins['top'];
1374    }
1375
1376    /**
1377     * Create new page
1378     *
1379     * @return int
1380     */
1381    protected function newPage()
1382    {
1383        $this->page = (is_array($this->pageSize)) ?
1384            new Document\Page($this->pageSize['width'], $this->pageSize['height']) : new Document\Page($this->pageSize);
1385        $this->document->addPage($this->page);
1386        $this->y = 0;
1387        return $this->page->getHeight() - $this->pageMargins['top'] - $this->y;
1388    }
1389
1390    /**
1391     * Go to next line
1392     *
1393     * @param  array $styles
1394     * @return void
1395     */
1396    protected function goToNextLine(array $styles)
1397    {
1398        $this->y += $styles['marginBottom'] + $styles['paddingBottom'] + $styles['lineHeight'];
1399    }
1400
1401    /**
1402     * Get string lines
1403     *
1404     * @param  string        $string
1405     * @param  int           $fontSize
1406     * @param  int           $wrapLength
1407     * @param  Document\Font $fontObject
1408     * @return array
1409     */
1410    protected function getStringLines($string, $fontSize, $wrapLength, Document\Font $fontObject)
1411    {
1412        $strings   = [];
1413        $curString = '';
1414        $words     = explode(' ', $string);
1415
1416        foreach ($words as $word) {
1417            $newString = ($curString != '') ? $curString . ' ' . $word : $word;
1418            if ($fontObject->getStringWidth($newString, $fontSize) <= $wrapLength) {
1419                $curString = $newString;
1420            } else {
1421                $strings[] = $curString;
1422                $curString = $word;
1423            }
1424        }
1425        if (!empty($curString)) {
1426            $strings[] = $curString;
1427        }
1428
1429        return $strings;
1430    }
1431
1432}