├── VERSION
├── lib
├── fonts
│ ├── DejaVuSans.ttf
│ ├── DejaVuSerif.ttf
│ ├── DejaVuSans-Bold.ttf
│ ├── DejaVuSansMono.ttf
│ ├── DejaVuSerif-Bold.ttf
│ ├── DejaVuSans-Oblique.ttf
│ ├── DejaVuSansMono-Bold.ttf
│ ├── DejaVuSerif-Italic.ttf
│ ├── DejaVuSans-BoldOblique.ttf
│ ├── DejaVuSansMono-Oblique.ttf
│ ├── DejaVuSerif-BoldItalic.ttf
│ ├── DejaVuSansMono-BoldOblique.ttf
│ ├── mustRead.html
│ └── dompdf_font_family_cache.dist.php
├── res
│ ├── broken_image.png
│ └── html.css
└── html5lib
│ ├── Parser.php
│ └── Data.php
├── src
├── Positioner
│ ├── NullPositioner.php
│ ├── TableCell.php
│ ├── TableRow.php
│ ├── AbstractPositioner.php
│ ├── Block.php
│ ├── Inline.php
│ ├── ListBullet.php
│ ├── Fixed.php
│ └── Absolute.php
├── Frame
│ ├── FrameList.php
│ ├── FrameTreeList.php
│ ├── FrameListIterator.php
│ ├── FrameTreeIterator.php
│ └── FrameTree.php
├── Exception.php
├── Exception
│ └── ImageException.php
├── FrameReflower
│ ├── NullFrameReflower.php
│ ├── ListBullet.php
│ ├── TableRow.php
│ ├── TableRowGroup.php
│ ├── Inline.php
│ ├── TableCell.php
│ ├── Page.php
│ └── Image.php
├── FrameDecorator
│ ├── NullFrameDecorator.php
│ ├── TableRow.php
│ ├── TableRowGroup.php
│ ├── ListBullet.php
│ ├── Image.php
│ ├── Inline.php
│ ├── TableCell.php
│ ├── ListBulletImage.php
│ ├── Text.php
│ └── Block.php
├── Autoloader.php
├── JavascriptEmbedder.php
├── PhpEvaluator.php
├── Renderer
│ ├── TableRowGroup.php
│ ├── Image.php
│ ├── Text.php
│ ├── TableCell.php
│ ├── ListBullet.php
│ ├── Inline.php
│ └── Block.php
├── CanvasFactory.php
├── Image
│ └── Cache.php
├── LineBox.php
└── Renderer.php
├── composer.json
├── CONTRIBUTING.md
├── phpcs.xml
└── README.md
/VERSION:
--------------------------------------------------------------------------------
1 | <7b7591c0b>
--------------------------------------------------------------------------------
/lib/fonts/DejaVuSans.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandu-muthyala/dompdf/master/lib/fonts/DejaVuSans.ttf
--------------------------------------------------------------------------------
/lib/res/broken_image.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandu-muthyala/dompdf/master/lib/res/broken_image.png
--------------------------------------------------------------------------------
/lib/fonts/DejaVuSerif.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandu-muthyala/dompdf/master/lib/fonts/DejaVuSerif.ttf
--------------------------------------------------------------------------------
/lib/fonts/DejaVuSans-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandu-muthyala/dompdf/master/lib/fonts/DejaVuSans-Bold.ttf
--------------------------------------------------------------------------------
/lib/fonts/DejaVuSansMono.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandu-muthyala/dompdf/master/lib/fonts/DejaVuSansMono.ttf
--------------------------------------------------------------------------------
/lib/fonts/DejaVuSerif-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandu-muthyala/dompdf/master/lib/fonts/DejaVuSerif-Bold.ttf
--------------------------------------------------------------------------------
/lib/fonts/DejaVuSans-Oblique.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandu-muthyala/dompdf/master/lib/fonts/DejaVuSans-Oblique.ttf
--------------------------------------------------------------------------------
/lib/fonts/DejaVuSansMono-Bold.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandu-muthyala/dompdf/master/lib/fonts/DejaVuSansMono-Bold.ttf
--------------------------------------------------------------------------------
/lib/fonts/DejaVuSerif-Italic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandu-muthyala/dompdf/master/lib/fonts/DejaVuSerif-Italic.ttf
--------------------------------------------------------------------------------
/lib/fonts/DejaVuSans-BoldOblique.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandu-muthyala/dompdf/master/lib/fonts/DejaVuSans-BoldOblique.ttf
--------------------------------------------------------------------------------
/lib/fonts/DejaVuSansMono-Oblique.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandu-muthyala/dompdf/master/lib/fonts/DejaVuSansMono-Oblique.ttf
--------------------------------------------------------------------------------
/lib/fonts/DejaVuSerif-BoldItalic.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandu-muthyala/dompdf/master/lib/fonts/DejaVuSerif-BoldItalic.ttf
--------------------------------------------------------------------------------
/lib/fonts/DejaVuSansMono-BoldOblique.ttf:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/chandu-muthyala/dompdf/master/lib/fonts/DejaVuSansMono-BoldOblique.ttf
--------------------------------------------------------------------------------
/src/Positioner/NullPositioner.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 | */
8 |
9 | namespace Dompdf\Positioner;
10 |
11 | use Dompdf\FrameDecorator\AbstractFrameDecorator;
12 |
13 | /**
14 | * Dummy positioner
15 | *
16 | * @package dompdf
17 | */
18 | class NullPositioner extends AbstractPositioner
19 | {
20 |
21 | /**
22 | * @param AbstractFrameDecorator $frame
23 | */
24 | function position(AbstractFrameDecorator $frame)
25 | {
26 | return;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/Frame/FrameList.php:
--------------------------------------------------------------------------------
1 | _frame = $frame;
26 | }
27 |
28 | /**
29 | * @return FrameListIterator
30 | */
31 | function getIterator()
32 | {
33 | return new FrameListIterator($this->_frame);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Exception.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 | */
8 |
9 | namespace Dompdf;
10 |
11 | /**
12 | * Standard exception thrown by DOMPDF classes
13 | *
14 | * @package dompdf
15 | */
16 | class Exception extends \Exception
17 | {
18 |
19 | /**
20 | * Class constructor
21 | *
22 | * @param string $message Error message
23 | * @param int $code Error code
24 | */
25 | public function __construct($message = null, $code = 0)
26 | {
27 | parent::__construct($message, $code);
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/src/Frame/FrameTreeList.php:
--------------------------------------------------------------------------------
1 | _root = $root;
26 | }
27 |
28 | /**
29 | * @return FrameTreeIterator
30 | */
31 | public function getIterator()
32 | {
33 | return new FrameTreeIterator($this->_root);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/src/Exception/ImageException.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 | */
8 | namespace Dompdf\Exception;
9 |
10 | use Dompdf\Exception;
11 |
12 | /**
13 | * Image exception thrown by DOMPDF
14 | *
15 | * @package dompdf
16 | */
17 | class ImageException extends Exception
18 | {
19 |
20 | /**
21 | * Class constructor
22 | *
23 | * @param string $message Error message
24 | * @param int $code Error code
25 | */
26 | function __construct($message = null, $code = 0)
27 | {
28 | parent::__construct($message, $code);
29 | }
30 |
31 | }
32 |
--------------------------------------------------------------------------------
/src/Positioner/TableCell.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 | */
8 |
9 | namespace Dompdf\Positioner;
10 |
11 | use Dompdf\FrameDecorator\AbstractFrameDecorator;
12 | use Dompdf\FrameDecorator\Table;
13 |
14 | /**
15 | * Positions table cells
16 | *
17 | * @package dompdf
18 | */
19 | class TableCell extends AbstractPositioner
20 | {
21 |
22 | /**
23 | * @param AbstractFrameDecorator $frame
24 | */
25 | function position(AbstractFrameDecorator $frame)
26 | {
27 | $table = Table::find_parent_table($frame);
28 | $cellmap = $table->get_cellmap();
29 | $frame->set_position($cellmap->get_frame_position($frame));
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/src/FrameReflower/NullFrameReflower.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 | */
8 |
9 | namespace Dompdf\FrameReflower;
10 |
11 | use Dompdf\Frame;
12 | use Dompdf\FrameDecorator\Block as BlockFrameDecorator;
13 |
14 | /**
15 | * Dummy reflower
16 | *
17 | * @package dompdf
18 | */
19 | class NullFrameReflower extends AbstractFrameReflower
20 | {
21 |
22 | /**
23 | * NullFrameReflower constructor.
24 | * @param Frame $frame
25 | */
26 | function __construct(Frame $frame)
27 | {
28 | parent::__construct($frame);
29 | }
30 |
31 | /**
32 | * @param BlockFrameDecorator|null $block
33 | */
34 | function reflow(BlockFrameDecorator $block = null)
35 | {
36 | return;
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/FrameDecorator/NullFrameDecorator.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 | */
8 | namespace Dompdf\FrameDecorator;
9 |
10 | use Dompdf\Dompdf;
11 | use Dompdf\Frame;
12 |
13 | /**
14 | * Dummy decorator
15 | *
16 | * @package dompdf
17 | */
18 | class NullFrameDecorator extends AbstractFrameDecorator
19 | {
20 | /**
21 | * NullFrameDecorator constructor.
22 | * @param Frame $frame
23 | * @param Dompdf $dompdf
24 | */
25 | function __construct(Frame $frame, Dompdf $dompdf)
26 | {
27 | parent::__construct($frame, $dompdf);
28 | $style = $this->_frame->get_style();
29 | $style->width = 0;
30 | $style->height = 0;
31 | $style->margin = 0;
32 | $style->padding = 0;
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/Positioner/TableRow.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 | */
8 |
9 | namespace Dompdf\Positioner;
10 |
11 | use Dompdf\FrameDecorator\AbstractFrameDecorator;
12 |
13 | /**
14 | * Positions table rows
15 | *
16 | * @package dompdf
17 | */
18 | class TableRow extends AbstractPositioner
19 | {
20 |
21 | /**
22 | * @param AbstractFrameDecorator $frame
23 | */
24 | function position(AbstractFrameDecorator $frame)
25 | {
26 | $cb = $frame->get_containing_block();
27 | $p = $frame->get_prev_sibling();
28 |
29 | if ($p) {
30 | $y = $p->get_position("y") + $p->get_margin_height();
31 | } else {
32 | $y = $cb["y"];
33 | }
34 | $frame->set_position($cb["x"], $y);
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/lib/fonts/mustRead.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Core 14 AFM Files - ReadMe
6 |
7 |
8 | or
9 |
10 |
11 | |
12 | This file and the 14 PostScript(R) AFM files it accompanies may be used, copied, and distributed for any purpose and without charge, with or without modification, provided that all copyright notices are retained; that the AFM files are not distributed without this file; that all modifications to this file or any of the AFM files are prominently noted in the modified file(s); and that this paragraph is not modified. Adobe Systems has no responsibility or obligation to support the use of the AFM files. Col |
13 |
14 |
15 | Source http://www.adobe.com/devnet/font/#pcfi
16 |
17 |
--------------------------------------------------------------------------------
/src/Autoloader.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 | */
8 | namespace Dompdf;
9 |
10 | /**
11 | * Embeds Javascript into the PDF document
12 | *
13 | * @package dompdf
14 | */
15 | class JavascriptEmbedder
16 | {
17 |
18 | /**
19 | * @var Dompdf
20 | */
21 | protected $_dompdf;
22 |
23 | /**
24 | * JavascriptEmbedder constructor.
25 | *
26 | * @param Dompdf $dompdf
27 | */
28 | public function __construct(Dompdf $dompdf)
29 | {
30 | $this->_dompdf = $dompdf;
31 | }
32 |
33 | /**
34 | * @param $script
35 | */
36 | public function insert($script)
37 | {
38 | $this->_dompdf->getCanvas()->javascript($script);
39 | }
40 |
41 | /**
42 | * @param Frame $frame
43 | */
44 | public function render(Frame $frame)
45 | {
46 | if (!$this->_dompdf->getOptions()->getIsJavascriptEnabled()) {
47 | return;
48 | }
49 |
50 | $this->insert($frame->get_node()->nodeValue);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "dompdf/dompdf",
3 | "type": "library",
4 | "description": "DOMPDF is a CSS 2.1 compliant HTML to PDF converter",
5 | "homepage": "https://github.com/dompdf/dompdf",
6 | "license": "LGPL-2.1",
7 | "authors": [
8 | {
9 | "name": "Fabien Ménager",
10 | "email": "fabien.menager@gmail.com"
11 | },
12 | {
13 | "name": "Brian Sweeney",
14 | "email": "eclecticgeek@gmail.com"
15 | },
16 | {
17 | "name": "Gabriel Bull",
18 | "email": "me@gabrielbull.com"
19 | }
20 | ],
21 | "autoload": {
22 | "psr-4" : {
23 | "Dompdf\\" : "src/"
24 | },
25 | "classmap" : ["lib/"]
26 | },
27 | "require": {
28 | "php": ">=5.4.0",
29 | "ext-gd": "*",
30 | "ext-dom": "*",
31 | "ext-mbstring": "*",
32 | "phenx/php-font-lib": "0.5.*",
33 | "phenx/php-svg-lib": "0.3.*"
34 | },
35 | "require-dev": {
36 | "phpunit/phpunit": "4.8.*",
37 | "squizlabs/php_codesniffer": "2.*"
38 | },
39 | "extra": {
40 | "branch-alias": {
41 | "dev-develop": "0.7-dev"
42 | }
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/src/FrameReflower/ListBullet.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 | */
8 | namespace Dompdf\FrameReflower;
9 |
10 | use Dompdf\FrameDecorator\Block as BlockFrameDecorator;
11 | use Dompdf\FrameDecorator\AbstractFrameDecorator;
12 |
13 | /**
14 | * Reflows list bullets
15 | *
16 | * @package dompdf
17 | */
18 | class ListBullet extends AbstractFrameReflower
19 | {
20 |
21 | /**
22 | * ListBullet constructor.
23 | * @param AbstractFrameDecorator $frame
24 | */
25 | function __construct(AbstractFrameDecorator $frame)
26 | {
27 | parent::__construct($frame);
28 | }
29 |
30 | /**
31 | * @param BlockFrameDecorator|null $block
32 | */
33 | function reflow(BlockFrameDecorator $block = null)
34 | {
35 | $style = $this->_frame->get_style();
36 |
37 | $style->width = $this->_frame->get_width();
38 | $this->_frame->position();
39 |
40 | if ($style->list_style_position === "inside") {
41 | $p = $this->_frame->find_block_parent();
42 | $p->add_frame_to_line($this->_frame);
43 | }
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/src/Positioner/AbstractPositioner.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 | */
8 |
9 | namespace Dompdf\Positioner;
10 |
11 | use Dompdf\FrameDecorator\AbstractFrameDecorator;
12 |
13 | /**
14 | * Base AbstractPositioner class
15 | *
16 | * Defines postioner interface
17 | *
18 | * @access private
19 | * @package dompdf
20 | */
21 | abstract class AbstractPositioner
22 | {
23 |
24 | /**
25 | * @param AbstractFrameDecorator $frame
26 | * @return mixed
27 | */
28 | abstract function position(AbstractFrameDecorator $frame);
29 |
30 | /**
31 | * @param AbstractFrameDecorator $frame
32 | * @param $offset_x
33 | * @param $offset_y
34 | * @param bool $ignore_self
35 | */
36 | function move(AbstractFrameDecorator $frame, $offset_x, $offset_y, $ignore_self = false)
37 | {
38 | list($x, $y) = $frame->get_position();
39 |
40 | if (!$ignore_self) {
41 | $frame->set_position($x + $offset_x, $y + $offset_y);
42 | }
43 |
44 | foreach ($frame->get_children() as $child) {
45 | $child->move($offset_x, $offset_y);
46 | }
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/lib/html5lib/Parser.php:
--------------------------------------------------------------------------------
1 | parse();
22 | return $tokenizer->save();
23 | }
24 |
25 | /**
26 | * Parses an HTML fragment.
27 | * @param $text | HTML text to parse
28 | * @param $context String name of context element to pretend parsing is in.
29 | * @param $builder | Custom builder implementation
30 | * @return DOMDocument|DOMNodeList Parsed HTML as DOMDocument
31 | */
32 | static public function parseFragment($text, $context = null, $builder = null) {
33 | $tokenizer = new HTML5_Tokenizer($text, $builder);
34 | $tokenizer->parseFragment($context);
35 | return $tokenizer->save();
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/Positioner/Block.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 | */
8 |
9 | namespace Dompdf\Positioner;
10 |
11 | use Dompdf\FrameDecorator\AbstractFrameDecorator;
12 |
13 | /**
14 | * Positions block frames
15 | *
16 | * @access private
17 | * @package dompdf
18 | */
19 | class Block extends AbstractPositioner {
20 |
21 | function position(AbstractFrameDecorator $frame)
22 | {
23 | $style = $frame->get_style();
24 | $cb = $frame->get_containing_block();
25 | $p = $frame->find_block_parent();
26 |
27 | if ($p) {
28 | $float = $style->float;
29 |
30 | if (!$float || $float === "none") {
31 | $p->add_line(true);
32 | }
33 | $y = $p->get_current_line_box()->y;
34 |
35 | } else {
36 | $y = $cb["y"];
37 | }
38 |
39 | $x = $cb["x"];
40 |
41 | // Relative positionning
42 | if ($style->position === "relative") {
43 | $top = (float)$style->length_in_pt($style->top, $cb["h"]);
44 | //$right = (float)$style->length_in_pt($style->right, $cb["w"]);
45 | //$bottom = (float)$style->length_in_pt($style->bottom, $cb["h"]);
46 | $left = (float)$style->length_in_pt($style->left, $cb["w"]);
47 |
48 | $x += $left;
49 | $y += $top;
50 | }
51 |
52 | $frame->set_position($x, $y);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/PhpEvaluator.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 | */
8 | namespace Dompdf;
9 |
10 | /**
11 | * Executes inline PHP code during the rendering process
12 | *
13 | * @package dompdf
14 | */
15 | class PhpEvaluator
16 | {
17 |
18 | /**
19 | * @var Canvas
20 | */
21 | protected $_canvas;
22 |
23 | /**
24 | * PhpEvaluator constructor.
25 | * @param Canvas $canvas
26 | */
27 | public function __construct(Canvas $canvas)
28 | {
29 | $this->_canvas = $canvas;
30 | }
31 |
32 | /**
33 | * @param $code
34 | * @param array $vars
35 | */
36 | public function evaluate($code, $vars = array())
37 | {
38 | if (!$this->_canvas->get_dompdf()->getOptions()->getIsPhpEnabled()) {
39 | return;
40 | }
41 |
42 | // Set up some variables for the inline code
43 | $pdf = $this->_canvas;
44 | $fontMetrics = $pdf->get_dompdf()->getFontMetrics();
45 | $PAGE_NUM = $pdf->get_page_number();
46 | $PAGE_COUNT = $pdf->get_page_count();
47 |
48 | // Override those variables if passed in
49 | foreach ($vars as $k => $v) {
50 | $$k = $v;
51 | }
52 |
53 | eval($code);
54 | }
55 |
56 | /**
57 | * @param Frame $frame
58 | */
59 | public function render(Frame $frame)
60 | {
61 | $this->evaluate($frame->get_node()->nodeValue);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/Renderer/TableRowGroup.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 | */
8 | namespace Dompdf\Renderer;
9 |
10 | use Dompdf\Frame;
11 |
12 | /**
13 | * Renders block frames
14 | *
15 | * @package dompdf
16 | */
17 | class TableRowGroup extends Block
18 | {
19 |
20 | /**
21 | * @param Frame $frame
22 | */
23 | function render(Frame $frame)
24 | {
25 | $style = $frame->get_style();
26 |
27 | $this->_set_opacity($frame->get_opacity($style->opacity));
28 |
29 | $this->_render_border($frame);
30 | $this->_render_outline($frame);
31 |
32 | if ($this->_dompdf->getOptions()->getDebugLayout() && $this->_dompdf->getOptions()->getDebugLayoutBlocks()) {
33 | $this->_debug_layout($frame->get_border_box(), "red");
34 | if ($this->_dompdf->getOptions()->getDebugLayoutPaddingBox()) {
35 | $this->_debug_layout($frame->get_padding_box(), "red", array(0.5, 0.5));
36 | }
37 | }
38 |
39 | if ($this->_dompdf->getOptions()->getDebugLayout() && $this->_dompdf->getOptions()->getDebugLayoutLines() && $frame->get_decorator()) {
40 | foreach ($frame->get_decorator()->get_line_boxes() as $line) {
41 | $frame->_debug_layout(array($line->x, $line->y, $line->w, $line->h), "orange");
42 | }
43 | }
44 |
45 | $id = $frame->get_node()->getAttribute("id");
46 | if (strlen($id) > 0) {
47 | $this->_canvas->add_named_dest($id);
48 | }
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/src/CanvasFactory.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 | */
8 | namespace Dompdf;
9 |
10 | /**
11 | * Create canvas instances
12 | *
13 | * The canvas factory creates canvas instances based on the
14 | * availability of rendering backends and config options.
15 | *
16 | * @package dompdf
17 | */
18 | class CanvasFactory
19 | {
20 | /**
21 | * Constructor is private: this is a static class
22 | */
23 | private function __construct()
24 | {
25 | }
26 |
27 | /**
28 | * @param Dompdf $dompdf
29 | * @param string|array $paper
30 | * @param string $orientation
31 | * @param string $class
32 | *
33 | * @return Canvas
34 | */
35 | static function get_instance(Dompdf $dompdf, $paper = null, $orientation = null, $class = null)
36 | {
37 | $backend = strtolower($dompdf->getOptions()->getPdfBackend());
38 |
39 | if (isset($class) && class_exists($class, false)) {
40 | $class .= "_Adapter";
41 | } else {
42 | if (($backend === "auto" || $backend === "pdflib") &&
43 | class_exists("PDFLib", false)
44 | ) {
45 | $class = "Dompdf\\Adapter\\PDFLib";
46 | }
47 |
48 | else {
49 | if ($backend === "gd") {
50 | $class = "Dompdf\\Adapter\\GD";
51 | } else {
52 | $class = "Dompdf\\Adapter\\CPDF";
53 | }
54 | }
55 | }
56 |
57 | return new $class($paper, $orientation, $dompdf);
58 | }
59 | }
60 |
--------------------------------------------------------------------------------
/src/Frame/FrameListIterator.php:
--------------------------------------------------------------------------------
1 | _parent = $frame;
40 | $this->_cur = $frame->get_first_child();
41 | $this->_num = 0;
42 | }
43 |
44 | /**
45 | *
46 | */
47 | public function rewind()
48 | {
49 | $this->_cur = $this->_parent->get_first_child();
50 | $this->_num = 0;
51 | }
52 |
53 | /**
54 | * @return bool
55 | */
56 | public function valid()
57 | {
58 | return isset($this->_cur); // && ($this->_cur->get_prev_sibling() === $this->_prev);
59 | }
60 |
61 | /**
62 | * @return int
63 | */
64 | public function key()
65 | {
66 | return $this->_num;
67 | }
68 |
69 | /**
70 | * @return Frame
71 | */
72 | public function current()
73 | {
74 | return $this->_cur;
75 | }
76 |
77 | /**
78 | * @return Frame
79 | */
80 | public function next()
81 | {
82 | $ret = $this->_cur;
83 | if (!$ret) {
84 | return null;
85 | }
86 |
87 | $this->_cur = $this->_cur->get_next_sibling();
88 | $this->_num++;
89 | return $ret;
90 | }
91 | }
--------------------------------------------------------------------------------
/src/FrameDecorator/TableRow.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 | */
8 | namespace Dompdf\FrameDecorator;
9 |
10 | use Dompdf\Dompdf;
11 | use Dompdf\Frame;
12 | use Dompdf\FrameDecorator\Table as TableFrameDecorator;
13 |
14 | /**
15 | * Decorates Frames for table row layout
16 | *
17 | * @package dompdf
18 | */
19 | class TableRow extends AbstractFrameDecorator
20 | {
21 | /**
22 | * TableRow constructor.
23 | * @param Frame $frame
24 | * @param Dompdf $dompdf
25 | */
26 | function __construct(Frame $frame, Dompdf $dompdf)
27 | {
28 | parent::__construct($frame, $dompdf);
29 | }
30 |
31 | //........................................................................
32 |
33 | /**
34 | * Remove all non table-cell frames from this row and move them after
35 | * the table.
36 | */
37 | function normalise()
38 | {
39 | // Find our table parent
40 | $p = TableFrameDecorator::find_parent_table($this);
41 |
42 | $erroneous_frames = array();
43 | foreach ($this->get_children() as $child) {
44 | $display = $child->get_style()->display;
45 |
46 | if ($display !== "table-cell") {
47 | $erroneous_frames[] = $child;
48 | }
49 | }
50 |
51 | // dump the extra nodes after the table.
52 | foreach ($erroneous_frames as $frame) {
53 | $p->move_after($frame);
54 | }
55 | }
56 |
57 | function split(Frame $child = null, $force_pagebreak = false)
58 | {
59 | $this->_already_pushed = true;
60 |
61 | if (is_null($child)) {
62 | parent::split();
63 | return;
64 | }
65 |
66 | parent::split($child, $force_pagebreak);
67 | }
68 | }
69 |
--------------------------------------------------------------------------------
/src/Frame/FrameTreeIterator.php:
--------------------------------------------------------------------------------
1 | _stack[] = $this->_root = $root;
38 | $this->_num = 0;
39 | }
40 |
41 | /**
42 | *
43 | */
44 | public function rewind()
45 | {
46 | $this->_stack = array($this->_root);
47 | $this->_num = 0;
48 | }
49 |
50 | /**
51 | * @return bool
52 | */
53 | public function valid()
54 | {
55 | return count($this->_stack) > 0;
56 | }
57 |
58 | /**
59 | * @return int
60 | */
61 | public function key()
62 | {
63 | return $this->_num;
64 | }
65 |
66 | /**
67 | * @return Frame
68 | */
69 | public function current()
70 | {
71 | return end($this->_stack);
72 | }
73 |
74 | /**
75 | * @return Frame
76 | */
77 | public function next()
78 | {
79 | $b = end($this->_stack);
80 |
81 | // Pop last element
82 | unset($this->_stack[key($this->_stack)]);
83 | $this->_num++;
84 |
85 | // Push all children onto the stack in reverse order
86 | if ($c = $b->get_last_child()) {
87 | $this->_stack[] = $c;
88 | while ($c = $c->get_prev_sibling()) {
89 | $this->_stack[] = $c;
90 | }
91 | }
92 |
93 | return $b;
94 | }
95 | }
96 |
97 |
--------------------------------------------------------------------------------
/src/FrameDecorator/TableRowGroup.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 | */
8 | namespace Dompdf\FrameDecorator;
9 |
10 | use Dompdf\Dompdf;
11 | use Dompdf\Frame;
12 |
13 | /**
14 | * Table row group decorator
15 | *
16 | * Overrides split() method for tbody, thead & tfoot elements
17 | *
18 | * @package dompdf
19 | */
20 | class TableRowGroup extends AbstractFrameDecorator
21 | {
22 |
23 | /**
24 | * Class constructor
25 | *
26 | * @param Frame $frame Frame to decorate
27 | * @param Dompdf $dompdf Current dompdf instance
28 | */
29 | function __construct(Frame $frame, Dompdf $dompdf)
30 | {
31 | parent::__construct($frame, $dompdf);
32 | }
33 |
34 | /**
35 | * Override split() to remove all child rows and this element from the cellmap
36 | *
37 | * @param Frame $child
38 | * @param bool $force_pagebreak
39 | *
40 | * @return void
41 | */
42 | function split(Frame $child = null, $force_pagebreak = false)
43 | {
44 | if (is_null($child)) {
45 | parent::split();
46 | return;
47 | }
48 |
49 | // Remove child & all subsequent rows from the cellmap
50 | $cellmap = $this->get_parent()->get_cellmap();
51 | $iter = $child;
52 |
53 | while ($iter) {
54 | $cellmap->remove_row($iter);
55 | $iter = $iter->get_next_sibling();
56 | }
57 |
58 | // If we are splitting at the first child remove the
59 | // table-row-group from the cellmap as well
60 | if ($child === $this->get_first_child()) {
61 | $cellmap->remove_row_group($this);
62 | parent::split();
63 | return;
64 | }
65 |
66 | $cellmap->update_row_group($this, $child->get_prev_sibling());
67 | parent::split($child);
68 | }
69 | }
70 |
71 |
--------------------------------------------------------------------------------
/src/FrameReflower/TableRow.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 | */
8 | namespace Dompdf\FrameReflower;
9 |
10 | use Dompdf\FrameDecorator\Block as BlockFrameDecorator;
11 | use Dompdf\FrameDecorator\Table as TableFrameDecorator;
12 | use Dompdf\FrameDecorator\TableRow as TableRowFrameDecorator;
13 | use Dompdf\Exception;
14 |
15 | /**
16 | * Reflows table rows
17 | *
18 | * @package dompdf
19 | */
20 | class TableRow extends AbstractFrameReflower
21 | {
22 | /**
23 | * TableRow constructor.
24 | * @param TableRowFrameDecorator $frame
25 | */
26 | function __construct(TableRowFrameDecorator $frame)
27 | {
28 | parent::__construct($frame);
29 | }
30 |
31 | /**
32 | * @param BlockFrameDecorator|null $block
33 | */
34 | function reflow(BlockFrameDecorator $block = null)
35 | {
36 | $page = $this->_frame->get_root();
37 |
38 | if ($page->is_full()) {
39 | return;
40 | }
41 |
42 | $this->_frame->position();
43 | $style = $this->_frame->get_style();
44 | $cb = $this->_frame->get_containing_block();
45 |
46 | foreach ($this->_frame->get_children() as $child) {
47 | if ($page->is_full()) {
48 | return;
49 | }
50 |
51 | $child->set_containing_block($cb);
52 | $child->reflow();
53 | }
54 |
55 | if ($page->is_full()) {
56 | return;
57 | }
58 |
59 | $table = TableFrameDecorator::find_parent_table($this->_frame);
60 | $cellmap = $table->get_cellmap();
61 | $style->width = $cellmap->get_frame_width($this->_frame);
62 | $style->height = $cellmap->get_frame_height($this->_frame);
63 |
64 | $this->_frame->set_position($cellmap->get_frame_position($this->_frame));
65 | }
66 |
67 | /**
68 | * @throws Exception
69 | */
70 | function get_min_max_width()
71 | {
72 | throw new Exception("Min/max width is undefined for table rows");
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/src/FrameReflower/TableRowGroup.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 | */
8 | namespace Dompdf\FrameReflower;
9 |
10 | use Dompdf\FrameDecorator\Block as BlockFrameDecorator;
11 | use Dompdf\FrameDecorator\Table as TableFrameDecorator;
12 |
13 | /**
14 | * Reflows table row groups (e.g. tbody tags)
15 | *
16 | * @package dompdf
17 | */
18 | class TableRowGroup extends AbstractFrameReflower
19 | {
20 |
21 | /**
22 | * TableRowGroup constructor.
23 | * @param \Dompdf\Frame $frame
24 | */
25 | function __construct($frame)
26 | {
27 | parent::__construct($frame);
28 | }
29 |
30 | /**
31 | * @param BlockFrameDecorator|null $block
32 | */
33 | function reflow(BlockFrameDecorator $block = null)
34 | {
35 | $page = $this->_frame->get_root();
36 |
37 | $style = $this->_frame->get_style();
38 |
39 | // Our width is equal to the width of our parent table
40 | $table = TableFrameDecorator::find_parent_table($this->_frame);
41 |
42 | $cb = $this->_frame->get_containing_block();
43 |
44 | foreach ($this->_frame->get_children() as $child) {
45 | // Bail if the page is full
46 | if ($page->is_full()) {
47 | return;
48 | }
49 |
50 | $child->set_containing_block($cb["x"], $cb["y"], $cb["w"], $cb["h"]);
51 | $child->reflow();
52 |
53 | // Check if a split has occured
54 | $page->check_page_break($child);
55 | }
56 |
57 | if ($page->is_full()) {
58 | return;
59 | }
60 |
61 | $cellmap = $table->get_cellmap();
62 | $style->width = $cellmap->get_frame_width($this->_frame);
63 | $style->height = $cellmap->get_frame_height($this->_frame);
64 |
65 | $this->_frame->set_position($cellmap->get_frame_position($this->_frame));
66 |
67 | if ($table->get_style()->border_collapse === "collapse") {
68 | // Unset our borders because our cells are now using them
69 | $style->border_style = "none";
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/src/Positioner/Inline.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 | */
8 |
9 | namespace Dompdf\Positioner;
10 |
11 | use Dompdf\FrameDecorator\AbstractFrameDecorator;
12 | use Dompdf\FrameDecorator\Inline as InlineFrameDecorator;
13 | use Dompdf\Exception;
14 |
15 | /**
16 | * Positions inline frames
17 | *
18 | * @package dompdf
19 | */
20 | class Inline extends AbstractPositioner
21 | {
22 |
23 | /**
24 | * @param AbstractFrameDecorator $frame
25 | * @throws Exception
26 | */
27 | function position(AbstractFrameDecorator $frame)
28 | {
29 | /**
30 | * Find our nearest block level parent and access its lines property.
31 | * @var BlockFrameDecorator
32 | */
33 | $p = $frame->find_block_parent();
34 |
35 | // Debugging code:
36 |
37 | // Helpers::pre_r("\nPositioning:");
38 | // Helpers::pre_r("Me: " . $frame->get_node()->nodeName . " (" . spl_object_hash($frame->get_node()) . ")");
39 | // Helpers::pre_r("Parent: " . $p->get_node()->nodeName . " (" . spl_object_hash($p->get_node()) . ")");
40 |
41 | // End debugging
42 |
43 | if (!$p) {
44 | throw new Exception("No block-level parent found. Not good.");
45 | }
46 |
47 | $f = $frame;
48 |
49 | $cb = $f->get_containing_block();
50 | $line = $p->get_current_line_box();
51 |
52 | // Skip the page break if in a fixed position element
53 | $is_fixed = false;
54 | while ($f = $f->get_parent()) {
55 | if ($f->get_style()->position === "fixed") {
56 | $is_fixed = true;
57 | break;
58 | }
59 | }
60 |
61 | $f = $frame;
62 |
63 | if (!$is_fixed && $f->get_parent() &&
64 | $f->get_parent() instanceof InlineFrameDecorator &&
65 | $f->is_text_node()
66 | ) {
67 | $min_max = $f->get_reflower()->get_min_max_width();
68 |
69 | // If the frame doesn't fit in the current line, a line break occurs
70 | if ($min_max["min"] > ($cb["w"] - $line->left - $line->w - $line->right)) {
71 | $p->add_line();
72 | }
73 | }
74 |
75 | $f->set_position($cb["x"] + $line->w, $line->y);
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/src/FrameDecorator/ListBullet.php:
--------------------------------------------------------------------------------
1 |
6 | * @author Helmut Tischer
7 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
8 | */
9 | namespace Dompdf\FrameDecorator;
10 |
11 | use Dompdf\Dompdf;
12 | use Dompdf\Frame;
13 |
14 | /**
15 | * Decorates frames for list bullet rendering
16 | *
17 | * @package dompdf
18 | */
19 | class ListBullet extends AbstractFrameDecorator
20 | {
21 |
22 | const BULLET_PADDING = 1; // Distance from bullet to text in pt
23 | // As fraction of font size (including descent). See also DECO_THICKNESS.
24 | const BULLET_THICKNESS = 0.04; // Thickness of bullet outline. Screen: 0.08, print: better less, e.g. 0.04
25 | const BULLET_DESCENT = 0.3; //descent of font below baseline. Todo: Guessed for now.
26 | const BULLET_SIZE = 0.35; // bullet diameter. For now 0.5 of font_size without descent.
27 |
28 | static $BULLET_TYPES = array("disc", "circle", "square");
29 |
30 | /**
31 | * ListBullet constructor.
32 | * @param Frame $frame
33 | * @param Dompdf $dompdf
34 | */
35 | function __construct(Frame $frame, Dompdf $dompdf)
36 | {
37 | parent::__construct($frame, $dompdf);
38 | }
39 |
40 | /**
41 | * @return float|int
42 | */
43 | function get_margin_width()
44 | {
45 | $style = $this->_frame->get_style();
46 |
47 | if ($style->list_style_type === "none") {
48 | return 0;
49 | }
50 |
51 | return $style->get_font_size() * self::BULLET_SIZE + 2 * self::BULLET_PADDING;
52 | }
53 |
54 | /**
55 | * hits only on "inset" lists items, to increase height of box
56 | *
57 | * @return float|int
58 | */
59 | function get_margin_height()
60 | {
61 | $style = $this->_frame->get_style();
62 |
63 | if ($style->list_style_type === "none") {
64 | return 0;
65 | }
66 |
67 | return $style->get_font_size() * self::BULLET_SIZE + 2 * self::BULLET_PADDING;
68 | }
69 |
70 | /**
71 | * @return float|int
72 | */
73 | function get_width()
74 | {
75 | return $this->get_margin_width();
76 | }
77 |
78 | /**
79 | * @return float|int
80 | */
81 | function get_height()
82 | {
83 | return $this->get_margin_height();
84 | }
85 |
86 | //........................................................................
87 | }
88 |
--------------------------------------------------------------------------------
/src/FrameDecorator/Image.php:
--------------------------------------------------------------------------------
1 |
6 | * @author Fabien Ménager
7 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
8 | */
9 | namespace Dompdf\FrameDecorator;
10 |
11 | use Dompdf\Dompdf;
12 | use Dompdf\Frame;
13 | use Dompdf\Image\Cache;
14 |
15 | /**
16 | * Decorates frames for image layout and rendering
17 | *
18 | * @package dompdf
19 | */
20 | class Image extends AbstractFrameDecorator
21 | {
22 |
23 | /**
24 | * The path to the image file (note that remote images are
25 | * downloaded locally to Options:tempDir).
26 | *
27 | * @var string
28 | */
29 | protected $_image_url;
30 |
31 | /**
32 | * The image's file error message
33 | *
34 | * @var string
35 | */
36 | protected $_image_msg;
37 |
38 | /**
39 | * Class constructor
40 | *
41 | * @param Frame $frame the frame to decorate
42 | * @param DOMPDF $dompdf the document's dompdf object (required to resolve relative & remote urls)
43 | */
44 | function __construct(Frame $frame, Dompdf $dompdf)
45 | {
46 | parent::__construct($frame, $dompdf);
47 | $url = $frame->get_node()->getAttribute("src");
48 |
49 | $debug_png = $dompdf->getOptions()->getDebugPng();
50 | if ($debug_png) {
51 | print '[__construct ' . $url . ']';
52 | }
53 |
54 | list($this->_image_url, /*$type*/, $this->_image_msg) = Cache::resolve_url(
55 | $url,
56 | $dompdf->getProtocol(),
57 | $dompdf->getBaseHost(),
58 | $dompdf->getBasePath(),
59 | $dompdf
60 | );
61 |
62 | if (Cache::is_broken($this->_image_url) &&
63 | $alt = $frame->get_node()->getAttribute("alt")
64 | ) {
65 | $style = $frame->get_style();
66 | $style->width = (4 / 3) * $dompdf->getFontMetrics()->getTextWidth($alt, $style->font_family, $style->font_size, $style->word_spacing);
67 | $style->height = $dompdf->getFontMetrics()->getFontHeight($style->font_family, $style->font_size);
68 | }
69 | }
70 |
71 | /**
72 | * Return the image's url
73 | *
74 | * @return string The url of this image
75 | */
76 | function get_image_url()
77 | {
78 | return $this->_image_url;
79 | }
80 |
81 | /**
82 | * Return the image's error message
83 | *
84 | * @return string The image's error message
85 | */
86 | function get_image_msg()
87 | {
88 | return $this->_image_msg;
89 | }
90 |
91 | }
92 |
--------------------------------------------------------------------------------
/src/Positioner/ListBullet.php:
--------------------------------------------------------------------------------
1 |
6 | * @author Helmut Tischer
7 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
8 | */
9 |
10 | namespace Dompdf\Positioner;
11 |
12 | use Dompdf\FrameDecorator\AbstractFrameDecorator;
13 |
14 | /**
15 | * Positions list bullets
16 | *
17 | * @package dompdf
18 | */
19 | class ListBullet extends AbstractPositioner
20 | {
21 |
22 | /**
23 | * @param AbstractFrameDecorator $frame
24 | */
25 | function position(AbstractFrameDecorator $frame)
26 | {
27 |
28 | // Bullets & friends are positioned an absolute distance to the left of
29 | // the content edge of their parent element
30 | $cb = $frame->get_containing_block();
31 |
32 | // Note: this differs from most frames in that we must position
33 | // ourselves after determining our width
34 | $x = $cb["x"] - $frame->get_width();
35 |
36 | $p = $frame->find_block_parent();
37 |
38 | $y = $p->get_current_line_box()->y;
39 |
40 | // This is a bit of a hack...
41 | $n = $frame->get_next_sibling();
42 | if ($n) {
43 | $style = $n->get_style();
44 | $line_height = $style->length_in_pt($style->line_height, $style->get_font_size());
45 | $offset = (float)$style->length_in_pt($line_height, $n->get_containing_block("h")) - $frame->get_height();
46 | $y += $offset / 2;
47 | }
48 |
49 | // Now the position is the left top of the block which should be marked with the bullet.
50 | // We tried to find out the y of the start of the first text character within the block.
51 | // But the top margin/padding does not fit, neither from this nor from the next sibling
52 | // The "bit of a hack" above does not work also.
53 |
54 | // Instead let's position the bullet vertically centered to the block which should be marked.
55 | // But for get_next_sibling() the get_containing_block is all zero, and for find_block_parent()
56 | // the get_containing_block is paper width and the entire list as height.
57 |
58 | // if ($p) {
59 | // //$cb = $n->get_containing_block();
60 | // $cb = $p->get_containing_block();
61 | // $y += $cb["h"]/2;
62 | // print 'cb:'.$cb["x"].':'.$cb["y"].':'.$cb["w"].':'.$cb["h"].':';
63 | // }
64 |
65 | // Todo:
66 | // For now give up on the above. Use Guesswork with font y-pos in the middle of the line spacing
67 |
68 | /*$style = $p->get_style();
69 | $font_size = $style->get_font_size();
70 | $line_height = (float)$style->length_in_pt($style->line_height, $font_size);
71 | $y += ($line_height - $font_size) / 2; */
72 |
73 | //Position is x-end y-top of character position of the bullet.
74 | $frame->set_position($x, $y);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 | # How to contribute
2 |
3 | - [Getting help](#getting-help)
4 | - [Submitting bug reports](#submitting-bug-reports)
5 | - [Contributing code](#contributing-code)
6 |
7 | ## Getting help
8 |
9 | Community discussion, questions, and informal bug reporting is done on the
10 | [dompdf Google group](http://groups.google.com/group/dompdf). You may also
11 | seek help on
12 | [StackOverflow](http://stackoverflow.com/questions/tagged/dompdf).
13 |
14 | ## Submitting bug reports
15 |
16 | The preferred way to report bugs is to use the
17 | [GitHub issue tracker](http://github.com/dompdf/dompdf/issues). Before
18 | reporting a bug, read these pointers.
19 |
20 | **Please search inside the bug tracker to see if the bug you found is not already reported.**
21 |
22 | **Note:** The issue tracker is for *bugs* and *feature requests*, not requests for help.
23 | Questions should be asked on the
24 | [dompdf Google group](http://groups.google.com/group/dompdf) instead.
25 |
26 | ### Reporting bugs effectively
27 |
28 | - dompdf is maintained by volunteers. They don't owe you anything, so be
29 | polite. Reports with an indignant or belligerent tone tend to be moved to the
30 | bottom of the pile.
31 |
32 | - Include information about **the PHP version on which the problem occurred**. Even
33 | if you tested several PHP version on different servers, and the problem occurred
34 | in all of them, mention this fact in the bug report.
35 | Also include the operating system it's installed on. PHP configuration can also help,
36 | and server error logs (like Apache logs)
37 |
38 | - Mention which release of dompdf you're using (the zip, the master branch, etc).
39 | Preferably, try also with the current development snapshot, to ensure the
40 | problem has not already been fixed.
41 |
42 | - Mention very precisely what went wrong. "X is broken" is not a good bug
43 | report. What did you expect to happen? What happened instead? Describe the
44 | exact steps a maintainer has to take to make the problem occur. We can not
45 | fix something that we can not observe.
46 |
47 | - If the problem can not be reproduced in any of the demos included in the
48 | dompdf distribution, please provide an HTML document that demonstrates
49 | the problem. There are a few options to show us your code:
50 | - [JS Fiddle](http://jsfiddle.net/)
51 | - [dompdf debug helper](http://eclecticgeek.com/dompdf/debug.php) (provided by @bsweeney)
52 | - Include the HTML/CSS inside the bug report, with
53 | [code highlighting](https://github.com/adam-p/markdown-here/wiki/Markdown-Cheatsheet#wiki-code).
54 |
55 | ## Contributing code
56 |
57 | - Make sure you have a [GitHub Account](https://github.com/signup/free)
58 | - Fork [dompdf](https://github.com/dompdf/dompdf/)
59 | ([how to fork a repo](https://help.github.com/articles/fork-a-repo))
60 | - *Make your changes on the `develop` branch* or the most appropriate feature branch. Please only patch
61 | the master branch if you are attempting to address an urgent bug in the released code.
62 | - Add a simple test file in `www/test/`, with a comprehensive name.
63 | - Add a unit test in the ``test/Dompdf/Tests/`` directory.
64 | - Submit a pull request
65 | ([how to create a pull request](https://help.github.com/articles/fork-a-repo))
66 |
--------------------------------------------------------------------------------
/src/FrameReflower/Inline.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 | */
8 | namespace Dompdf\FrameReflower;
9 |
10 | use Dompdf\Frame;
11 | use Dompdf\FrameDecorator\Block as BlockFrameDecorator;
12 | use Dompdf\FrameDecorator\Text as TextFrameDecorator;
13 |
14 | /**
15 | * Reflows inline frames
16 | *
17 | * @package dompdf
18 | */
19 | class Inline extends AbstractFrameReflower
20 | {
21 |
22 | /**
23 | * Inline constructor.
24 | * @param Frame $frame
25 | */
26 | function __construct(Frame $frame)
27 | {
28 | parent::__construct($frame);
29 | }
30 |
31 | /**
32 | * @param BlockFrameDecorator|null $block
33 | */
34 | function reflow(BlockFrameDecorator $block = null)
35 | {
36 | $frame = $this->_frame;
37 |
38 | // Check if a page break is forced
39 | $page = $frame->get_root();
40 | $page->check_forced_page_break($frame);
41 |
42 | if ($page->is_full()) {
43 | return;
44 | }
45 |
46 | $style = $frame->get_style();
47 |
48 | // Generated content
49 | $this->_set_content();
50 |
51 | $frame->position();
52 |
53 | $cb = $frame->get_containing_block();
54 |
55 | // Add our margin, padding & border to the first and last children
56 | if (($f = $frame->get_first_child()) && $f instanceof TextFrameDecorator) {
57 | $f_style = $f->get_style();
58 | $f_style->margin_left = $style->margin_left;
59 | $f_style->padding_left = $style->padding_left;
60 | $f_style->border_left = $style->border_left;
61 | }
62 |
63 | if (($l = $frame->get_last_child()) && $l instanceof TextFrameDecorator) {
64 | $l_style = $l->get_style();
65 | $l_style->margin_right = $style->margin_right;
66 | $l_style->padding_right = $style->padding_right;
67 | $l_style->border_right = $style->border_right;
68 | }
69 |
70 | if ($block) {
71 | $block->add_frame_to_line($this->_frame);
72 | }
73 |
74 | // Set the containing blocks and reflow each child. The containing
75 | // block is not changed by line boxes.
76 | foreach ($frame->get_children() as $child) {
77 | $child->set_containing_block($cb);
78 | $child->reflow($block);
79 | }
80 | }
81 |
82 | /**
83 | * Determine current frame width based on contents
84 | *
85 | * @return float
86 | */
87 | public function calculate_auto_width()
88 | {
89 | $width = 0;
90 |
91 | foreach ($this->_frame->get_children() as $child) {
92 | if ($child->get_original_style()->width == 'auto') {
93 | $width += $child->calculate_auto_width();
94 | } else {
95 | $width += $child->get_margin_width();
96 | }
97 | }
98 |
99 | $this->_frame->get_style()->width = $width;
100 |
101 | return $this->_frame->get_margin_width();
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/src/Positioner/Fixed.php:
--------------------------------------------------------------------------------
1 |
6 | * @author Fabien Ménager
7 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
8 | */
9 |
10 | namespace Dompdf\Positioner;
11 |
12 | use Dompdf\FrameDecorator\AbstractFrameDecorator;
13 |
14 | /**
15 | * Positions fixely positioned frames
16 | */
17 | class Fixed extends AbstractPositioner
18 | {
19 |
20 | /**
21 | * @param AbstractFrameDecorator $frame
22 | */
23 | function position(AbstractFrameDecorator $frame)
24 | {
25 | $style = $frame->get_original_style();
26 | $root = $frame->get_root();
27 | $initialcb = $root->get_containing_block();
28 | $initialcb_style = $root->get_style();
29 |
30 | $p = $frame->find_block_parent();
31 | if ($p) {
32 | $p->add_line();
33 | }
34 |
35 | // Compute the margins of the @page style
36 | $margin_top = (float)$initialcb_style->length_in_pt($initialcb_style->margin_top, $initialcb["h"]);
37 | $margin_right = (float)$initialcb_style->length_in_pt($initialcb_style->margin_right, $initialcb["w"]);
38 | $margin_bottom = (float)$initialcb_style->length_in_pt($initialcb_style->margin_bottom, $initialcb["h"]);
39 | $margin_left = (float)$initialcb_style->length_in_pt($initialcb_style->margin_left, $initialcb["w"]);
40 |
41 | // The needed computed style of the element
42 | $height = (float)$style->length_in_pt($style->height, $initialcb["h"]);
43 | $width = (float)$style->length_in_pt($style->width, $initialcb["w"]);
44 |
45 | $top = $style->length_in_pt($style->top, $initialcb["h"]);
46 | $right = $style->length_in_pt($style->right, $initialcb["w"]);
47 | $bottom = $style->length_in_pt($style->bottom, $initialcb["h"]);
48 | $left = $style->length_in_pt($style->left, $initialcb["w"]);
49 |
50 | $y = $margin_top;
51 | if (isset($top)) {
52 | $y = (float)$top + $margin_top;
53 | if ($top === "auto") {
54 | $y = $margin_top;
55 | if (isset($bottom) && $bottom !== "auto") {
56 | $y = $initialcb["h"] - $bottom - $margin_bottom;
57 | if ($frame->is_auto_height()) {
58 | $y -= $height;
59 | } else {
60 | $y -= $frame->get_margin_height();
61 | }
62 | }
63 | }
64 | }
65 |
66 | $x = $margin_left;
67 | if (isset($left)) {
68 | $x = (float)$left + $margin_left;
69 | if ($left === "auto") {
70 | $x = $margin_left;
71 | if (isset($right) && $right !== "auto") {
72 | $x = $initialcb["w"] - $right - $margin_right;
73 | if ($frame->is_auto_width()) {
74 | $x -= $width;
75 | } else {
76 | $x -= $frame->get_margin_width();
77 | }
78 | }
79 | }
80 | }
81 |
82 | $frame->set_position($x, $y);
83 |
84 | $children = $frame->get_children();
85 | foreach ($children as $child) {
86 | $child->set_position($x, $y);
87 | }
88 | }
89 | }
--------------------------------------------------------------------------------
/src/FrameDecorator/Inline.php:
--------------------------------------------------------------------------------
1 |
6 | * @author Helmut Tischer
7 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
8 | */
9 | namespace Dompdf\FrameDecorator;
10 |
11 | use DOMElement;
12 | use Dompdf\Dompdf;
13 | use Dompdf\Frame;
14 | use Dompdf\Exception;
15 |
16 | /**
17 | * Decorates frames for inline layout
18 | *
19 | * @access private
20 | * @package dompdf
21 | */
22 | class Inline extends AbstractFrameDecorator
23 | {
24 |
25 | /**
26 | * Inline constructor.
27 | * @param Frame $frame
28 | * @param Dompdf $dompdf
29 | */
30 | function __construct(Frame $frame, Dompdf $dompdf)
31 | {
32 | parent::__construct($frame, $dompdf);
33 | }
34 |
35 | /**
36 | * @param Frame|null $frame
37 | * @param bool $force_pagebreak
38 | * @throws Exception
39 | */
40 | function split(Frame $frame = null, $force_pagebreak = false)
41 | {
42 | if (is_null($frame)) {
43 | $this->get_parent()->split($this, $force_pagebreak);
44 | return;
45 | }
46 |
47 | if ($frame->get_parent() !== $this) {
48 | throw new Exception("Unable to split: frame is not a child of this one.");
49 | }
50 |
51 | $node = $this->_frame->get_node();
52 |
53 | if ($node instanceof DOMElement && $node->hasAttribute("id")) {
54 | $node->setAttribute("data-dompdf-original-id", $node->getAttribute("id"));
55 | $node->removeAttribute("id");
56 | }
57 |
58 | $split = $this->copy($node->cloneNode());
59 | // if this is a generated node don't propagate the content style
60 | if ($split->get_node()->nodeName == "dompdf_generated") {
61 | $split->get_style()->content = "normal";
62 | }
63 | $this->get_parent()->insert_child_after($split, $this);
64 |
65 | // Unset the current node's right style properties
66 | $style = $this->_frame->get_style();
67 | $style->margin_right = 0;
68 | $style->padding_right = 0;
69 | $style->border_right_width = 0;
70 |
71 | // Unset the split node's left style properties since we don't want them
72 | // to propagate
73 | $style = $split->get_style();
74 | $style->margin_left = 0;
75 | $style->padding_left = 0;
76 | $style->border_left_width = 0;
77 |
78 | //On continuation of inline element on next line,
79 | //don't repeat non-vertically repeatble background images
80 | //See e.g. in testcase image_variants, long desriptions
81 | if (($url = $style->background_image) && $url !== "none"
82 | && ($repeat = $style->background_repeat) && $repeat !== "repeat" && $repeat !== "repeat-y"
83 | ) {
84 | $style->background_image = "none";
85 | }
86 |
87 | // Add $frame and all following siblings to the new split node
88 | $iter = $frame;
89 | while ($iter) {
90 | $frame = $iter;
91 | $iter = $iter->get_next_sibling();
92 | $frame->reset();
93 | $split->append_child($frame);
94 | }
95 |
96 | $page_breaks = array("always", "left", "right");
97 | $frame_style = $frame->get_style();
98 | if ($force_pagebreak ||
99 | in_array($frame_style->page_break_before, $page_breaks) ||
100 | in_array($frame_style->page_break_after, $page_breaks)
101 | ) {
102 | $this->get_parent()->split($split, true);
103 | }
104 | }
105 |
106 | }
107 |
--------------------------------------------------------------------------------
/lib/fonts/dompdf_font_family_cache.dist.php:
--------------------------------------------------------------------------------
1 |
5 | array(
6 | 'normal' => $distFontDir . 'Helvetica',
7 | 'bold' => $distFontDir . 'Helvetica-Bold',
8 | 'italic' => $distFontDir . 'Helvetica-Oblique',
9 | 'bold_italic' => $distFontDir . 'Helvetica-BoldOblique'
10 | ),
11 | 'times' =>
12 | array(
13 | 'normal' => $distFontDir . 'Times-Roman',
14 | 'bold' => $distFontDir . 'Times-Bold',
15 | 'italic' => $distFontDir . 'Times-Italic',
16 | 'bold_italic' => $distFontDir . 'Times-BoldItalic'
17 | ),
18 | 'times-roman' =>
19 | array(
20 | 'normal' => $distFontDir . 'Times-Roman',
21 | 'bold' => $distFontDir . 'Times-Bold',
22 | 'italic' => $distFontDir . 'Times-Italic',
23 | 'bold_italic' => $distFontDir . 'Times-BoldItalic'
24 | ),
25 | 'courier' =>
26 | array(
27 | 'normal' => $distFontDir . 'Courier',
28 | 'bold' => $distFontDir . 'Courier-Bold',
29 | 'italic' => $distFontDir . 'Courier-Oblique',
30 | 'bold_italic' => $distFontDir . 'Courier-BoldOblique'
31 | ),
32 | 'helvetica' =>
33 | array(
34 | 'normal' => $distFontDir . 'Helvetica',
35 | 'bold' => $distFontDir . 'Helvetica-Bold',
36 | 'italic' => $distFontDir . 'Helvetica-Oblique',
37 | 'bold_italic' => $distFontDir . 'Helvetica-BoldOblique'
38 | ),
39 | 'zapfdingbats' =>
40 | array(
41 | 'normal' => $distFontDir . 'ZapfDingbats',
42 | 'bold' => $distFontDir . 'ZapfDingbats',
43 | 'italic' => $distFontDir . 'ZapfDingbats',
44 | 'bold_italic' => $distFontDir . 'ZapfDingbats'
45 | ),
46 | 'symbol' =>
47 | array(
48 | 'normal' => $distFontDir . 'Symbol',
49 | 'bold' => $distFontDir . 'Symbol',
50 | 'italic' => $distFontDir . 'Symbol',
51 | 'bold_italic' => $distFontDir . 'Symbol'
52 | ),
53 | 'serif' =>
54 | array(
55 | 'normal' => $distFontDir . 'Times-Roman',
56 | 'bold' => $distFontDir . 'Times-Bold',
57 | 'italic' => $distFontDir . 'Times-Italic',
58 | 'bold_italic' => $distFontDir . 'Times-BoldItalic'
59 | ),
60 | 'monospace' =>
61 | array(
62 | 'normal' => $distFontDir . 'Courier',
63 | 'bold' => $distFontDir . 'Courier-Bold',
64 | 'italic' => $distFontDir . 'Courier-Oblique',
65 | 'bold_italic' => $distFontDir . 'Courier-BoldOblique'
66 | ),
67 | 'fixed' =>
68 | array(
69 | 'normal' => $distFontDir . 'Courier',
70 | 'bold' => $distFontDir . 'Courier-Bold',
71 | 'italic' => $distFontDir . 'Courier-Oblique',
72 | 'bold_italic' => $distFontDir . 'Courier-BoldOblique'
73 | ),
74 | 'dejavu sans' =>
75 | array(
76 | 'bold' => $distFontDir . 'DejaVuSans-Bold',
77 | 'bold_italic' => $distFontDir . 'DejaVuSans-BoldOblique',
78 | 'italic' => $distFontDir . 'DejaVuSans-Oblique',
79 | 'normal' => $distFontDir . 'DejaVuSans'
80 | ),
81 | 'dejavu sans mono' =>
82 | array(
83 | 'bold' => $distFontDir . 'DejaVuSansMono-Bold',
84 | 'bold_italic' => $distFontDir . 'DejaVuSansMono-BoldOblique',
85 | 'italic' => $distFontDir . 'DejaVuSansMono-Oblique',
86 | 'normal' => $distFontDir . 'DejaVuSansMono'
87 | ),
88 | 'dejavu serif' =>
89 | array(
90 | 'bold' => $distFontDir . 'DejaVuSerif-Bold',
91 | 'bold_italic' => $distFontDir . 'DejaVuSerif-BoldItalic',
92 | 'italic' => $distFontDir . 'DejaVuSerif-Italic',
93 | 'normal' => $distFontDir . 'DejaVuSerif'
94 | )
95 | );
--------------------------------------------------------------------------------
/src/FrameDecorator/TableCell.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 | */
8 | namespace Dompdf\FrameDecorator;
9 |
10 | use Dompdf\Dompdf;
11 | use Dompdf\Frame;
12 | use Dompdf\FrameDecorator\Block as BlockFrameDecorator;
13 |
14 | /**
15 | * Decorates table cells for layout
16 | *
17 | * @package dompdf
18 | */
19 | class TableCell extends BlockFrameDecorator
20 | {
21 |
22 | protected $_resolved_borders;
23 | protected $_content_height;
24 |
25 | //........................................................................
26 |
27 | /**
28 | * TableCell constructor.
29 | * @param Frame $frame
30 | * @param Dompdf $dompdf
31 | */
32 | function __construct(Frame $frame, Dompdf $dompdf)
33 | {
34 | parent::__construct($frame, $dompdf);
35 | $this->_resolved_borders = array();
36 | $this->_content_height = 0;
37 | }
38 |
39 | //........................................................................
40 |
41 | function reset()
42 | {
43 | parent::reset();
44 | $this->_resolved_borders = array();
45 | $this->_content_height = 0;
46 | $this->_frame->reset();
47 | }
48 |
49 | /**
50 | * @return int
51 | */
52 | function get_content_height()
53 | {
54 | return $this->_content_height;
55 | }
56 |
57 | /**
58 | * @param $height
59 | */
60 | function set_content_height($height)
61 | {
62 | $this->_content_height = $height;
63 | }
64 |
65 | /**
66 | * @param $height
67 | */
68 | function set_cell_height($height)
69 | {
70 | $style = $this->get_style();
71 | $v_space = (float)$style->length_in_pt(
72 | array(
73 | $style->margin_top,
74 | $style->padding_top,
75 | $style->border_top_width,
76 | $style->border_bottom_width,
77 | $style->padding_bottom,
78 | $style->margin_bottom
79 | ),
80 | (float)$style->length_in_pt($style->height)
81 | );
82 |
83 | $new_height = $height - $v_space;
84 | $style->height = $new_height;
85 |
86 | if ($new_height > $this->_content_height) {
87 | $y_offset = 0;
88 |
89 | // Adjust our vertical alignment
90 | switch ($style->vertical_align) {
91 | default:
92 | case "baseline":
93 | // FIXME: this isn't right
94 |
95 | case "top":
96 | // Don't need to do anything
97 | return;
98 |
99 | case "middle":
100 | $y_offset = ($new_height - $this->_content_height) / 2;
101 | break;
102 |
103 | case "bottom":
104 | $y_offset = $new_height - $this->_content_height;
105 | break;
106 | }
107 |
108 | if ($y_offset) {
109 | // Move our children
110 | foreach ($this->get_line_boxes() as $line) {
111 | foreach ($line->get_frames() as $frame) {
112 | $frame->move(0, $y_offset);
113 | }
114 | }
115 | }
116 | }
117 | }
118 |
119 | /**
120 | * @param $side
121 | * @param $border_spec
122 | */
123 | function set_resolved_border($side, $border_spec)
124 | {
125 | $this->_resolved_borders[$side] = $border_spec;
126 | }
127 |
128 | /**
129 | * @param $side
130 | * @return mixed
131 | */
132 | function get_resolved_border($side)
133 | {
134 | return $this->_resolved_borders[$side];
135 | }
136 |
137 | /**
138 | * @return array
139 | */
140 | function get_resolved_borders()
141 | {
142 | return $this->_resolved_borders;
143 | }
144 | }
145 |
--------------------------------------------------------------------------------
/src/FrameReflower/TableCell.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 | */
8 | namespace Dompdf\FrameReflower;
9 |
10 | use Dompdf\FrameDecorator\Block as BlockFrameDecorator;
11 | use Dompdf\FrameDecorator\Table as TableFrameDecorator;
12 |
13 | /**
14 | * Reflows table cells
15 | *
16 | * @package dompdf
17 | */
18 | class TableCell extends Block
19 | {
20 | /**
21 | * TableCell constructor.
22 | * @param BlockFrameDecorator $frame
23 | */
24 | function __construct(BlockFrameDecorator $frame)
25 | {
26 | parent::__construct($frame);
27 | }
28 |
29 | /**
30 | * @param BlockFrameDecorator|null $block
31 | */
32 | function reflow(BlockFrameDecorator $block = null)
33 | {
34 | $style = $this->_frame->get_style();
35 |
36 | $table = TableFrameDecorator::find_parent_table($this->_frame);
37 | $cellmap = $table->get_cellmap();
38 |
39 | list($x, $y) = $cellmap->get_frame_position($this->_frame);
40 | $this->_frame->set_position($x, $y);
41 |
42 | $cells = $cellmap->get_spanned_cells($this->_frame);
43 |
44 | $w = 0;
45 | foreach ($cells["columns"] as $i) {
46 | $col = $cellmap->get_column($i);
47 | $w += $col["used-width"];
48 | }
49 |
50 | //FIXME?
51 | $h = $this->_frame->get_containing_block("h");
52 |
53 | $left_space = (float)$style->length_in_pt(array($style->margin_left,
54 | $style->padding_left,
55 | $style->border_left_width),
56 | $w);
57 |
58 | $right_space = (float)$style->length_in_pt(array($style->padding_right,
59 | $style->margin_right,
60 | $style->border_right_width),
61 | $w);
62 |
63 | $top_space = (float)$style->length_in_pt(array($style->margin_top,
64 | $style->padding_top,
65 | $style->border_top_width),
66 | $h);
67 | $bottom_space = (float)$style->length_in_pt(array($style->margin_bottom,
68 | $style->padding_bottom,
69 | $style->border_bottom_width),
70 | $h);
71 |
72 | $style->width = $cb_w = $w - $left_space - $right_space;
73 |
74 | $content_x = $x + $left_space;
75 | $content_y = $line_y = $y + $top_space;
76 |
77 | // Adjust the first line based on the text-indent property
78 | $indent = (float)$style->length_in_pt($style->text_indent, $w);
79 | $this->_frame->increase_line_width($indent);
80 |
81 | $page = $this->_frame->get_root();
82 |
83 | // Set the y position of the first line in the cell
84 | $line_box = $this->_frame->get_current_line_box();
85 | $line_box->y = $line_y;
86 |
87 | // Set the containing blocks and reflow each child
88 | foreach ($this->_frame->get_children() as $child) {
89 | if ($page->is_full()) {
90 | break;
91 | }
92 |
93 | $child->set_containing_block($content_x, $content_y, $cb_w, $h);
94 | $this->process_clear($child);
95 | $child->reflow($this->_frame);
96 | $this->process_float($child, $x + $left_space, $w - $right_space - $left_space);
97 | }
98 |
99 | // Determine our height
100 | $style_height = (float)$style->length_in_pt($style->height, $h);
101 |
102 | $this->_frame->set_content_height($this->_calculate_content_height());
103 |
104 | $height = max($style_height, (float)$this->_frame->get_content_height());
105 |
106 | // Let the cellmap know our height
107 | $cell_height = $height / count($cells["rows"]);
108 |
109 | if ($style_height <= $height) {
110 | $cell_height += $top_space + $bottom_space;
111 | }
112 |
113 | foreach ($cells["rows"] as $i) {
114 | $cellmap->set_row_height($i, $cell_height);
115 | }
116 |
117 | $style->height = $height;
118 | $this->_text_align();
119 | $this->vertical_align();
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/src/Positioner/Absolute.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 | */
8 |
9 | namespace Dompdf\Positioner;
10 |
11 | use Dompdf\FrameDecorator\AbstractFrameDecorator;
12 |
13 | /**
14 | * Positions absolutely positioned frames
15 | */
16 | class Absolute extends AbstractPositioner
17 | {
18 |
19 | /**
20 | * @param AbstractFrameDecorator $frame
21 | */
22 | function position(AbstractFrameDecorator $frame)
23 | {
24 | $style = $frame->get_style();
25 |
26 | $p = $frame->find_positionned_parent();
27 |
28 | list($x, $y, $w, $h) = $frame->get_containing_block();
29 |
30 | $top = $style->length_in_pt($style->top, $h);
31 | $right = $style->length_in_pt($style->right, $w);
32 | $bottom = $style->length_in_pt($style->bottom, $h);
33 | $left = $style->length_in_pt($style->left, $w);
34 |
35 | if ($p && !($left === "auto" && $right === "auto")) {
36 | // Get the parent's padding box (see http://www.w3.org/TR/CSS21/visuren.html#propdef-top)
37 | list($x, $y, $w, $h) = $p->get_padding_box();
38 | }
39 |
40 | list($width, $height) = array($frame->get_margin_width(), $frame->get_margin_height());
41 |
42 | $orig_style = $frame->get_original_style();
43 | $orig_width = $orig_style->width;
44 | $orig_height = $orig_style->height;
45 |
46 | /****************************
47 | *
48 | * Width auto:
49 | * ____________| left=auto | left=fixed |
50 | * right=auto | A | B |
51 | * right=fixed | C | D |
52 | *
53 | * Width fixed:
54 | * ____________| left=auto | left=fixed |
55 | * right=auto | E | F |
56 | * right=fixed | G | H |
57 | *****************************/
58 |
59 | if ($left === "auto") {
60 | if ($right === "auto") {
61 | // A or E - Keep the frame at the same position
62 | $x = $x + $frame->find_block_parent()->get_current_line_box()->w;
63 | } else {
64 | if ($orig_width === "auto") {
65 | // C
66 | $x += $w - $width - $right;
67 | } else {
68 | // G
69 | $x += $w - $width - $right;
70 | }
71 | }
72 | } else {
73 | if ($right === "auto") {
74 | // B or F
75 | $x += (float)$left;
76 | } else {
77 | if ($orig_width === "auto") {
78 | // D - TODO change width
79 | $x += (float)$left;
80 | } else {
81 | // H - Everything is fixed: left + width win
82 | $x += (float)$left;
83 | }
84 | }
85 | }
86 |
87 | // The same vertically
88 | if ($top === "auto") {
89 | if ($bottom === "auto") {
90 | // A or E - Keep the frame at the same position
91 | $y = $frame->find_block_parent()->get_current_line_box()->y;
92 | } else {
93 | if ($orig_height === "auto") {
94 | // C
95 | $y += (float)$h - $height - (float)$bottom;
96 | } else {
97 | // G
98 | $y += (float)$h - $height - (float)$bottom;
99 | }
100 | }
101 | } else {
102 | if ($bottom === "auto") {
103 | // B or F
104 | $y += (float)$top;
105 | } else {
106 | if ($orig_height === "auto") {
107 | // D - TODO change height
108 | $y += (float)$top;
109 | } else {
110 | // H - Everything is fixed: top + height win
111 | $y += (float)$top;
112 | }
113 | }
114 | }
115 |
116 | $frame->set_position($x, $y);
117 | }
118 | }
119 |
--------------------------------------------------------------------------------
/src/Renderer/Image.php:
--------------------------------------------------------------------------------
1 |
6 | * @author Fabien Ménager
7 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
8 | */
9 | namespace Dompdf\Renderer;
10 |
11 | use Dompdf\Frame;
12 | use Dompdf\Image\Cache;
13 |
14 | /**
15 | * Image renderer
16 | *
17 | * @access private
18 | * @package dompdf
19 | */
20 | class Image extends Block
21 | {
22 |
23 | /**
24 | * @param Frame $frame
25 | */
26 | function render(Frame $frame)
27 | {
28 | // Render background & borders
29 | $style = $frame->get_style();
30 | $cb = $frame->get_containing_block();
31 | list($x, $y, $w, $h) = $frame->get_border_box();
32 |
33 | $this->_set_opacity($frame->get_opacity($style->opacity));
34 |
35 | list($tl, $tr, $br, $bl) = $style->get_computed_border_radius($w, $h);
36 |
37 | $has_border_radius = $tl + $tr + $br + $bl > 0;
38 |
39 | if ($has_border_radius) {
40 | $this->_canvas->clipping_roundrectangle($x, $y, (float)$w, (float)$h, $tl, $tr, $br, $bl);
41 | }
42 |
43 | if (($bg = $style->background_color) !== "transparent") {
44 | $this->_canvas->filled_rectangle($x, $y, (float)$w, (float)$h, $bg);
45 | }
46 |
47 | if (($url = $style->background_image) && $url !== "none") {
48 | $this->_background_image($url, $x, $y, $w, $h, $style);
49 | }
50 |
51 | if ($has_border_radius) {
52 | $this->_canvas->clipping_end();
53 | }
54 |
55 | $this->_render_border($frame);
56 | $this->_render_outline($frame);
57 |
58 | list($x, $y) = $frame->get_padding_box();
59 |
60 | $x += (float)$style->length_in_pt($style->padding_left, $cb["w"]);
61 | $y += (float)$style->length_in_pt($style->padding_top, $cb["h"]);
62 |
63 | $w = (float)$style->length_in_pt($style->width, $cb["w"]);
64 | $h = (float)$style->length_in_pt($style->height, $cb["h"]);
65 |
66 | if ($has_border_radius) {
67 | list($wt, $wr, $wb, $wl) = array(
68 | $style->border_top_width,
69 | $style->border_right_width,
70 | $style->border_bottom_width,
71 | $style->border_left_width,
72 | );
73 |
74 | // we have to get the "inner" radius
75 | if ($tl > 0) {
76 | $tl -= ($wt + $wl) / 2;
77 | }
78 | if ($tr > 0) {
79 | $tr -= ($wt + $wr) / 2;
80 | }
81 | if ($br > 0) {
82 | $br -= ($wb + $wr) / 2;
83 | }
84 | if ($bl > 0) {
85 | $bl -= ($wb + $wl) / 2;
86 | }
87 |
88 | $this->_canvas->clipping_roundrectangle($x, $y, $w, $h, $tl, $tr, $br, $bl);
89 | }
90 |
91 | $src = $frame->get_image_url();
92 | $alt = null;
93 |
94 | if (Cache::is_broken($src) &&
95 | $alt = $frame->get_node()->getAttribute("alt")
96 | ) {
97 | $font = $style->font_family;
98 | $size = $style->font_size;
99 | $spacing = $style->word_spacing;
100 | $this->_canvas->text(
101 | $x,
102 | $y,
103 | $alt,
104 | $font,
105 | $size,
106 | $style->color,
107 | $spacing
108 | );
109 | } else {
110 | $this->_canvas->image($src, $x, $y, $w, $h, $style->image_resolution);
111 | }
112 |
113 | if ($has_border_radius) {
114 | $this->_canvas->clipping_end();
115 | }
116 |
117 | if ($msg = $frame->get_image_msg()) {
118 | $parts = preg_split("/\s*\n\s*/", $msg);
119 | $height = 10;
120 | $_y = $alt ? $y + $h - count($parts) * $height : $y;
121 |
122 | foreach ($parts as $i => $_part) {
123 | $this->_canvas->text($x, $_y + $i * $height, $_part, "times", $height * 0.8, array(0.5, 0.5, 0.5));
124 | }
125 | }
126 |
127 | if ($this->_dompdf->getOptions()->getDebugLayout() && $this->_dompdf->getOptions()->getDebugLayoutBlocks()) {
128 | $this->_debug_layout($frame->get_border_box(), "blue");
129 | if ($this->_dompdf->getOptions()->getDebugLayoutPaddingBox()) {
130 | $this->_debug_layout($frame->get_padding_box(), "blue", array(0.5, 0.5));
131 | }
132 | }
133 |
134 | $id = $frame->get_node()->getAttribute("id");
135 | if (strlen($id) > 0) {
136 | $this->_canvas->add_named_dest($id);
137 | }
138 | }
139 | }
140 |
--------------------------------------------------------------------------------
/lib/html5lib/Data.php:
--------------------------------------------------------------------------------
1 | 0xFFFD, // REPLACEMENT CHARACTER
15 | 0x0D => 0x000A, // LINE FEED (LF)
16 | 0x80 => 0x20AC, // EURO SIGN ('€')
17 | 0x81 => 0x0081, //
18 | 0x82 => 0x201A, // SINGLE LOW-9 QUOTATION MARK ('‚')
19 | 0x83 => 0x0192, // LATIN SMALL LETTER F WITH HOOK ('ƒ')
20 | 0x84 => 0x201E, // DOUBLE LOW-9 QUOTATION MARK ('„')
21 | 0x85 => 0x2026, // HORIZONTAL ELLIPSIS ('…')
22 | 0x86 => 0x2020, // DAGGER ('†')
23 | 0x87 => 0x2021, // DOUBLE DAGGER ('‡')
24 | 0x88 => 0x02C6, // MODIFIER LETTER CIRCUMFLEX ACCENT ('ˆ')
25 | 0x89 => 0x2030, // PER MILLE SIGN ('‰')
26 | 0x8A => 0x0160, // LATIN CAPITAL LETTER S WITH CARON ('Š')
27 | 0x8B => 0x2039, // SINGLE LEFT-POINTING ANGLE QUOTATION MARK ('‹')
28 | 0x8C => 0x0152, // LATIN CAPITAL LIGATURE OE ('Œ')
29 | 0x8D => 0x008D, //
30 | 0x8E => 0x017D, // LATIN CAPITAL LETTER Z WITH CARON ('Ž')
31 | 0x8F => 0x008F, //
32 | 0x90 => 0x0090, //
33 | 0x91 => 0x2018, // LEFT SINGLE QUOTATION MARK ('‘')
34 | 0x92 => 0x2019, // RIGHT SINGLE QUOTATION MARK ('’')
35 | 0x93 => 0x201C, // LEFT DOUBLE QUOTATION MARK ('“')
36 | 0x94 => 0x201D, // RIGHT DOUBLE QUOTATION MARK ('”')
37 | 0x95 => 0x2022, // BULLET ('•')
38 | 0x96 => 0x2013, // EN DASH ('–')
39 | 0x97 => 0x2014, // EM DASH ('—')
40 | 0x98 => 0x02DC, // SMALL TILDE ('˜')
41 | 0x99 => 0x2122, // TRADE MARK SIGN ('™')
42 | 0x9A => 0x0161, // LATIN SMALL LETTER S WITH CARON ('š')
43 | 0x9B => 0x203A, // SINGLE RIGHT-POINTING ANGLE QUOTATION MARK ('›')
44 | 0x9C => 0x0153, // LATIN SMALL LIGATURE OE ('œ')
45 | 0x9D => 0x009D, //
46 | 0x9E => 0x017E, // LATIN SMALL LETTER Z WITH CARON ('ž')
47 | 0x9F => 0x0178, // LATIN CAPITAL LETTER Y WITH DIAERESIS ('Ÿ')
48 | );
49 |
50 | protected static $namedCharacterReferences;
51 |
52 | protected static $namedCharacterReferenceMaxLength;
53 |
54 | /**
55 | * Returns the "real" Unicode codepoint of a malformed character
56 | * reference.
57 | */
58 | public static function getRealCodepoint($ref) {
59 | if (!isset(self::$realCodepointTable[$ref])) {
60 | return false;
61 | } else {
62 | return self::$realCodepointTable[$ref];
63 | }
64 | }
65 |
66 | public static function getNamedCharacterReferences() {
67 | if (!self::$namedCharacterReferences) {
68 | self::$namedCharacterReferences = unserialize(
69 | file_get_contents(dirname(__FILE__) . '/named-character-references.ser'));
70 | }
71 | return self::$namedCharacterReferences;
72 | }
73 |
74 | /**
75 | * Converts a Unicode codepoint to sequence of UTF-8 bytes.
76 | * @note Shamelessly stolen from HTML Purifier, which is also
77 | * shamelessly stolen from Feyd (which is in public domain).
78 | */
79 | public static function utf8chr($code) {
80 | /* We don't care: we live dangerously
81 | * if($code > 0x10FFFF or $code < 0x0 or
82 | ($code >= 0xD800 and $code <= 0xDFFF) ) {
83 | // bits are set outside the "valid" range as defined
84 | // by UNICODE 4.1.0
85 | return "\xEF\xBF\xBD";
86 | }*/
87 |
88 | $y = $z = $w = 0;
89 | if ($code < 0x80) {
90 | // regular ASCII character
91 | $x = $code;
92 | } else {
93 | // set up bits for UTF-8
94 | $x = ($code & 0x3F) | 0x80;
95 | if ($code < 0x800) {
96 | $y = (($code & 0x7FF) >> 6) | 0xC0;
97 | } else {
98 | $y = (($code & 0xFC0) >> 6) | 0x80;
99 | if ($code < 0x10000) {
100 | $z = (($code >> 12) & 0x0F) | 0xE0;
101 | } else {
102 | $z = (($code >> 12) & 0x3F) | 0x80;
103 | $w = (($code >> 18) & 0x07) | 0xF0;
104 | }
105 | }
106 | }
107 | // set up the actual character
108 | $ret = '';
109 | if ($w) {
110 | $ret .= chr($w);
111 | }
112 | if ($z) {
113 | $ret .= chr($z);
114 | }
115 | if ($y) {
116 | $ret .= chr($y);
117 | }
118 | $ret .= chr($x);
119 |
120 | return $ret;
121 | }
122 |
123 | }
124 |
--------------------------------------------------------------------------------
/phpcs.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Coding standard ruleset based on the PSR-2 coding standard.
5 |
6 |
7 | 0
8 |
9 |
10 | 0
11 |
12 |
13 | 0
14 |
15 |
16 | 0
17 |
18 |
19 | 0
20 |
21 |
22 | 0
23 |
24 |
25 | 0
26 |
27 |
28 | 0
29 |
30 |
31 | 0
32 |
33 |
34 |
35 | 0
36 |
37 |
38 | 0
39 |
40 |
41 | 0
42 |
43 |
44 | 0
45 |
46 |
47 | 0
48 |
49 |
50 | 0
51 |
52 |
53 | 0
54 |
55 |
56 | 0
57 |
58 |
59 | 0
60 |
61 |
62 | 0
63 |
64 |
65 | 0
66 |
67 |
68 | 0
69 |
70 |
71 | 0
72 |
73 |
74 | 0
75 |
76 |
77 | 0
78 |
79 |
80 | 0
81 |
82 |
83 | 0
84 |
85 |
86 | 0
87 |
88 |
89 | 0
90 |
91 |
92 | 0
93 |
94 |
95 | 0
96 |
97 |
98 | 0
99 |
100 |
101 | 0
102 |
103 |
104 | 0
105 |
106 |
107 | 0
108 |
109 |
110 | 0
111 |
112 |
113 | 0
114 |
115 |
116 | 0
117 |
118 |
119 | 0
120 |
121 |
122 | 0
123 |
124 |
125 | 0
126 |
127 |
128 | 0
129 |
130 |
131 | 0
132 |
133 |
134 | 0
135 |
136 |
137 | 0
138 |
139 |
140 | 0
141 |
142 |
143 |
--------------------------------------------------------------------------------
/src/FrameDecorator/ListBulletImage.php:
--------------------------------------------------------------------------------
1 |
6 | * @author Helmut Tischer
7 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
8 | */
9 | namespace Dompdf\FrameDecorator;
10 |
11 | use Dompdf\Dompdf;
12 | use Dompdf\Frame;
13 | use Dompdf\Helpers;
14 |
15 | /**
16 | * Decorates frames for list bullets with custom images
17 | *
18 | * @package dompdf
19 | */
20 | class ListBulletImage extends AbstractFrameDecorator
21 | {
22 |
23 | /**
24 | * The underlying image frame
25 | *
26 | * @var Image
27 | */
28 | protected $_img;
29 |
30 | /**
31 | * The image's width in pixels
32 | *
33 | * @var int
34 | */
35 | protected $_width;
36 |
37 | /**
38 | * The image's height in pixels
39 | *
40 | * @var int
41 | */
42 | protected $_height;
43 |
44 | /**
45 | * Class constructor
46 | *
47 | * @param Frame $frame the bullet frame to decorate
48 | * @param Dompdf $dompdf the document's dompdf object
49 | */
50 | function __construct(Frame $frame, Dompdf $dompdf)
51 | {
52 | $style = $frame->get_style();
53 | $url = $style->list_style_image;
54 | $frame->get_node()->setAttribute("src", $url);
55 | $this->_img = new Image($frame, $dompdf);
56 | parent::__construct($this->_img, $dompdf);
57 | list($width, $height) = Helpers::dompdf_getimagesize($this->_img->get_image_url(), $dompdf->getHttpContext());
58 |
59 | // Resample the bullet image to be consistent with 'auto' sized images
60 | // See also Image::get_min_max_width
61 | // Tested php ver: value measured in px, suffix "px" not in value: rtrim unnecessary.
62 | $dpi = $this->_dompdf->getOptions()->getDpi();
63 | $this->_width = ((float)rtrim($width, "px") * 72) / $dpi;
64 | $this->_height = ((float)rtrim($height, "px") * 72) / $dpi;
65 |
66 | //If an image is taller as the containing block/box, the box should be extended.
67 | //Neighbour elements are overwriting the overlapping image areas.
68 | //Todo: Where can the box size be extended?
69 | //Code below has no effect.
70 | //See block_frame_reflower _calculate_restricted_height
71 | //See generated_frame_reflower, Dompdf:render() "list-item", "-dompdf-list-bullet"S.
72 | //Leave for now
73 | //if ($style->min_height < $this->_height ) {
74 | // $style->min_height = $this->_height;
75 | //}
76 | //$style->height = "auto";
77 | }
78 |
79 | /**
80 | * Return the bullet's width
81 | *
82 | * @return int
83 | */
84 | function get_width()
85 | {
86 | //ignore image width, use same width as on predefined bullet ListBullet
87 | //for proper alignment of bullet image and text. Allow image to not fitting on left border.
88 | //This controls the distance between bullet image and text
89 | //return $this->_width;
90 | return $this->_frame->get_style()->get_font_size() * ListBullet::BULLET_SIZE +
91 | 2 * ListBullet::BULLET_PADDING;
92 | }
93 |
94 | /**
95 | * Return the bullet's height
96 | *
97 | * @return int
98 | */
99 | function get_height()
100 | {
101 | //based on image height
102 | return $this->_height;
103 | }
104 |
105 | /**
106 | * Override get_margin_width
107 | *
108 | * @return int
109 | */
110 | function get_margin_width()
111 | {
112 | //ignore image width, use same width as on predefined bullet ListBullet
113 | //for proper alignment of bullet image and text. Allow image to not fitting on left border.
114 | //This controls the extra indentation of text to make room for the bullet image.
115 | //Here use actual image size, not predefined bullet size
116 | //return $this->_frame->get_style()->get_font_size()*ListBullet::BULLET_SIZE +
117 | // 2 * ListBullet::BULLET_PADDING;
118 |
119 | // Small hack to prevent indenting of list text
120 | // Image Might not exist, then position like on list_bullet_frame_decorator fallback to none.
121 | if ($this->_frame->get_style()->list_style_position === "outside" || $this->_width == 0) {
122 | return 0;
123 | }
124 | //This aligns the "inside" image position with the text.
125 | //The text starts to the right of the image.
126 | //Between the image and the text there is an added margin of image width.
127 | //Where this comes from is unknown.
128 | //The corresponding ListBullet sets a smaller margin. bullet size?
129 | return $this->_width + 2 * ListBullet::BULLET_PADDING;
130 | }
131 |
132 | /**
133 | * Override get_margin_height()
134 | *
135 | * @return int
136 | */
137 | function get_margin_height()
138 | {
139 | //Hits only on "inset" lists items, to increase height of box
140 | //based on image height
141 | return $this->_height + 2 * ListBullet::BULLET_PADDING;
142 | }
143 |
144 | /**
145 | * Return image url
146 | *
147 | * @return string
148 | */
149 | function get_image_url()
150 | {
151 | return $this->_img->get_image_url();
152 | }
153 |
154 | }
155 |
--------------------------------------------------------------------------------
/src/Renderer/Text.php:
--------------------------------------------------------------------------------
1 |
6 | * @author Helmut Tischer
7 | * @author Fabien Ménager
8 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
9 | */
10 | namespace Dompdf\Renderer;
11 |
12 | use Dompdf\Adapter\CPDF;
13 | use Dompdf\Frame;
14 |
15 | /**
16 | * Renders text frames
17 | *
18 | * @package dompdf
19 | */
20 | class Text extends AbstractRenderer
21 | {
22 | /** Thickness of underline. Screen: 0.08, print: better less, e.g. 0.04 */
23 | const DECO_THICKNESS = 0.02;
24 |
25 | //Tweaking if $base and $descent are not accurate.
26 | //Check method_exists( $this->_canvas, "get_cpdf" )
27 | //- For cpdf these can and must stay 0, because font metrics are used directly.
28 | //- For other renderers, if different values are wanted, separate the parameter sets.
29 | // But $size and $size-$height seem to be accurate enough
30 |
31 | /** Relative to bottom of text, as fraction of height */
32 | const UNDERLINE_OFFSET = 0.0;
33 |
34 | /** Relative to top of text */
35 | const OVERLINE_OFFSET = 0.0;
36 |
37 | /** Relative to centre of text. */
38 | const LINETHROUGH_OFFSET = 0.0;
39 |
40 | /** How far to extend lines past either end, in pt */
41 | const DECO_EXTENSION = 0.0;
42 |
43 | /**
44 | * @param \Dompdf\FrameDecorator\Text $frame
45 | */
46 | function render(Frame $frame)
47 | {
48 | $text = $frame->get_text();
49 | if (trim($text) === "") {
50 | return;
51 | }
52 |
53 | $style = $frame->get_style();
54 | list($x, $y) = $frame->get_position();
55 | $cb = $frame->get_containing_block();
56 |
57 | if (($ml = $style->margin_left) === "auto" || $ml === "none") {
58 | $ml = 0;
59 | }
60 |
61 | if (($pl = $style->padding_left) === "auto" || $pl === "none") {
62 | $pl = 0;
63 | }
64 |
65 | if (($bl = $style->border_left_width) === "auto" || $bl === "none") {
66 | $bl = 0;
67 | }
68 |
69 | $x += (float)$style->length_in_pt(array($ml, $pl, $bl), $cb["w"]);
70 |
71 | $font = $style->font_family;
72 | $size = $frame_font_size = $style->font_size;
73 | $word_spacing = $frame->get_text_spacing() + (float)$style->length_in_pt($style->word_spacing);
74 | $char_spacing = (float)$style->length_in_pt($style->letter_spacing);
75 | $width = $style->width;
76 |
77 | /*$text = str_replace(
78 | array("{PAGE_NUM}"),
79 | array($this->_canvas->get_page_number()),
80 | $text
81 | );*/
82 |
83 | $this->_canvas->text($x, $y, $text,
84 | $font, $size,
85 | $style->color, $word_spacing, $char_spacing);
86 |
87 | $line = $frame->get_containing_line();
88 |
89 | // FIXME Instead of using the tallest frame to position,
90 | // the decoration, the text should be well placed
91 | if (false && $line->tallest_frame) {
92 | $base_frame = $line->tallest_frame;
93 | $style = $base_frame->get_style();
94 | $size = $style->font_size;
95 | }
96 |
97 | $line_thickness = $size * self::DECO_THICKNESS;
98 | $underline_offset = $size * self::UNDERLINE_OFFSET;
99 | $overline_offset = $size * self::OVERLINE_OFFSET;
100 | $linethrough_offset = $size * self::LINETHROUGH_OFFSET;
101 | $underline_position = -0.08;
102 |
103 | if ($this->_canvas instanceof CPDF) {
104 | $cpdf_font = $this->_canvas->get_cpdf()->fonts[$style->font_family];
105 |
106 | if (isset($cpdf_font["UnderlinePosition"])) {
107 | $underline_position = $cpdf_font["UnderlinePosition"] / 1000;
108 | }
109 |
110 | if (isset($cpdf_font["UnderlineThickness"])) {
111 | $line_thickness = $size * ($cpdf_font["UnderlineThickness"] / 1000);
112 | }
113 | }
114 |
115 | $descent = $size * $underline_position;
116 | $base = $size;
117 |
118 | // Handle text decoration:
119 | // http://www.w3.org/TR/CSS21/text.html#propdef-text-decoration
120 |
121 | // Draw all applicable text-decorations. Start with the root and work our way down.
122 | $p = $frame;
123 | $stack = array();
124 | while ($p = $p->get_parent()) {
125 | $stack[] = $p;
126 | }
127 |
128 | while (isset($stack[0])) {
129 | $f = array_pop($stack);
130 |
131 | if (($text_deco = $f->get_style()->text_decoration) === "none") {
132 | continue;
133 | }
134 |
135 | $deco_y = $y; //$line->y;
136 | $color = $f->get_style()->color;
137 |
138 | switch ($text_deco) {
139 | default:
140 | continue;
141 |
142 | case "underline":
143 | $deco_y += $base - $descent + $underline_offset + $line_thickness / 2;
144 | break;
145 |
146 | case "overline":
147 | $deco_y += $overline_offset + $line_thickness / 2;
148 | break;
149 |
150 | case "line-through":
151 | $deco_y += $base * 0.7 + $linethrough_offset;
152 | break;
153 | }
154 |
155 | $dx = 0;
156 | $x1 = $x - self::DECO_EXTENSION;
157 | $x2 = $x + $width + $dx + self::DECO_EXTENSION;
158 | $this->_canvas->line($x1, $deco_y, $x2, $deco_y, $color, $line_thickness);
159 | }
160 |
161 | if ($this->_dompdf->getOptions()->getDebugLayout() && $this->_dompdf->getOptions()->getDebugLayoutLines()) {
162 | $text_width = $this->_dompdf->getFontMetrics()->getTextWidth($text, $font, $frame_font_size);
163 | $this->_debug_layout(array($x, $y, $text_width + ($line->wc - 1) * $word_spacing, $frame_font_size), "orange", array(0.5, 0.5));
164 | }
165 | }
166 | }
167 |
--------------------------------------------------------------------------------
/src/FrameDecorator/Text.php:
--------------------------------------------------------------------------------
1 |
6 | * @author Brian Sweeney
7 | * @author Fabien Ménager
8 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
9 | */
10 | namespace Dompdf\FrameDecorator;
11 |
12 | use Dompdf\Dompdf;
13 | use Dompdf\Frame;
14 | use Dompdf\Exception;
15 |
16 | /**
17 | * Decorates Frame objects for text layout
18 | *
19 | * @access private
20 | * @package dompdf
21 | */
22 | class Text extends AbstractFrameDecorator
23 | {
24 |
25 | // protected members
26 | protected $_text_spacing;
27 |
28 | /**
29 | * Text constructor.
30 | * @param Frame $frame
31 | * @param Dompdf $dompdf
32 | * @throws Exception
33 | */
34 | function __construct(Frame $frame, Dompdf $dompdf)
35 | {
36 | if (!$frame->is_text_node()) {
37 | throw new Exception("Text_Decorator can only be applied to #text nodes.");
38 | }
39 |
40 | parent::__construct($frame, $dompdf);
41 | $this->_text_spacing = null;
42 | }
43 |
44 | function reset()
45 | {
46 | parent::reset();
47 | $this->_text_spacing = null;
48 | }
49 |
50 | // Accessor methods
51 |
52 | /**
53 | * @return null
54 | */
55 | function get_text_spacing()
56 | {
57 | return $this->_text_spacing;
58 | }
59 |
60 | /**
61 | * @return string
62 | */
63 | function get_text()
64 | {
65 | // FIXME: this should be in a child class (and is incorrect)
66 | // if ( $this->_frame->get_style()->content !== "normal" ) {
67 | // $this->_frame->get_node()->data = $this->_frame->get_style()->content;
68 | // $this->_frame->get_style()->content = "normal";
69 | // }
70 |
71 | // Helpers::pre_r("---");
72 | // $style = $this->_frame->get_style();
73 | // var_dump($text = $this->_frame->get_node()->data);
74 | // var_dump($asc = utf8_decode($text));
75 | // for ($i = 0; $i < strlen($asc); $i++)
76 | // Helpers::pre_r("$i: " . $asc[$i] . " - " . ord($asc[$i]));
77 | // Helpers::pre_r("width: " . $this->_dompdf->getFontMetrics()->getTextWidth($text, $style->font_family, $style->font_size));
78 |
79 | return $this->_frame->get_node()->data;
80 | }
81 |
82 | //........................................................................
83 |
84 | /**
85 | * Vertical margins & padding do not apply to text frames
86 | *
87 | * http://www.w3.org/TR/CSS21/visudet.html#inline-non-replaced:
88 | *
89 | * The vertical padding, border and margin of an inline, non-replaced box
90 | * start at the top and bottom of the content area, not the
91 | * 'line-height'. But only the 'line-height' is used to calculate the
92 | * height of the line box.
93 | *
94 | * @return float|int
95 | */
96 | function get_margin_height()
97 | {
98 | // This function is called in add_frame_to_line() and is used to
99 | // determine the line height, so we actually want to return the
100 | // 'line-height' property, not the actual margin box
101 | $style = $this->get_parent()->get_style();
102 | $font = $style->font_family;
103 | $size = $style->font_size;
104 |
105 | /*
106 | Helpers::pre_r('-----');
107 | Helpers::pre_r($style->line_height);
108 | Helpers::pre_r($style->font_size);
109 | Helpers::pre_r($this->_dompdf->getFontMetrics()->getFontHeight($font, $size));
110 | Helpers::pre_r(($style->line_height / $size) * $this->_dompdf->getFontMetrics()->getFontHeight($font, $size));
111 | */
112 |
113 | return ($style->line_height / ($size > 0 ? $size : 1)) * $this->_dompdf->getFontMetrics()->getFontHeight($font, $size);
114 | }
115 |
116 | /**
117 | * @return array
118 | */
119 | function get_padding_box()
120 | {
121 | $pb = $this->_frame->get_padding_box();
122 | $pb[3] = $pb["h"] = $this->_frame->get_style()->height;
123 |
124 | return $pb;
125 | }
126 |
127 | /**
128 | * @param $spacing
129 | */
130 | function set_text_spacing($spacing)
131 | {
132 | $style = $this->_frame->get_style();
133 |
134 | $this->_text_spacing = $spacing;
135 | $char_spacing = (float)$style->length_in_pt($style->letter_spacing);
136 |
137 | // Re-adjust our width to account for the change in spacing
138 | $style->width = $this->_dompdf->getFontMetrics()->getTextWidth($this->get_text(), $style->font_family, $style->font_size, $spacing, $char_spacing);
139 | }
140 |
141 | /**
142 | * Recalculate the text width
143 | *
144 | * @return float
145 | */
146 | function recalculate_width()
147 | {
148 | $style = $this->get_style();
149 | $text = $this->get_text();
150 | $size = $style->font_size;
151 | $font = $style->font_family;
152 | $word_spacing = (float)$style->length_in_pt($style->word_spacing);
153 | $char_spacing = (float)$style->length_in_pt($style->letter_spacing);
154 |
155 | return $style->width = $this->_dompdf->getFontMetrics()->getTextWidth($text, $font, $size, $word_spacing, $char_spacing);
156 | }
157 |
158 | // Text manipulation methods
159 |
160 | /**
161 | * split the text in this frame at the offset specified. The remaining
162 | * text is added a sibling frame following this one and is returned.
163 | *
164 | * @param $offset
165 | * @return Frame|null
166 | */
167 | function split_text($offset)
168 | {
169 | if ($offset == 0) {
170 | return null;
171 | }
172 |
173 | $split = $this->_frame->get_node()->splitText($offset);
174 |
175 | $deco = $this->copy($split);
176 |
177 | $p = $this->get_parent();
178 | $p->insert_child_after($deco, $this, false);
179 |
180 | if ($p instanceof Inline) {
181 | $p->split($deco);
182 | }
183 |
184 | return $deco;
185 | }
186 |
187 | /**
188 | * @param $offset
189 | * @param $count
190 | */
191 | function delete_text($offset, $count)
192 | {
193 | $this->_frame->get_node()->deleteData($offset, $count);
194 | }
195 |
196 | /**
197 | * @param $text
198 | */
199 | function set_text($text)
200 | {
201 | $this->_frame->get_node()->data = $text;
202 | }
203 | }
204 |
--------------------------------------------------------------------------------
/src/Renderer/TableCell.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 | */
8 | namespace Dompdf\Renderer;
9 |
10 | use Dompdf\Frame;
11 | use Dompdf\FrameDecorator\Table;
12 |
13 | /**
14 | * Renders table cells
15 | *
16 | * @package dompdf
17 | */
18 | class TableCell extends Block
19 | {
20 |
21 | /**
22 | * @param Frame $frame
23 | */
24 | function render(Frame $frame)
25 | {
26 | $style = $frame->get_style();
27 |
28 | if (trim($frame->get_node()->nodeValue) === "" && $style->empty_cells === "hide") {
29 | return;
30 | }
31 |
32 | $this->_set_opacity($frame->get_opacity($style->opacity));
33 | list($x, $y, $w, $h) = $frame->get_border_box();
34 |
35 | // Draw our background, border and content
36 | if (($bg = $style->background_color) !== "transparent") {
37 | $this->_canvas->filled_rectangle($x, $y, (float)$w, (float)$h, $bg);
38 | }
39 |
40 | if (($url = $style->background_image) && $url !== "none") {
41 | $this->_background_image($url, $x, $y, $w, $h, $style);
42 | }
43 |
44 | $table = Table::find_parent_table($frame);
45 |
46 | if ($table->get_style()->border_collapse !== "collapse") {
47 | $this->_render_border($frame);
48 | $this->_render_outline($frame);
49 | return;
50 | }
51 |
52 | // The collapsed case is slightly complicated...
53 | // @todo Add support for outlines here
54 |
55 | $cellmap = $table->get_cellmap();
56 | $cells = $cellmap->get_spanned_cells($frame);
57 |
58 | if (is_null($cells)) {
59 | return;
60 | }
61 |
62 | $num_rows = $cellmap->get_num_rows();
63 | $num_cols = $cellmap->get_num_cols();
64 |
65 | // Determine the top row spanned by this cell
66 | $i = $cells["rows"][0];
67 | $top_row = $cellmap->get_row($i);
68 |
69 | // Determine if this cell borders on the bottom of the table. If so,
70 | // then we draw its bottom border. Otherwise the next row down will
71 | // draw its top border instead.
72 | if (in_array($num_rows - 1, $cells["rows"])) {
73 | $draw_bottom = true;
74 | $bottom_row = $cellmap->get_row($num_rows - 1);
75 | } else {
76 | $draw_bottom = false;
77 | }
78 |
79 | // Draw the horizontal borders
80 | foreach ($cells["columns"] as $j) {
81 | $bp = $cellmap->get_border_properties($i, $j);
82 |
83 | $y = $top_row["y"] - $bp["top"]["width"] / 2;
84 |
85 | $col = $cellmap->get_column($j);
86 | $x = $col["x"] - $bp["left"]["width"] / 2;
87 | $w = $col["used-width"] + ($bp["left"]["width"] + $bp["right"]["width"]) / 2;
88 |
89 | if ($bp["top"]["style"] !== "none" && $bp["top"]["width"] > 0) {
90 | $widths = array(
91 | (float)$bp["top"]["width"],
92 | (float)$bp["right"]["width"],
93 | (float)$bp["bottom"]["width"],
94 | (float)$bp["left"]["width"]
95 | );
96 | $method = "_border_" . $bp["top"]["style"];
97 | $this->$method($x, $y, $w, $bp["top"]["color"], $widths, "top", "square");
98 | }
99 |
100 | if ($draw_bottom) {
101 | $bp = $cellmap->get_border_properties($num_rows - 1, $j);
102 | if ($bp["bottom"]["style"] === "none" || $bp["bottom"]["width"] <= 0) {
103 | continue;
104 | }
105 |
106 | $y = $bottom_row["y"] + $bottom_row["height"] + $bp["bottom"]["width"] / 2;
107 |
108 | $widths = array(
109 | (float)$bp["top"]["width"],
110 | (float)$bp["right"]["width"],
111 | (float)$bp["bottom"]["width"],
112 | (float)$bp["left"]["width"]
113 | );
114 | $method = "_border_" . $bp["bottom"]["style"];
115 | $this->$method($x, $y, $w, $bp["bottom"]["color"], $widths, "bottom", "square");
116 |
117 | }
118 | }
119 |
120 | $j = $cells["columns"][0];
121 |
122 | $left_col = $cellmap->get_column($j);
123 |
124 | if (in_array($num_cols - 1, $cells["columns"])) {
125 | $draw_right = true;
126 | $right_col = $cellmap->get_column($num_cols - 1);
127 | } else {
128 | $draw_right = false;
129 | }
130 |
131 | // Draw the vertical borders
132 | foreach ($cells["rows"] as $i) {
133 | $bp = $cellmap->get_border_properties($i, $j);
134 |
135 | $x = $left_col["x"] - $bp["left"]["width"] / 2;
136 |
137 | $row = $cellmap->get_row($i);
138 |
139 | $y = $row["y"] - $bp["top"]["width"] / 2;
140 | $h = $row["height"] + ($bp["top"]["width"] + $bp["bottom"]["width"]) / 2;
141 |
142 | if ($bp["left"]["style"] !== "none" && $bp["left"]["width"] > 0) {
143 | $widths = array(
144 | (float)$bp["top"]["width"],
145 | (float)$bp["right"]["width"],
146 | (float)$bp["bottom"]["width"],
147 | (float)$bp["left"]["width"]
148 | );
149 |
150 | $method = "_border_" . $bp["left"]["style"];
151 | $this->$method($x, $y, $h, $bp["left"]["color"], $widths, "left", "square");
152 | }
153 |
154 | if ($draw_right) {
155 | $bp = $cellmap->get_border_properties($i, $num_cols - 1);
156 | if ($bp["right"]["style"] === "none" || $bp["right"]["width"] <= 0) {
157 | continue;
158 | }
159 |
160 | $x = $right_col["x"] + $right_col["used-width"] + $bp["right"]["width"] / 2;
161 |
162 | $widths = array(
163 | (float)$bp["top"]["width"],
164 | (float)$bp["right"]["width"],
165 | (float)$bp["bottom"]["width"],
166 | (float)$bp["left"]["width"]
167 | );
168 |
169 | $method = "_border_" . $bp["right"]["style"];
170 | $this->$method($x, $y, $h, $bp["right"]["color"], $widths, "right", "square");
171 | }
172 | }
173 |
174 | $id = $frame->get_node()->getAttribute("id");
175 | if (strlen($id) > 0) {
176 | $this->_canvas->add_named_dest($id);
177 | }
178 | }
179 | }
180 |
--------------------------------------------------------------------------------
/src/FrameReflower/Page.php:
--------------------------------------------------------------------------------
1 |
6 | * @author Fabien Ménager
7 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
8 | */
9 | namespace Dompdf\FrameReflower;
10 |
11 | use Dompdf\Frame;
12 | use Dompdf\FrameDecorator\Block as BlockFrameDecorator;
13 | use Dompdf\FrameDecorator\Page as PageFrameDecorator;
14 |
15 | /**
16 | * Reflows pages
17 | *
18 | * @package dompdf
19 | */
20 | class Page extends AbstractFrameReflower
21 | {
22 |
23 | /**
24 | * Cache of the callbacks array
25 | *
26 | * @var array
27 | */
28 | private $_callbacks;
29 |
30 | /**
31 | * Cache of the canvas
32 | *
33 | * @var \Dompdf\Canvas
34 | */
35 | private $_canvas;
36 |
37 | /**
38 | * Page constructor.
39 | * @param PageFrameDecorator $frame
40 | */
41 | function __construct(PageFrameDecorator $frame)
42 | {
43 | parent::__construct($frame);
44 | }
45 |
46 | /**
47 | * @param Frame $frame
48 | * @param $page_number
49 | */
50 | function apply_page_style(Frame $frame, $page_number)
51 | {
52 | $style = $frame->get_style();
53 | $page_styles = $style->get_stylesheet()->get_page_styles();
54 |
55 | // http://www.w3.org/TR/CSS21/page.html#page-selectors
56 | if (count($page_styles) > 1) {
57 | $odd = $page_number % 2 == 1;
58 | $first = $page_number == 1;
59 |
60 | $style = clone $page_styles["base"];
61 |
62 | // FIXME RTL
63 | if ($odd && isset($page_styles[":right"])) {
64 | $style->merge($page_styles[":right"]);
65 | }
66 |
67 | if ($odd && isset($page_styles[":odd"])) {
68 | $style->merge($page_styles[":odd"]);
69 | }
70 |
71 | // FIXME RTL
72 | if (!$odd && isset($page_styles[":left"])) {
73 | $style->merge($page_styles[":left"]);
74 | }
75 |
76 | if (!$odd && isset($page_styles[":even"])) {
77 | $style->merge($page_styles[":even"]);
78 | }
79 |
80 | if ($first && isset($page_styles[":first"])) {
81 | $style->merge($page_styles[":first"]);
82 | }
83 |
84 | $frame->set_style($style);
85 | }
86 | }
87 |
88 | /**
89 | * Paged layout:
90 | * http://www.w3.org/TR/CSS21/page.html
91 | *
92 | * @param BlockFrameDecorator|null $block
93 | */
94 | function reflow(BlockFrameDecorator $block = null)
95 | {
96 | $fixed_children = array();
97 | $prev_child = null;
98 | $child = $this->_frame->get_first_child();
99 | $current_page = 0;
100 |
101 | while ($child) {
102 | $this->apply_page_style($this->_frame, $current_page + 1);
103 |
104 | $style = $this->_frame->get_style();
105 |
106 | // Pages are only concerned with margins
107 | $cb = $this->_frame->get_containing_block();
108 | $left = (float)$style->length_in_pt($style->margin_left, $cb["w"]);
109 | $right = (float)$style->length_in_pt($style->margin_right, $cb["w"]);
110 | $top = (float)$style->length_in_pt($style->margin_top, $cb["h"]);
111 | $bottom = (float)$style->length_in_pt($style->margin_bottom, $cb["h"]);
112 |
113 | $content_x = $cb["x"] + $left;
114 | $content_y = $cb["y"] + $top;
115 | $content_width = $cb["w"] - $left - $right;
116 | $content_height = $cb["h"] - $top - $bottom;
117 |
118 | // Only if it's the first page, we save the nodes with a fixed position
119 | if ($current_page == 0) {
120 | $children = $child->get_children();
121 | foreach ($children as $onechild) {
122 | if ($onechild->get_style()->position === "fixed") {
123 | $fixed_children[] = $onechild->deep_copy();
124 | }
125 | }
126 | $fixed_children = array_reverse($fixed_children);
127 | }
128 |
129 | $child->set_containing_block($content_x, $content_y, $content_width, $content_height);
130 |
131 | // Check for begin reflow callback
132 | $this->_check_callbacks("begin_page_reflow", $child);
133 |
134 | //Insert a copy of each node which have a fixed position
135 | if ($current_page >= 1) {
136 | foreach ($fixed_children as $fixed_child) {
137 | $child->insert_child_before($fixed_child->deep_copy(), $child->get_first_child());
138 | }
139 | }
140 |
141 | $child->reflow();
142 | $next_child = $child->get_next_sibling();
143 |
144 | // Check for begin render callback
145 | $this->_check_callbacks("begin_page_render", $child);
146 |
147 | // Render the page
148 | $this->_frame->get_renderer()->render($child);
149 |
150 | // Check for end render callback
151 | $this->_check_callbacks("end_page_render", $child);
152 |
153 | if ($next_child) {
154 | $this->_frame->next_page();
155 | }
156 |
157 | // Wait to dispose of all frames on the previous page
158 | // so callback will have access to them
159 | if ($prev_child) {
160 | $prev_child->dispose(true);
161 | }
162 | $prev_child = $child;
163 | $child = $next_child;
164 | $current_page++;
165 | }
166 |
167 | // Dispose of previous page if it still exists
168 | if ($prev_child) {
169 | $prev_child->dispose(true);
170 | }
171 | }
172 |
173 | /**
174 | * Check for callbacks that need to be performed when a given event
175 | * gets triggered on a page
176 | *
177 | * @param string $event the type of event
178 | * @param Frame $frame the frame that event is triggered on
179 | */
180 | protected function _check_callbacks($event, $frame)
181 | {
182 | if (!isset($this->_callbacks)) {
183 | $dompdf = $this->_frame->get_dompdf();
184 | $this->_callbacks = $dompdf->get_callbacks();
185 | $this->_canvas = $dompdf->get_canvas();
186 | }
187 |
188 | if (is_array($this->_callbacks) && isset($this->_callbacks[$event])) {
189 | $info = array(
190 | 0 => $this->_canvas, "canvas" => $this->_canvas,
191 | 1 => $frame, "frame" => $frame,
192 | );
193 | $fs = $this->_callbacks[$event];
194 | foreach ($fs as $f) {
195 | if (is_callable($f)) {
196 | if (is_array($f)) {
197 | $f[0]->{$f[1]}($info);
198 | } else {
199 | $f($info);
200 | }
201 | }
202 | }
203 | }
204 | }
205 | }
206 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | Dompdf
2 | ======
3 |
4 | [](https://travis-ci.org/dompdf/dompdf)
5 | [](https://packagist.org/packages/dompdf/dompdf)
6 | [](https://packagist.org/packages/dompdf/dompdf)
7 | [](https://packagist.org/packages/dompdf/dompdf)
8 | [](https://packagist.org/packages/dompdf/dompdf)
9 |
10 | **Dompdf is an HTML to PDF converter**
11 |
12 | At its heart, dompdf is (mostly) a [CSS 2.1](http://www.w3.org/TR/CSS2/) compliant
13 | HTML layout and rendering engine written in PHP. It is a style-driven renderer:
14 | it will download and read external stylesheets, inline style tags, and the style
15 | attributes of individual HTML elements. It also supports most presentational
16 | HTML attributes.
17 |
18 | *This document applies to the latest stable code which may not reflect the current
19 | release. For released code please
20 | [navigate to the appropriate tag](https://github.com/dompdf/dompdf/tags).*
21 |
22 | ----
23 |
24 | **Check out the [demo](https://dompdf.net/examples.php) and ask any
25 | question on [StackOverflow](http://stackoverflow.com/questions/tagged/dompdf) or
26 | on the [Google Groups](http://groups.google.com/group/dompdf).**
27 |
28 | Follow us on [](http://www.twitter.com/dompdf) or
29 | [](https://plus.google.com/108710008521858993320?prsrc=3).
30 |
31 | ---
32 |
33 |
34 |
35 | ## Features
36 |
37 | * Handles most CSS 2.1 and a few CSS3 properties, including @import, @media &
38 | @page rules
39 | * Supports most presentational HTML 4.0 attributes
40 | * Supports external stylesheets, either local or through http/ftp (via
41 | fopen-wrappers)
42 | * Supports complex tables, including row & column spans, separate & collapsed
43 | border models, individual cell styling
44 | * Image support (gif, png (8, 24 and 32 bit with alpha channel), bmp & jpeg)
45 | * No dependencies on external PDF libraries, thanks to the R&OS PDF class
46 | * Inline PHP support
47 | * Basic SVG support
48 |
49 | ## Requirements
50 |
51 | * PHP version 5.4.0 or higher
52 | * DOM extension
53 | * GD extension
54 | * MBString extension
55 | * php-font-lib
56 | * php-svg-lib
57 |
58 | ### Recommendations
59 |
60 | * OPcache (OPcache, XCache, APC, etc.): improves performance
61 | * IMagick or GMagick extension: improves image processing performance
62 |
63 | Visit the wiki for more information:
64 | https://github.com/dompdf/dompdf/wiki/Requirements
65 |
66 | ## About Fonts & Character Encoding
67 |
68 | PDF documents internally support the following fonts: Helvetica, Times-Roman,
69 | Courier, Zapf-Dingbats, & Symbol. These fonts only support Windows ANSI
70 | encoding. In order for a PDF to display characters that are not available in
71 | Windows ANSI you must supply an external font. Dompdf will embed any referenced
72 | font in the PDF so long as it has been pre-loaded or is accessible to dompdf and
73 | reference in CSS @font-face rules. See the
74 | [font overview](https://github.com/dompdf/dompdf/wiki/About-Fonts-and-Character-Encoding)
75 | for more information on how to use fonts.
76 |
77 | The [DejaVu TrueType fonts](http://dejavu-fonts.org) have been pre-installed
78 | to give dompdf decent Unicode character coverage by default. To use the DejaVu
79 | fonts reference the font in your stylesheet, e.g. `body { font-family: DejaVu
80 | Sans; }` (for DejaVu Sans). The following DejaVu 2.34 fonts are available:
81 | DejaVu Sans, DejaVu Serif, and DejaVu Sans Mono.
82 |
83 | ## Easy Installation
84 |
85 | ### Install with composer
86 |
87 | To install with [Composer](https://getcomposer.org/), simply require the
88 | latest version of this package.
89 |
90 | ```bash
91 | composer require dompdf/dompdf
92 | ```
93 |
94 | Make sure that the autoload file from Composer is loaded.
95 |
96 | ```php
97 | // somewhere early in your project's loading, require the Composer autoloader
98 | // see: http://getcomposer.org/doc/00-intro.md
99 | require 'vendor/autoload.php';
100 |
101 | ```
102 |
103 | ### Download and install
104 |
105 | Download an archive of dompdf and extract it into the directory where dompdf
106 | will reside
107 | * You can download stable copies of dompdf from
108 | https://github.com/dompdf/dompdf/releases
109 | * Or download a nightly (the latest, unreleased code) from
110 | http://eclecticgeek.com/dompdf
111 |
112 | Use the packaged release autoloader to load dompdf, libraries,
113 | and helper functions in your PHP:
114 |
115 | ```php
116 | // include autoloader
117 | require_once 'dompdf/autoload.inc.php';
118 | ```
119 |
120 | ### Install with git
121 |
122 | From the command line, switch to the directory where dompdf will reside and run
123 | the following commands:
124 |
125 | ```sh
126 | git clone https://github.com/dompdf/dompdf.git
127 | cd dompdf
128 |
129 | git clone https://github.com/PhenX/php-font-lib.git lib/php-font-lib
130 | cd lib/php-font-lib
131 | git checkout 0.5.1
132 | cd ..
133 |
134 | git clone https://github.com/PhenX/php-svg-lib.git php-svg-lib
135 | cd php-svg-lib
136 | git checkout v0.3
137 | ```
138 |
139 | Require dompdf, libraries, and helper functions in your PHP:
140 |
141 | ```php
142 | require_once 'dompdf/lib/html5lib/Parser.php';
143 | require_once 'dompdf/lib/php-font-lib/src/FontLib/Autoloader.php';
144 | require_once 'dompdf/lib/php-svg-lib/src/autoload.php';
145 | require_once 'dompdf/src/Autoloader.php';
146 | Dompdf\Autoloader::register();
147 | ```
148 |
149 | ## Quick Start
150 |
151 | Just pass your HTML in to dompdf and stream the output:
152 |
153 | ```php
154 | // reference the Dompdf namespace
155 | use Dompdf\Dompdf;
156 |
157 | // instantiate and use the dompdf class
158 | $dompdf = new Dompdf();
159 | $dompdf->loadHtml('hello world');
160 |
161 | // (Optional) Setup the paper size and orientation
162 | $dompdf->setPaper('A4', 'landscape');
163 |
164 | // Render the HTML as PDF
165 | $dompdf->render();
166 |
167 | // Output the generated PDF to Browser
168 | $dompdf->stream();
169 | ```
170 |
171 | ### Setting Options
172 |
173 | Set options during dompdf instantiation:
174 |
175 | ```php
176 | use Dompdf\Dompdf;
177 | use Dompdf\Options;
178 |
179 | $options = new Options();
180 | $options->set('defaultFont', 'Courier');
181 | $dompdf = new Dompdf($options);
182 | ```
183 |
184 | or at run time
185 |
186 | ```php
187 | use Dompdf\Dompdf;
188 |
189 | $dompdf = new Dompdf();
190 | $dompdf->set_option('defaultFont', 'Courier');
191 | ```
192 |
193 | See [Dompdf\Options](src/Options.php) for a list of available options.
194 |
195 |
196 | ## Limitations (Known Issues)
197 |
198 | * Dompdf is not particularly tolerant to poorly-formed HTML input. To avoid
199 | any unexpected rendering issues you should either enable the built-in HTML5
200 | parser at runtime (`$dompdf->set_option('isHtml5ParserEnabled', true);`)
201 | or run your HTML through a HTML validator/cleaner (such as
202 | [Tidy](http://tidy.sourceforge.net) or the
203 | [W3C Markup Validation Service](http://validator.w3.org)).
204 | * Large files or large tables can take a while to render.
205 | * CSS float is in development and may not produce the desired result
206 |
207 | ---
208 |
209 | [](http://goo.gl/DSvWf)
210 |
211 | *If you find this project useful, please consider making a donation. Any funds donated will be used to help further development on this project.)*
212 |
--------------------------------------------------------------------------------
/src/FrameReflower/Image.php:
--------------------------------------------------------------------------------
1 |
6 | * @author Fabien Ménager
7 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
8 | */
9 | namespace Dompdf\FrameReflower;
10 |
11 | use Dompdf\Helpers;
12 | use Dompdf\FrameDecorator\Block as BlockFrameDecorator;
13 | use Dompdf\FrameDecorator\Image as ImageFrameDecorator;
14 |
15 | /**
16 | * Image reflower class
17 | *
18 | * @package dompdf
19 | */
20 | class Image extends AbstractFrameReflower
21 | {
22 |
23 | /**
24 | * Image constructor.
25 | * @param ImageFrameDecorator $frame
26 | */
27 | function __construct(ImageFrameDecorator $frame)
28 | {
29 | parent::__construct($frame);
30 | }
31 |
32 | /**
33 | * @param BlockFrameDecorator|null $block
34 | */
35 | function reflow(BlockFrameDecorator $block = null)
36 | {
37 | $this->_frame->position();
38 |
39 | //FLOAT
40 | //$frame = $this->_frame;
41 | //$page = $frame->get_root();
42 |
43 | //if ($frame->get_style()->float !== "none" ) {
44 | // $page->add_floating_frame($this);
45 | //}
46 |
47 | // Set the frame's width
48 | $this->get_min_max_width();
49 |
50 | if ($block) {
51 | $block->add_frame_to_line($this->_frame);
52 | }
53 | }
54 |
55 | /**
56 | * @return array
57 | */
58 | function get_min_max_width()
59 | {
60 | if ($this->get_dompdf()->getOptions()->getDebugPng()) {
61 | // Determine the image's size. Time consuming. Only when really needed?
62 | list($img_width, $img_height) = Helpers::dompdf_getimagesize($this->_frame->get_image_url(), $this->get_dompdf()->getHttpContext());
63 | print "get_min_max_width() " .
64 | $this->_frame->get_style()->width . ' ' .
65 | $this->_frame->get_style()->height . ';' .
66 | $this->_frame->get_parent()->get_style()->width . " " .
67 | $this->_frame->get_parent()->get_style()->height . ";" .
68 | $this->_frame->get_parent()->get_parent()->get_style()->width . ' ' .
69 | $this->_frame->get_parent()->get_parent()->get_style()->height . ';' .
70 | $img_width . ' ' .
71 | $img_height . '|';
72 | }
73 |
74 | $style = $this->_frame->get_style();
75 |
76 | $width_forced = true;
77 | $height_forced = true;
78 |
79 | //own style auto or invalid value: use natural size in px
80 | //own style value: ignore suffix text including unit, use given number as px
81 | //own style %: walk up parent chain until found available space in pt; fill available space
82 | //
83 | //special ignored unit: e.g. 10ex: e treated as exponent; x ignored; 10e completely invalid ->like auto
84 |
85 | $width = ($style->width > 0 ? $style->width : 0);
86 | if (Helpers::is_percent($width)) {
87 | $t = 0.0;
88 | for ($f = $this->_frame->get_parent(); $f; $f = $f->get_parent()) {
89 | $f_style = $f->get_style();
90 | $t = $f_style->length_in_pt($f_style->width);
91 | if ($t != 0) {
92 | break;
93 | }
94 | }
95 | $width = ((float)rtrim($width, "%") * $t) / 100; //maybe 0
96 | } else {
97 | // Don't set image original size if "%" branch was 0 or size not given.
98 | // Otherwise aspect changed on %/auto combination for width/height
99 | // Resample according to px per inch
100 | // See also ListBulletImage::__construct
101 | $width = $style->length_in_pt($width);
102 | }
103 |
104 | $height = ($style->height > 0 ? $style->height : 0);
105 | if (Helpers::is_percent($height)) {
106 | $t = 0.0;
107 | for ($f = $this->_frame->get_parent(); $f; $f = $f->get_parent()) {
108 | $f_style = $f->get_style();
109 | $t = (float)$f_style->length_in_pt($f_style->height);
110 | if ($t != 0) {
111 | break;
112 | }
113 | }
114 | $height = ((float)rtrim($height, "%") * $t) / 100; //maybe 0
115 | } else {
116 | // Don't set image original size if "%" branch was 0 or size not given.
117 | // Otherwise aspect changed on %/auto combination for width/height
118 | // Resample according to px per inch
119 | // See also ListBulletImage::__construct
120 | $height = $style->length_in_pt($height);
121 | }
122 |
123 | if ($width == 0 || $height == 0) {
124 | // Determine the image's size. Time consuming. Only when really needed!
125 | list($img_width, $img_height) = Helpers::dompdf_getimagesize($this->_frame->get_image_url(), $this->get_dompdf()->getHttpContext());
126 |
127 | // don't treat 0 as error. Can be downscaled or can be catched elsewhere if image not readable.
128 | // Resample according to px per inch
129 | // See also ListBulletImage::__construct
130 | if ($width == 0 && $height == 0) {
131 | $dpi = $this->_frame->get_dompdf()->getOptions()->getDpi();
132 | $width = (float)($img_width * 72) / $dpi;
133 | $height = (float)($img_height * 72) / $dpi;
134 | $width_forced = false;
135 | $height_forced = false;
136 | } elseif ($height == 0 && $width != 0) {
137 | $height_forced = false;
138 | $height = ($width / $img_width) * $img_height; //keep aspect ratio
139 | } elseif ($width == 0 && $height != 0) {
140 | $width_forced = false;
141 | $width = ($height / $img_height) * $img_width; //keep aspect ratio
142 | }
143 | }
144 |
145 | // Handle min/max width/height
146 | if ($style->min_width !== "none" ||
147 | $style->max_width !== "none" ||
148 | $style->min_height !== "none" ||
149 | $style->max_height !== "none"
150 | ) {
151 |
152 | list( /*$x*/, /*$y*/, $w, $h) = $this->_frame->get_containing_block();
153 |
154 | $min_width = $style->length_in_pt($style->min_width, $w);
155 | $max_width = $style->length_in_pt($style->max_width, $w);
156 | $min_height = $style->length_in_pt($style->min_height, $h);
157 | $max_height = $style->length_in_pt($style->max_height, $h);
158 |
159 | if ($max_width !== "none" && $width > $max_width) {
160 | if (!$height_forced) {
161 | $height *= $max_width / $width;
162 | }
163 |
164 | $width = $max_width;
165 | }
166 |
167 | if ($min_width !== "none" && $width < $min_width) {
168 | if (!$height_forced) {
169 | $height *= $min_width / $width;
170 | }
171 |
172 | $width = $min_width;
173 | }
174 |
175 | if ($max_height !== "none" && $height > $max_height) {
176 | if (!$width_forced) {
177 | $width *= $max_height / $height;
178 | }
179 |
180 | $height = $max_height;
181 | }
182 |
183 | if ($min_height !== "none" && $height < $min_height) {
184 | if (!$width_forced) {
185 | $width *= $min_height / $height;
186 | }
187 |
188 | $height = $min_height;
189 | }
190 | }
191 |
192 | if ($this->get_dompdf()->getOptions()->getDebugPng()) {
193 | print $width . ' ' . $height . ';';
194 | }
195 |
196 | $style->width = $width . "pt";
197 | $style->height = $height . "pt";
198 |
199 | $style->min_width = "none";
200 | $style->max_width = "none";
201 | $style->min_height = "none";
202 | $style->max_height = "none";
203 |
204 | return array($width, $width, "min" => $width, "max" => $width);
205 | }
206 | }
207 |
--------------------------------------------------------------------------------
/src/Image/Cache.php:
--------------------------------------------------------------------------------
1 |
6 | * @author Helmut Tischer
7 | * @author Fabien Ménager
8 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
9 | */
10 | namespace Dompdf\Image;
11 |
12 | use Dompdf\Dompdf;
13 | use Dompdf\Helpers;
14 | use Dompdf\Exception\ImageException;
15 |
16 | /**
17 | * Static class that resolves image urls and downloads and caches
18 | * remote images if required.
19 | *
20 | * @package dompdf
21 | */
22 | class Cache
23 | {
24 | /**
25 | * Array of downloaded images. Cached so that identical images are
26 | * not needlessly downloaded.
27 | *
28 | * @var array
29 | */
30 | protected static $_cache = array();
31 |
32 | /**
33 | * The url to the "broken image" used when images can't be loaded
34 | *
35 | * @var string
36 | */
37 | public static $broken_image = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABABAMAAABYR2ztAAAAA3NCSVQICAjb4U/gAAAAHlBMVEWZmZn////g4OCkpKS1tbXv7++9vb2tra3m5ub5+fkFnN6oAAAACXBIWXMAAAsSAAALEgHS3X78AAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M0BrLToAAAABZ0RVh0Q3JlYXRpb24gVGltZQAwNC8xMi8xMRPnI58AAAGZSURBVEiJhZbPasMwDMbTw2DHKhDQcbDQPsEge4BAjg0Mxh5gkBcY7Niwkpx32PvOjv9JspX60It/+fxJsqxW1b11gN11rA7N3v6vAd5nfR9fDYCTDiyzAeA6qgKd9QDNoAtsAKyKCxzAAfhdBuyHGwC3oovNvQOaxxJwnSNg3ZQFAlBy4ax7AG6ZBLrgA5Cn038SAPgREiaJHJASwXYEhEQQIACyikTTCWCBJJoANBfpPAKQdBLHFMBYkctcBKIE9lAGggt6gRjgA2GV44CL7m1WgS08fAAdsPHxyyMAIyHujgRwEldHArCKy5cBz90+gNOyf8TTyKOUQN2LPEmgnWWPcKD+sr+rnuqTK1avAcHfRSv3afTgVAbqmCPiggLtGM8aSkBNOidVjADrmIDYebT1PoGsWJEE8Oc0b96aZoe4iMBZPiADB6RAzEUA2vwRmyiAL3Lfv6MBSEmUEg7ALt/3LhxwLgj4QNw4UCbKEsaBNpPsyRbgVRASFig78BIGyJNIJQyQTwIi0RvgT98H+Mi6W67j3X8H/427u5bfpQGVAAAAAElFTkSuQmCC";
38 |
39 | public static $error_message = "Image not found or type unknown";
40 |
41 | /**
42 | * Current dompdf instance
43 | *
44 | * @var Dompdf
45 | */
46 | protected static $_dompdf;
47 |
48 | /**
49 | * Resolve and fetch an image for use.
50 | *
51 | * @param string $url The url of the image
52 | * @param string $protocol Default protocol if none specified in $url
53 | * @param string $host Default host if none specified in $url
54 | * @param string $base_path Default path if none specified in $url
55 | * @param Dompdf $dompdf The Dompdf instance
56 | *
57 | * @throws ImageException
58 | * @return array An array with two elements: The local path to the image and the image extension
59 | */
60 | static function resolve_url($url, $protocol, $host, $base_path, Dompdf $dompdf)
61 | {
62 | self::$_dompdf = $dompdf;
63 |
64 | $protocol = mb_strtolower($protocol);
65 | $parsed_url = Helpers::explode_url($url);
66 | $message = null;
67 |
68 | $remote = ($protocol && $protocol !== "file://") || ($parsed_url['protocol'] != "");
69 |
70 | $data_uri = strpos($parsed_url['protocol'], "data:") === 0;
71 | $full_url = null;
72 | $enable_remote = $dompdf->getOptions()->getIsRemoteEnabled();
73 |
74 | try {
75 |
76 | // Remote not allowed and is not DataURI
77 | if (!$enable_remote && $remote && !$data_uri) {
78 | throw new ImageException("Remote file access is disabled.", E_WARNING);
79 | } // Remote allowed or DataURI
80 | else {
81 | if ($enable_remote && $remote || $data_uri) {
82 | // Download remote files to a temporary directory
83 | $full_url = Helpers::build_url($protocol, $host, $base_path, $url);
84 |
85 | // From cache
86 | if (isset(self::$_cache[$full_url])) {
87 | $resolved_url = self::$_cache[$full_url];
88 | } // From remote
89 | else {
90 | $tmp_dir = $dompdf->getOptions()->getTempDir();
91 | $resolved_url = tempnam($tmp_dir, "ca_dompdf_img_");
92 | $image = "";
93 |
94 | if ($data_uri) {
95 | if ($parsed_data_uri = Helpers::parse_data_uri($url)) {
96 | $image = $parsed_data_uri['data'];
97 | }
98 | } else {
99 | list($image, $http_response_header) = Helpers::getFileContent($full_url, $dompdf->getHttpContext());
100 | }
101 |
102 | // Image not found or invalid
103 | if (strlen($image) == 0) {
104 | $msg = ($data_uri ? "Data-URI could not be parsed" : "Image not found");
105 | throw new ImageException($msg, E_WARNING);
106 | } // Image found, put in cache and process
107 | else {
108 | //e.g. fetch.php?media=url.jpg&cache=1
109 | //- Image file name might be one of the dynamic parts of the url, don't strip off!
110 | //- a remote url does not need to have a file extension at all
111 | //- local cached file does not have a matching file extension
112 | //Therefore get image type from the content
113 | file_put_contents($resolved_url, $image);
114 | }
115 | }
116 | } // Not remote, local image
117 | else {
118 | $resolved_url = Helpers::build_url($protocol, $host, $base_path, $url);
119 | }
120 | }
121 |
122 | // Check if the local file is readable
123 | if (!is_readable($resolved_url) || !filesize($resolved_url)) {
124 | throw new ImageException("Image not readable or empty", E_WARNING);
125 | } // Check is the file is an image
126 | else {
127 | list($width, $height, $type) = Helpers::dompdf_getimagesize($resolved_url, $dompdf->getHttpContext());
128 |
129 | // Known image type
130 | if ($width && $height && in_array($type, array("gif", "png", "jpeg", "bmp", "svg"))) {
131 | //Don't put replacement image into cache - otherwise it will be deleted on cache cleanup.
132 | //Only execute on successful caching of remote image.
133 | if ($enable_remote && $remote || $data_uri) {
134 | self::$_cache[$full_url] = $resolved_url;
135 | }
136 | } // Unknown image type
137 | else {
138 | throw new ImageException("Image type unknown", E_WARNING);
139 | }
140 | }
141 | } catch (ImageException $e) {
142 | $resolved_url = self::$broken_image;
143 | $type = "png";
144 | $message = self::$error_message;
145 | Helpers::record_warnings($e->getCode(), $e->getMessage() . " \n $url", $e->getFile(), $e->getLine());
146 | }
147 |
148 | return array($resolved_url, $type, $message);
149 | }
150 |
151 | /**
152 | * Unlink all cached images (i.e. temporary images either downloaded
153 | * or converted)
154 | */
155 | static function clear()
156 | {
157 | if (empty(self::$_cache) || self::$_dompdf->getOptions()->getDebugKeepTemp()) {
158 | return;
159 | }
160 |
161 | foreach (self::$_cache as $file) {
162 | if (self::$_dompdf->getOptions()->getDebugPng()) {
163 | print "[clear unlink $file]";
164 | }
165 | unlink($file);
166 | }
167 |
168 | self::$_cache = array();
169 | }
170 |
171 | static function detect_type($file, $context = null)
172 | {
173 | list(, , $type) = Helpers::dompdf_getimagesize($file, $context);
174 |
175 | return $type;
176 | }
177 |
178 | static function is_broken($url)
179 | {
180 | return $url === self::$broken_image;
181 | }
182 | }
183 |
184 | if (file_exists(realpath(__DIR__ . "/../../lib/res/broken_image.png"))) {
185 | Cache::$broken_image = realpath(__DIR__ . "/../../lib/res/broken_image.png");
186 | }
--------------------------------------------------------------------------------
/src/LineBox.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 | */
8 | namespace Dompdf;
9 |
10 | use Dompdf\FrameDecorator\Block;
11 | use Dompdf\FrameDecorator\Page;
12 |
13 | /**
14 | * The line box class
15 | *
16 | * This class represents a line box
17 | * http://www.w3.org/TR/CSS2/visuren.html#line-box
18 | *
19 | * @package dompdf
20 | */
21 | class LineBox
22 | {
23 |
24 | /**
25 | * @var Block
26 | */
27 | protected $_block_frame;
28 |
29 | /**
30 | * @var Frame[]
31 | */
32 | protected $_frames = array();
33 |
34 | /**
35 | * @var integer
36 | */
37 | public $wc = 0;
38 |
39 | /**
40 | * @var float
41 | */
42 | public $y = null;
43 |
44 | /**
45 | * @var float
46 | */
47 | public $w = 0.0;
48 |
49 | /**
50 | * @var float
51 | */
52 | public $h = 0.0;
53 |
54 | /**
55 | * @var float
56 | */
57 | public $left = 0.0;
58 |
59 | /**
60 | * @var float
61 | */
62 | public $right = 0.0;
63 |
64 | /**
65 | * @var Frame
66 | */
67 | public $tallest_frame = null;
68 |
69 | /**
70 | * @var bool[]
71 | */
72 | public $floating_blocks = array();
73 |
74 | /**
75 | * @var bool
76 | */
77 | public $br = false;
78 |
79 | /**
80 | * Class constructor
81 | *
82 | * @param Block $frame the Block containing this line
83 | * @param int $y
84 | */
85 | public function __construct(Block $frame, $y = 0)
86 | {
87 | $this->_block_frame = $frame;
88 | $this->_frames = array();
89 | $this->y = $y;
90 |
91 | $this->get_float_offsets();
92 | }
93 |
94 | /**
95 | * Returns the floating elements inside the first floating parent
96 | *
97 | * @param Page $root
98 | *
99 | * @return Frame[]
100 | */
101 | public function get_floats_inside(Page $root)
102 | {
103 | $floating_frames = $root->get_floating_frames();
104 |
105 | if (count($floating_frames) == 0) {
106 | return $floating_frames;
107 | }
108 |
109 | // Find nearest floating element
110 | $p = $this->_block_frame;
111 | while ($p->get_style()->float === "none") {
112 | $parent = $p->get_parent();
113 |
114 | if (!$parent) {
115 | break;
116 | }
117 |
118 | $p = $parent;
119 | }
120 |
121 | if ($p == $root) {
122 | return $floating_frames;
123 | }
124 |
125 | $parent = $p;
126 |
127 | $childs = array();
128 |
129 | foreach ($floating_frames as $_floating) {
130 | $p = $_floating->get_parent();
131 |
132 | while (($p = $p->get_parent()) && $p !== $parent);
133 |
134 | if ($p) {
135 | $childs[] = $p;
136 | }
137 | }
138 |
139 | return $childs;
140 | }
141 |
142 | /**
143 | *
144 | */
145 | public function get_float_offsets()
146 | {
147 | static $anti_infinite_loop = 10000; // FIXME smelly hack
148 |
149 | $reflower = $this->_block_frame->get_reflower();
150 |
151 | if (!$reflower) {
152 | return;
153 | }
154 |
155 | $cb_w = null;
156 |
157 | $block = $this->_block_frame;
158 | $root = $block->get_root();
159 |
160 | if (!$root) {
161 | return;
162 | }
163 |
164 | $style = $this->_block_frame->get_style();
165 | $floating_frames = $this->get_floats_inside($root);
166 | $inside_left_floating_width = 0;
167 | $inside_right_floating_width = 0;
168 | $outside_left_floating_width = 0;
169 | $outside_right_floating_width = 0;
170 |
171 | foreach ($floating_frames as $child_key => $floating_frame) {
172 | $floating_frame_parent = $floating_frame->get_parent();
173 | $id = $floating_frame->get_id();
174 |
175 | if (isset($this->floating_blocks[$id])) {
176 | continue;
177 | }
178 |
179 | $float = $floating_frame->get_style()->float;
180 | $floating_width = $floating_frame->get_margin_width();
181 |
182 | if (!$cb_w) {
183 | $cb_w = $floating_frame->get_containing_block("w");
184 | }
185 |
186 | $line_w = $this->get_width();
187 |
188 | if (!$floating_frame->_float_next_line && ($cb_w <= $line_w + $floating_width) && ($cb_w > $line_w)) {
189 | $floating_frame->_float_next_line = true;
190 | continue;
191 | }
192 |
193 | // If the child is still shifted by the floating element
194 | if ($anti_infinite_loop-- > 0 &&
195 | $floating_frame->get_position("y") + $floating_frame->get_margin_height() >= $this->y &&
196 | $block->get_position("x") + $block->get_margin_width() >= $floating_frame->get_position("x")
197 | ) {
198 | if ($float === "left") {
199 | if ($floating_frame_parent === $this->_block_frame) {
200 | $inside_left_floating_width += $floating_width;
201 | } else {
202 | $outside_left_floating_width += $floating_width;
203 | }
204 | } elseif ($float === "right") {
205 | if ($floating_frame_parent === $this->_block_frame) {
206 | $inside_right_floating_width += $floating_width;
207 | } else {
208 | $outside_right_floating_width += $floating_width;
209 | }
210 | }
211 |
212 | $this->floating_blocks[$id] = true;
213 | } // else, the floating element won't shift anymore
214 | else {
215 | $root->remove_floating_frame($child_key);
216 | }
217 | }
218 |
219 | $this->left += $inside_left_floating_width;
220 | if ($outside_left_floating_width > 0 && $outside_left_floating_width > ((float)$style->length_in_pt($style->margin_left) + (float)$style->length_in_pt($style->padding_left))) {
221 | $this->left += $outside_left_floating_width - (float)$style->length_in_pt($style->margin_left) - (float)$style->length_in_pt($style->padding_left);
222 | }
223 | $this->right += $inside_right_floating_width;
224 | if ($outside_right_floating_width > 0 && $outside_right_floating_width > ((float)$style->length_in_pt($style->margin_left) + (float)$style->length_in_pt($style->padding_right))) {
225 | $this->right += $outside_right_floating_width - (float)$style->length_in_pt($style->margin_right) - (float)$style->length_in_pt($style->padding_right);
226 | }
227 | }
228 |
229 | /**
230 | * @return float
231 | */
232 | public function get_width()
233 | {
234 | return $this->left + $this->w + $this->right;
235 | }
236 |
237 | /**
238 | * @return Block
239 | */
240 | public function get_block_frame()
241 | {
242 | return $this->_block_frame;
243 | }
244 |
245 | /**
246 | * @return Frame[]
247 | */
248 | function &get_frames()
249 | {
250 | return $this->_frames;
251 | }
252 |
253 | /**
254 | * @param Frame $frame
255 | */
256 | public function add_frame(Frame $frame)
257 | {
258 | $this->_frames[] = $frame;
259 | }
260 |
261 | /**
262 | * Recalculate LineBox width based on the contained frames total width.
263 | *
264 | * @return float
265 | */
266 | public function recalculate_width()
267 | {
268 | $width = 0;
269 |
270 | foreach ($this->get_frames() as $frame) {
271 | $width += $frame->calculate_auto_width();
272 | }
273 |
274 | return $this->w = $width;
275 | }
276 |
277 | /**
278 | * @return string
279 | */
280 | public function __toString()
281 | {
282 | $props = array("wc", "y", "w", "h", "left", "right", "br");
283 | $s = "";
284 | foreach ($props as $prop) {
285 | $s .= "$prop: " . $this->$prop . "\n";
286 | }
287 | $s .= count($this->_frames) . " frames\n";
288 |
289 | return $s;
290 | }
291 | /*function __get($prop) {
292 | if (!isset($this->{"_$prop"})) return;
293 | return $this->{"_$prop"};
294 | }*/
295 | }
296 |
297 | /*
298 | class LineBoxList implements Iterator {
299 | private $_p = 0;
300 | private $_lines = array();
301 |
302 | }
303 | */
304 |
--------------------------------------------------------------------------------
/src/FrameDecorator/Block.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 | */
8 | namespace Dompdf\FrameDecorator;
9 |
10 | use Dompdf\Dompdf;
11 | use Dompdf\Frame;
12 | use Dompdf\LineBox;
13 |
14 | /**
15 | * Decorates frames for block layout
16 | *
17 | * @access private
18 | * @package dompdf
19 | */
20 | class Block extends AbstractFrameDecorator
21 | {
22 | /**
23 | * Current line index
24 | *
25 | * @var int
26 | */
27 | protected $_cl;
28 |
29 | /**
30 | * The block's line boxes
31 | *
32 | * @var LineBox[]
33 | */
34 | protected $_line_boxes;
35 |
36 | /**
37 | * Block constructor.
38 | * @param Frame $frame
39 | * @param Dompdf $dompdf
40 | */
41 | function __construct(Frame $frame, Dompdf $dompdf)
42 | {
43 | parent::__construct($frame, $dompdf);
44 |
45 | $this->_line_boxes = array(new LineBox($this));
46 | $this->_cl = 0;
47 | }
48 |
49 | /**
50 | *
51 | */
52 | function reset()
53 | {
54 | parent::reset();
55 |
56 | $this->_line_boxes = array(new LineBox($this));
57 | $this->_cl = 0;
58 | }
59 |
60 | /**
61 | * @return LineBox
62 | */
63 | function get_current_line_box()
64 | {
65 | return $this->_line_boxes[$this->_cl];
66 | }
67 |
68 | /**
69 | * @return integer
70 | */
71 | function get_current_line_number()
72 | {
73 | return $this->_cl;
74 | }
75 |
76 | /**
77 | * @return LineBox[]
78 | */
79 | function get_line_boxes()
80 | {
81 | return $this->_line_boxes;
82 | }
83 |
84 | /**
85 | * @param integer $line_number
86 | * @return integer
87 | */
88 | function set_current_line_number($line_number)
89 | {
90 | $line_boxes_count = count($this->_line_boxes);
91 | $cl = max(min($line_number, $line_boxes_count), 0);
92 | return ($this->_cl = $cl);
93 | }
94 |
95 | /**
96 | * @param integer $i
97 | */
98 | function clear_line($i)
99 | {
100 | if (isset($this->_line_boxes[$i])) {
101 | unset($this->_line_boxes[$i]);
102 | }
103 | }
104 |
105 | /**
106 | * @param Frame $frame
107 | */
108 | function add_frame_to_line(Frame $frame)
109 | {
110 | if (!$frame->is_in_flow()) {
111 | return;
112 | }
113 |
114 | $style = $frame->get_style();
115 |
116 | $frame->set_containing_line($this->_line_boxes[$this->_cl]);
117 |
118 | /*
119 | // Adds a new line after a block, only if certain conditions are met
120 | if ((($frame instanceof Inline && $frame->get_node()->nodeName !== "br") ||
121 | $frame instanceof Text && trim($frame->get_text())) &&
122 | ($frame->get_prev_sibling() && $frame->get_prev_sibling()->get_style()->display === "block" &&
123 | $this->_line_boxes[$this->_cl]->w > 0 )) {
124 |
125 | $this->maximize_line_height( $style->length_in_pt($style->line_height), $frame );
126 | $this->add_line();
127 |
128 | // Add each child of the inline frame to the line individually
129 | foreach ($frame->get_children() as $child)
130 | $this->add_frame_to_line( $child );
131 | }
132 | else*/
133 |
134 | // Handle inline frames (which are effectively wrappers)
135 | if ($frame instanceof Inline) {
136 | // Handle line breaks
137 | if ($frame->get_node()->nodeName === "br") {
138 | $this->maximize_line_height($style->length_in_pt($style->line_height), $frame);
139 | $this->add_line(true);
140 | }
141 |
142 | return;
143 | }
144 |
145 | // Trim leading text if this is an empty line. Kinda a hack to put it here,
146 | // but what can you do...
147 | if ($this->get_current_line_box()->w == 0 &&
148 | $frame->is_text_node() &&
149 | !$frame->is_pre()
150 | ) {
151 | $frame->set_text(ltrim($frame->get_text()));
152 | $frame->recalculate_width();
153 | }
154 |
155 | $w = $frame->get_margin_width();
156 |
157 | // FIXME: Why? Doesn't quite seem to be the correct thing to do,
158 | // but does appear to be necessary. Hack to handle wrapped white space?
159 | if ($w == 0 && $frame->get_node()->nodeName !== "hr") {
160 | return;
161 | }
162 |
163 | // Debugging code:
164 | /*
165 | Helpers::pre_r("\nAdding frame to line:
");
166 |
167 | // Helpers::pre_r("Me: " . $this->get_node()->nodeName . " (" . spl_object_hash($this->get_node()) . ")");
168 | // Helpers::pre_r("Node: " . $frame->get_node()->nodeName . " (" . spl_object_hash($frame->get_node()) . ")");
169 | if ( $frame->is_text_node() )
170 | Helpers::pre_r('"'.$frame->get_node()->nodeValue.'"');
171 |
172 | Helpers::pre_r("Line width: " . $this->_line_boxes[$this->_cl]->w);
173 | Helpers::pre_r("Frame: " . get_class($frame));
174 | Helpers::pre_r("Frame width: " . $w);
175 | Helpers::pre_r("Frame height: " . $frame->get_margin_height());
176 | Helpers::pre_r("Containing block width: " . $this->get_containing_block("w"));
177 | */
178 | // End debugging
179 |
180 | $line = $this->_line_boxes[$this->_cl];
181 | if ($line->left + $line->w + $line->right + $w > $this->get_containing_block("w")) {
182 | $this->add_line();
183 | }
184 |
185 | $frame->position();
186 |
187 | $current_line = $this->_line_boxes[$this->_cl];
188 | $current_line->add_frame($frame);
189 |
190 | if ($frame->is_text_node()) {
191 | $current_line->wc += count(preg_split("/\s+/", trim($frame->get_text())));
192 | }
193 |
194 | $this->increase_line_width($w);
195 |
196 | $this->maximize_line_height($frame->get_margin_height(), $frame);
197 | }
198 |
199 | /**
200 | * @param Frame $frame
201 | */
202 | function remove_frames_from_line(Frame $frame)
203 | {
204 | // Search backwards through the lines for $frame
205 | $i = $this->_cl;
206 | $j = null;
207 |
208 | while ($i >= 0) {
209 | if (($j = in_array($frame, $this->_line_boxes[$i]->get_frames(), true)) !== false) {
210 | break;
211 | }
212 |
213 | $i--;
214 | }
215 |
216 | if ($j === false) {
217 | return;
218 | }
219 |
220 | // Remove $frame and all frames that follow
221 | while ($j < count($this->_line_boxes[$i]->get_frames())) {
222 | $frames = $this->_line_boxes[$i]->get_frames();
223 | $f = $frames[$j];
224 | $frames[$j] = null;
225 | unset($frames[$j]);
226 | $j++;
227 | $this->_line_boxes[$i]->w -= $f->get_margin_width();
228 | }
229 |
230 | // Recalculate the height of the line
231 | $h = 0;
232 | foreach ($this->_line_boxes[$i]->get_frames() as $f) {
233 | $h = max($h, $f->get_margin_height());
234 | }
235 |
236 | $this->_line_boxes[$i]->h = $h;
237 |
238 | // Remove all lines that follow
239 | while ($this->_cl > $i) {
240 | $this->_line_boxes[$this->_cl] = null;
241 | unset($this->_line_boxes[$this->_cl]);
242 | $this->_cl--;
243 | }
244 | }
245 |
246 | /**
247 | * @param float $w
248 | */
249 | function increase_line_width($w)
250 | {
251 | $this->_line_boxes[$this->_cl]->w += $w;
252 | }
253 |
254 | /**
255 | * @param $val
256 | * @param Frame $frame
257 | */
258 | function maximize_line_height($val, Frame $frame)
259 | {
260 | if ($val > $this->_line_boxes[$this->_cl]->h) {
261 | $this->_line_boxes[$this->_cl]->tallest_frame = $frame;
262 | $this->_line_boxes[$this->_cl]->h = $val;
263 | }
264 | }
265 |
266 | /**
267 | * @param bool $br
268 | */
269 | function add_line($br = false)
270 | {
271 |
272 | // if ( $this->_line_boxes[$this->_cl]["h"] == 0 ) //count($this->_line_boxes[$i]["frames"]) == 0 ||
273 | // return;
274 |
275 | $this->_line_boxes[$this->_cl]->br = $br;
276 | $y = $this->_line_boxes[$this->_cl]->y + $this->_line_boxes[$this->_cl]->h;
277 |
278 | $new_line = new LineBox($this, $y);
279 |
280 | $this->_line_boxes[++$this->_cl] = $new_line;
281 | }
282 |
283 | //........................................................................
284 | }
285 |
--------------------------------------------------------------------------------
/src/Renderer/ListBullet.php:
--------------------------------------------------------------------------------
1 |
6 | * @author Helmut Tischer
7 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
8 | */
9 | namespace Dompdf\Renderer;
10 |
11 | use Dompdf\Helpers;
12 | use Dompdf\Frame;
13 | use Dompdf\Image\Cache;
14 | use Dompdf\FrameDecorator\ListBullet as ListBulletFrameDecorator;
15 |
16 | /**
17 | * Renders list bullets
18 | *
19 | * @access private
20 | * @package dompdf
21 | */
22 | class ListBullet extends AbstractRenderer
23 | {
24 | /**
25 | * @param $type
26 | * @return mixed|string
27 | */
28 | static function get_counter_chars($type)
29 | {
30 | static $cache = array();
31 |
32 | if (isset($cache[$type])) {
33 | return $cache[$type];
34 | }
35 |
36 | $uppercase = false;
37 | $text = "";
38 |
39 | switch ($type) {
40 | case "decimal-leading-zero":
41 | case "decimal":
42 | case "1":
43 | return "0123456789";
44 |
45 | case "upper-alpha":
46 | case "upper-latin":
47 | case "A":
48 | $uppercase = true;
49 | case "lower-alpha":
50 | case "lower-latin":
51 | case "a":
52 | $text = "abcdefghijklmnopqrstuvwxyz";
53 | break;
54 |
55 | case "upper-roman":
56 | case "I":
57 | $uppercase = true;
58 | case "lower-roman":
59 | case "i":
60 | $text = "ivxlcdm";
61 | break;
62 |
63 | case "lower-greek":
64 | for ($i = 0; $i < 24; $i++) {
65 | $text .= Helpers::unichr($i + 944);
66 | }
67 | break;
68 | }
69 |
70 | if ($uppercase) {
71 | $text = strtoupper($text);
72 | }
73 |
74 | return $cache[$type] = "$text.";
75 | }
76 |
77 | /**
78 | * @param integer $n
79 | * @param string $type
80 | * @param integer $pad
81 | *
82 | * @return string
83 | */
84 | private function make_counter($n, $type, $pad = null)
85 | {
86 | $n = intval($n);
87 | $text = "";
88 | $uppercase = false;
89 |
90 | switch ($type) {
91 | case "decimal-leading-zero":
92 | case "decimal":
93 | case "1":
94 | if ($pad) {
95 | $text = str_pad($n, $pad, "0", STR_PAD_LEFT);
96 | } else {
97 | $text = $n;
98 | }
99 | break;
100 |
101 | case "upper-alpha":
102 | case "upper-latin":
103 | case "A":
104 | $uppercase = true;
105 | case "lower-alpha":
106 | case "lower-latin":
107 | case "a":
108 | $text = chr(($n % 26) + ord('a') - 1);
109 | break;
110 |
111 | case "upper-roman":
112 | case "I":
113 | $uppercase = true;
114 | case "lower-roman":
115 | case "i":
116 | $text = Helpers::dec2roman($n);
117 | break;
118 |
119 | case "lower-greek":
120 | $text = Helpers::unichr($n + 944);
121 | break;
122 | }
123 |
124 | if ($uppercase) {
125 | $text = strtoupper($text);
126 | }
127 |
128 | return "$text.";
129 | }
130 |
131 | /**
132 | * @param Frame $frame
133 | */
134 | function render(Frame $frame)
135 | {
136 | $style = $frame->get_style();
137 | $font_size = $style->get_font_size();
138 | $line_height = (float)$style->length_in_pt($style->line_height, $frame->get_containing_block("h"));
139 |
140 | $this->_set_opacity($frame->get_opacity($style->opacity));
141 |
142 | $li = $frame->get_parent();
143 |
144 | // Don't render bullets twice if if was split
145 | if ($li->_splitted) {
146 | return;
147 | }
148 |
149 | // Handle list-style-image
150 | // If list style image is requested but missing, fall back to predefined types
151 | if ($style->list_style_image !== "none" && !Cache::is_broken($img = $frame->get_image_url())) {
152 | list($x, $y) = $frame->get_position();
153 |
154 | //For expected size and aspect, instead of box size, use image natural size scaled to DPI.
155 | // Resample the bullet image to be consistent with 'auto' sized images
156 | // See also Image::get_min_max_width
157 | // Tested php ver: value measured in px, suffix "px" not in value: rtrim unnecessary.
158 | //$w = $frame->get_width();
159 | //$h = $frame->get_height();
160 | list($width, $height) = Helpers::dompdf_getimagesize($img, $this->_dompdf->getHttpContext());
161 | $dpi = $this->_dompdf->getOptions()->getDpi();
162 | $w = ((float)rtrim($width, "px") * 72) / $dpi;
163 | $h = ((float)rtrim($height, "px") * 72) / $dpi;
164 |
165 | $x -= $w;
166 | $y -= ($line_height - $font_size) / 2; //Reverse hinting of list_bullet_positioner
167 |
168 | $this->_canvas->image($img, $x, $y, $w, $h);
169 | } else {
170 | $bullet_style = $style->list_style_type;
171 |
172 | $fill = false;
173 |
174 | switch ($bullet_style) {
175 | default:
176 | /** @noinspection PhpMissingBreakStatementInspection */
177 | case "disc":
178 | $fill = true;
179 |
180 | case "circle":
181 | list($x, $y) = $frame->get_position();
182 | $r = ($font_size * (ListBulletFrameDecorator::BULLET_SIZE /*-ListBulletFrameDecorator::BULLET_THICKNESS*/)) / 2;
183 | $x -= $font_size * (ListBulletFrameDecorator::BULLET_SIZE / 2);
184 | $y += ($font_size * (1 - ListBulletFrameDecorator::BULLET_DESCENT)) / 2;
185 | $o = $font_size * ListBulletFrameDecorator::BULLET_THICKNESS;
186 | $this->_canvas->circle($x, $y, $r, $style->color, $o, null, $fill);
187 | break;
188 |
189 | case "square":
190 | list($x, $y) = $frame->get_position();
191 | $w = $font_size * ListBulletFrameDecorator::BULLET_SIZE;
192 | $x -= $w;
193 | $y += ($font_size * (1 - ListBulletFrameDecorator::BULLET_DESCENT - ListBulletFrameDecorator::BULLET_SIZE)) / 2;
194 | $this->_canvas->filled_rectangle($x, $y, $w, $w, $style->color);
195 | break;
196 |
197 | case "decimal-leading-zero":
198 | case "decimal":
199 | case "lower-alpha":
200 | case "lower-latin":
201 | case "lower-roman":
202 | case "lower-greek":
203 | case "upper-alpha":
204 | case "upper-latin":
205 | case "upper-roman":
206 | case "1": // HTML 4.0 compatibility
207 | case "a":
208 | case "i":
209 | case "A":
210 | case "I":
211 | $pad = null;
212 | if ($bullet_style === "decimal-leading-zero") {
213 | $pad = strlen($li->get_parent()->get_node()->getAttribute("dompdf-children-count"));
214 | }
215 |
216 | $node = $frame->get_node();
217 |
218 | if (!$node->hasAttribute("dompdf-counter")) {
219 | return;
220 | }
221 |
222 | $index = $node->getAttribute("dompdf-counter");
223 | $text = $this->make_counter($index, $bullet_style, $pad);
224 |
225 | if (trim($text) == "") {
226 | return;
227 | }
228 |
229 | $spacing = 0;
230 | $font_family = $style->font_family;
231 |
232 | $line = $li->get_containing_line();
233 | list($x, $y) = array($frame->get_position("x"), $line->y);
234 |
235 | $x -= $this->_dompdf->getFontMetrics()->getTextWidth($text, $font_family, $font_size, $spacing);
236 |
237 | // Take line-height into account
238 | $line_height = $style->line_height;
239 | $y += ($line_height - $font_size) / 4; // FIXME I thought it should be 2, but 4 gives better results
240 |
241 | $this->_canvas->text($x, $y, $text,
242 | $font_family, $font_size,
243 | $style->color, $spacing);
244 |
245 | case "none":
246 | break;
247 | }
248 | }
249 |
250 | $id = $frame->get_node()->getAttribute("id");
251 | if (strlen($id) > 0) {
252 | $this->_canvas->add_named_dest($id);
253 | }
254 | }
255 | }
256 |
--------------------------------------------------------------------------------
/src/Renderer/Inline.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 | */
8 | namespace Dompdf\Renderer;
9 |
10 | use Dompdf\Frame;
11 | use Dompdf\Helpers;
12 |
13 | /**
14 | * Renders inline frames
15 | *
16 | * @access private
17 | * @package dompdf
18 | */
19 | class Inline extends AbstractRenderer
20 | {
21 |
22 | /**
23 | * @param Frame $frame
24 | */
25 | function render(Frame $frame)
26 | {
27 | $style = $frame->get_style();
28 |
29 | if (!$frame->get_first_child()) {
30 | return; // No children, no service
31 | }
32 |
33 | // Draw the left border if applicable
34 | $bp = $style->get_border_properties();
35 | $widths = array(
36 | (float)$style->length_in_pt($bp["top"]["width"]),
37 | (float)$style->length_in_pt($bp["right"]["width"]),
38 | (float)$style->length_in_pt($bp["bottom"]["width"]),
39 | (float)$style->length_in_pt($bp["left"]["width"])
40 | );
41 |
42 | // Draw the background & border behind each child. To do this we need
43 | // to figure out just how much space each child takes:
44 | list($x, $y) = $frame->get_first_child()->get_position();
45 | $w = null;
46 | $h = 0;
47 | // $x += $widths[3];
48 | // $y += $widths[0];
49 |
50 | $this->_set_opacity($frame->get_opacity($style->opacity));
51 |
52 | $first_row = true;
53 |
54 | $DEBUGLAYOUTINLINE = $this->_dompdf->getOptions()->getDebugLayout() && $this->_dompdf->getOptions()->getDebugLayoutInline();
55 |
56 | foreach ($frame->get_children() as $child) {
57 | list($child_x, $child_y, $child_w, $child_h) = $child->get_padding_box();
58 |
59 | if (!is_null($w) && $child_x < $x + $w) {
60 | //This branch seems to be supposed to being called on the first part
61 | //of an inline html element, and the part after the if clause for the
62 | //parts after a line break.
63 | //But because $w initially mostly is 0, and gets updated only on the next
64 | //round, this seem to be never executed and the common close always.
65 |
66 | // The next child is on another line. Draw the background &
67 | // borders on this line.
68 |
69 | // Background:
70 | if (($bg = $style->background_color) !== "transparent") {
71 | $this->_canvas->filled_rectangle($x, $y, $w, $h, $bg);
72 | }
73 |
74 | if (($url = $style->background_image) && $url !== "none") {
75 | $this->_background_image($url, $x, $y, $w, $h, $style);
76 | }
77 |
78 | // If this is the first row, draw the left border
79 | if ($first_row) {
80 | if ($bp["left"]["style"] !== "none" && $bp["left"]["color"] !== "transparent" && $bp["left"]["width"] > 0) {
81 | $method = "_border_" . $bp["left"]["style"];
82 | $this->$method($x, $y, $h + $widths[0] + $widths[2], $bp["left"]["color"], $widths, "left");
83 | }
84 | $first_row = false;
85 | }
86 |
87 | // Draw the top & bottom borders
88 | if ($bp["top"]["style"] !== "none" && $bp["top"]["color"] !== "transparent" && $bp["top"]["width"] > 0) {
89 | $method = "_border_" . $bp["top"]["style"];
90 | $this->$method($x, $y, $w + $widths[1] + $widths[3], $bp["top"]["color"], $widths, "top");
91 | }
92 |
93 | if ($bp["bottom"]["style"] !== "none" && $bp["bottom"]["color"] !== "transparent" && $bp["bottom"]["width"] > 0) {
94 | $method = "_border_" . $bp["bottom"]["style"];
95 | $this->$method($x, $y + $h + $widths[0] + $widths[2], $w + $widths[1] + $widths[3], $bp["bottom"]["color"], $widths, "bottom");
96 | }
97 |
98 | // Handle anchors & links
99 | $link_node = null;
100 | if ($frame->get_node()->nodeName === "a") {
101 | $link_node = $frame->get_node();
102 | } else if ($frame->get_parent()->get_node()->nodeName === "a") {
103 | $link_node = $frame->get_parent()->get_node();
104 | }
105 |
106 | if ($link_node && $href = $link_node->getAttribute("href")) {
107 | $href = Helpers::build_url($this->_dompdf->getProtocol(), $this->_dompdf->getBaseHost(), $this->_dompdf->getBasePath(), $href);
108 | $this->_canvas->add_link($href, $x, $y, $w, $h);
109 | }
110 |
111 | $x = $child_x;
112 | $y = $child_y;
113 | $w = (float)$child_w;
114 | $h = (float)$child_h;
115 | continue;
116 | }
117 |
118 | if (is_null($w)) {
119 | $w = (float)$child_w;
120 | }else {
121 | $w += (float)$child_w;
122 | }
123 |
124 | $h = max($h, $child_h);
125 |
126 | if ($DEBUGLAYOUTINLINE) {
127 | $this->_debug_layout($child->get_border_box(), "blue");
128 | if ($this->_dompdf->getOptions()->getDebugLayoutPaddingBox()) {
129 | $this->_debug_layout($child->get_padding_box(), "blue", array(0.5, 0.5));
130 | }
131 | }
132 | }
133 |
134 | // Handle the last child
135 | if (($bg = $style->background_color) !== "transparent") {
136 | $this->_canvas->filled_rectangle($x + $widths[3], $y + $widths[0], $w, $h, $bg);
137 | }
138 |
139 | //On continuation lines (after line break) of inline elements, the style got copied.
140 | //But a non repeatable background image should not be repeated on the next line.
141 | //But removing the background image above has never an effect, and removing it below
142 | //removes it always, even on the initial line.
143 | //Need to handle it elsewhere, e.g. on certain ...clone()... usages.
144 | // Repeat not given: default is Style::__construct
145 | // ... && (!($repeat = $style->background_repeat) || $repeat === "repeat" ...
146 | //different position? $this->_background_image($url, $x, $y, $w, $h, $style);
147 | if (($url = $style->background_image) && $url !== "none") {
148 | $this->_background_image($url, $x + $widths[3], $y + $widths[0], $w, $h, $style);
149 | }
150 |
151 | // Add the border widths
152 | $w += (float)$widths[1] + (float)$widths[3];
153 | $h += (float)$widths[0] + (float)$widths[2];
154 |
155 | // make sure the border and background start inside the left margin
156 | $left_margin = (float)$style->length_in_pt($style->margin_left);
157 | $x += $left_margin;
158 |
159 | // If this is the first row, draw the left border too
160 | if ($first_row && $bp["left"]["style"] !== "none" && $bp["left"]["color"] !== "transparent" && $widths[3] > 0) {
161 | $method = "_border_" . $bp["left"]["style"];
162 | $this->$method($x, $y, $h, $bp["left"]["color"], $widths, "left");
163 | }
164 |
165 | // Draw the top & bottom borders
166 | if ($bp["top"]["style"] !== "none" && $bp["top"]["color"] !== "transparent" && $widths[0] > 0) {
167 | $method = "_border_" . $bp["top"]["style"];
168 | $this->$method($x, $y, $w, $bp["top"]["color"], $widths, "top");
169 | }
170 |
171 | if ($bp["bottom"]["style"] !== "none" && $bp["bottom"]["color"] !== "transparent" && $widths[2] > 0) {
172 | $method = "_border_" . $bp["bottom"]["style"];
173 | $this->$method($x, $y + $h, $w, $bp["bottom"]["color"], $widths, "bottom");
174 | }
175 |
176 | // Helpers::var_dump(get_class($frame->get_next_sibling()));
177 | // $last_row = get_class($frame->get_next_sibling()) !== 'Inline';
178 | // Draw the right border if this is the last row
179 | if ($bp["right"]["style"] !== "none" && $bp["right"]["color"] !== "transparent" && $widths[1] > 0) {
180 | $method = "_border_" . $bp["right"]["style"];
181 | $this->$method($x + $w, $y, $h, $bp["right"]["color"], $widths, "right");
182 | }
183 |
184 | $id = $frame->get_node()->getAttribute("id");
185 | if (strlen($id) > 0) {
186 | $this->_canvas->add_named_dest($id);
187 | }
188 |
189 | // Only two levels of links frames
190 | $link_node = null;
191 | if ($frame->get_node()->nodeName === "a") {
192 | $link_node = $frame->get_node();
193 |
194 | if (($name = $link_node->getAttribute("name"))) {
195 | $this->_canvas->add_named_dest($name);
196 | }
197 | }
198 |
199 | if ($frame->get_parent() && $frame->get_parent()->get_node()->nodeName === "a") {
200 | $link_node = $frame->get_parent()->get_node();
201 | }
202 |
203 | // Handle anchors & links
204 | if ($link_node) {
205 | if ($href = $link_node->getAttribute("href")) {
206 | $href = Helpers::build_url($this->_dompdf->getProtocol(), $this->_dompdf->getBaseHost(), $this->_dompdf->getBasePath(), $href);
207 | $this->_canvas->add_link($href, $x, $y, $w, $h);
208 | }
209 | }
210 | }
211 | }
212 |
--------------------------------------------------------------------------------
/src/Renderer/Block.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 | */
8 | namespace Dompdf\Renderer;
9 |
10 | use Dompdf\Frame;
11 | use Dompdf\FrameDecorator\AbstractFrameDecorator;
12 | use Dompdf\Helpers;
13 |
14 | /**
15 | * Renders block frames
16 | *
17 | * @package dompdf
18 | */
19 | class Block extends AbstractRenderer
20 | {
21 |
22 | /**
23 | * @param Frame $frame
24 | */
25 | function render(Frame $frame)
26 | {
27 | $style = $frame->get_style();
28 | $node = $frame->get_node();
29 |
30 | list($x, $y, $w, $h) = $frame->get_border_box();
31 |
32 | $this->_set_opacity($frame->get_opacity($style->opacity));
33 |
34 | if ($node->nodeName === "body") {
35 | $h = $frame->get_containing_block("h") - (float)$style->length_in_pt(array(
36 | $style->margin_top,
37 | $style->border_top_width,
38 | $style->border_bottom_width,
39 | $style->margin_bottom),
40 | (float)$style->length_in_pt($style->width));
41 | }
42 |
43 | // Handle anchors & links
44 | if ($node->nodeName === "a" && $href = $node->getAttribute("href")) {
45 | $href = Helpers::build_url($this->_dompdf->getProtocol(), $this->_dompdf->getBaseHost(), $this->_dompdf->getBasePath(), $href);
46 | $this->_canvas->add_link($href, $x, $y, (float)$w, (float)$h);
47 | }
48 |
49 | // Draw our background, border and content
50 | list($tl, $tr, $br, $bl) = $style->get_computed_border_radius($w, $h);
51 |
52 | if ($tl + $tr + $br + $bl > 0) {
53 | $this->_canvas->clipping_roundrectangle($x, $y, (float)$w, (float)$h, $tl, $tr, $br, $bl);
54 | }
55 |
56 | if (($bg = $style->background_color) !== "transparent") {
57 | $this->_canvas->filled_rectangle($x, $y, (float)$w, (float)$h, $bg);
58 | }
59 |
60 | if (($url = $style->background_image) && $url !== "none") {
61 | $this->_background_image($url, $x, $y, $w, $h, $style);
62 | }
63 |
64 | if ($tl + $tr + $br + $bl > 0) {
65 | $this->_canvas->clipping_end();
66 | }
67 |
68 | $border_box = array($x, $y, $w, $h);
69 | $this->_render_border($frame, $border_box);
70 | $this->_render_outline($frame, $border_box);
71 |
72 | if ($this->_dompdf->getOptions()->getDebugLayout() && $this->_dompdf->getOptions()->getDebugLayoutBlocks()) {
73 | $this->_debug_layout($frame->get_border_box(), "red");
74 | if ($this->_dompdf->getOptions()->getDebugLayoutPaddingBox()) {
75 | $this->_debug_layout($frame->get_padding_box(), "red", array(0.5, 0.5));
76 | }
77 | }
78 |
79 | if ($this->_dompdf->getOptions()->getDebugLayout() && $this->_dompdf->getOptions()->getDebugLayoutLines() && $frame->get_decorator()) {
80 | foreach ($frame->get_decorator()->get_line_boxes() as $line) {
81 | $frame->_debug_layout(array($line->x, $line->y, $line->w, $line->h), "orange");
82 | }
83 | }
84 |
85 | $id = $frame->get_node()->getAttribute("id");
86 | if (strlen($id) > 0) {
87 | $this->_canvas->add_named_dest($id);
88 | }
89 | }
90 |
91 | /**
92 | * @param AbstractFrameDecorator $frame
93 | * @param null $border_box
94 | * @param string $corner_style
95 | */
96 | protected function _render_border(AbstractFrameDecorator $frame, $border_box = null, $corner_style = "bevel")
97 | {
98 | $style = $frame->get_style();
99 | $bp = $style->get_border_properties();
100 |
101 | if (empty($border_box)) {
102 | $border_box = $frame->get_border_box();
103 | }
104 |
105 | // find the radius
106 | $radius = $style->get_computed_border_radius($border_box[2], $border_box[3]); // w, h
107 |
108 | // Short-cut: If all the borders are "solid" with the same color and style, and no radius, we'd better draw a rectangle
109 | if (
110 | in_array($bp["top"]["style"], array("solid", "dashed", "dotted")) &&
111 | $bp["top"] == $bp["right"] &&
112 | $bp["right"] == $bp["bottom"] &&
113 | $bp["bottom"] == $bp["left"] &&
114 | array_sum($radius) == 0
115 | ) {
116 | $props = $bp["top"];
117 | if ($props["color"] === "transparent" || $props["width"] <= 0) {
118 | return;
119 | }
120 |
121 | list($x, $y, $w, $h) = $border_box;
122 | $width = (float)$style->length_in_pt($props["width"]);
123 | $pattern = $this->_get_dash_pattern($props["style"], $width);
124 | $this->_canvas->rectangle($x + $width / 2, $y + $width / 2, (float)$w - $width, (float)$h - $width, $props["color"], $width, $pattern);
125 | return;
126 | }
127 |
128 | // Do it the long way
129 | $widths = array(
130 | (float)$style->length_in_pt($bp["top"]["width"]),
131 | (float)$style->length_in_pt($bp["right"]["width"]),
132 | (float)$style->length_in_pt($bp["bottom"]["width"]),
133 | (float)$style->length_in_pt($bp["left"]["width"])
134 | );
135 |
136 | foreach ($bp as $side => $props) {
137 | list($x, $y, $w, $h) = $border_box;
138 | $length = 0;
139 | $r1 = 0;
140 | $r2 = 0;
141 |
142 | if (!$props["style"] ||
143 | $props["style"] === "none" ||
144 | $props["width"] <= 0 ||
145 | $props["color"] == "transparent"
146 | ) {
147 | continue;
148 | }
149 |
150 | switch ($side) {
151 | case "top":
152 | $length = (float)$w;
153 | $r1 = $radius["top-left"];
154 | $r2 = $radius["top-right"];
155 | break;
156 |
157 | case "bottom":
158 | $length = (float)$w;
159 | $y += (float)$h;
160 | $r1 = $radius["bottom-left"];
161 | $r2 = $radius["bottom-right"];
162 | break;
163 |
164 | case "left":
165 | $length = (float)$h;
166 | $r1 = $radius["top-left"];
167 | $r2 = $radius["bottom-left"];
168 | break;
169 |
170 | case "right":
171 | $length = (float)$h;
172 | $x += (float)$w;
173 | $r1 = $radius["top-right"];
174 | $r2 = $radius["bottom-right"];
175 | break;
176 | default:
177 | break;
178 | }
179 | $method = "_border_" . $props["style"];
180 |
181 | // draw rounded corners
182 | $this->$method($x, $y, $length, $props["color"], $widths, $side, $corner_style, $r1, $r2);
183 | }
184 | }
185 |
186 | /**
187 | * @param AbstractFrameDecorator $frame
188 | * @param null $border_box
189 | * @param string $corner_style
190 | */
191 | protected function _render_outline(AbstractFrameDecorator $frame, $border_box = null, $corner_style = "bevel")
192 | {
193 | $style = $frame->get_style();
194 |
195 | $props = array(
196 | "width" => $style->outline_width,
197 | "style" => $style->outline_style,
198 | "color" => $style->outline_color,
199 | );
200 |
201 | if (!$props["style"] || $props["style"] === "none" || $props["width"] <= 0) {
202 | return;
203 | }
204 |
205 | if (empty($border_box)) {
206 | $border_box = $frame->get_border_box();
207 | }
208 |
209 | $offset = (float)$style->length_in_pt($props["width"]);
210 | $pattern = $this->_get_dash_pattern($props["style"], $offset);
211 |
212 | // If the outline style is "solid" we'd better draw a rectangle
213 | if (in_array($props["style"], array("solid", "dashed", "dotted"))) {
214 | $border_box[0] -= $offset / 2;
215 | $border_box[1] -= $offset / 2;
216 | $border_box[2] += $offset;
217 | $border_box[3] += $offset;
218 |
219 | list($x, $y, $w, $h) = $border_box;
220 | $this->_canvas->rectangle($x, $y, (float)$w, (float)$h, $props["color"], $offset, $pattern);
221 | return;
222 | }
223 |
224 | $border_box[0] -= $offset;
225 | $border_box[1] -= $offset;
226 | $border_box[2] += $offset * 2;
227 | $border_box[3] += $offset * 2;
228 |
229 | $method = "_border_" . $props["style"];
230 | $widths = array_fill(0, 4, (float)$style->length_in_pt($props["width"]));
231 | $sides = array("top", "right", "left", "bottom");
232 | $length = 0;
233 |
234 | foreach ($sides as $side) {
235 | list($x, $y, $w, $h) = $border_box;
236 |
237 | switch ($side) {
238 | case "top":
239 | $length = (float)$w;
240 | break;
241 |
242 | case "bottom":
243 | $length = (float)$w;
244 | $y += (float)$h;
245 | break;
246 |
247 | case "left":
248 | $length = (float)$h;
249 | break;
250 |
251 | case "right":
252 | $length = (float)$h;
253 | $x += (float)$w;
254 | break;
255 | default:
256 | break;
257 | }
258 |
259 | $this->$method($x, $y, $length, $props["color"], $widths, $side, $corner_style);
260 | }
261 | }
262 | }
263 |
--------------------------------------------------------------------------------
/src/Renderer.php:
--------------------------------------------------------------------------------
1 |
6 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
7 | */
8 | namespace Dompdf;
9 |
10 | use Dompdf\Renderer\AbstractRenderer;
11 | use Dompdf\Renderer\Block;
12 | use Dompdf\Renderer\Image;
13 | use Dompdf\Renderer\ListBullet;
14 | use Dompdf\Renderer\TableCell;
15 | use Dompdf\Renderer\TableRowGroup;
16 | use Dompdf\Renderer\Text;
17 |
18 | /**
19 | * Concrete renderer
20 | *
21 | * Instantiates several specific renderers in order to render any given frame.
22 | *
23 | * @package dompdf
24 | */
25 | class Renderer extends AbstractRenderer
26 | {
27 |
28 | /**
29 | * Array of renderers for specific frame types
30 | *
31 | * @var AbstractRenderer[]
32 | */
33 | protected $_renderers;
34 |
35 | /**
36 | * Cache of the callbacks array
37 | *
38 | * @var array
39 | */
40 | private $_callbacks;
41 |
42 | /**
43 | * Advance the canvas to the next page
44 | */
45 | function new_page()
46 | {
47 | $this->_canvas->new_page();
48 | }
49 |
50 | /**
51 | * Render frames recursively
52 | *
53 | * @param Frame $frame the frame to render
54 | */
55 | public function render(Frame $frame)
56 | {
57 | global $_dompdf_debug;
58 |
59 | $this->_check_callbacks("begin_frame", $frame);
60 |
61 | if ($_dompdf_debug) {
62 | echo $frame;
63 | flush();
64 | }
65 |
66 | $style = $frame->get_style();
67 |
68 | if (in_array($style->visibility, array("hidden", "collapse"))) {
69 | return;
70 | }
71 |
72 | $display = $style->display;
73 |
74 | // Starts the CSS transformation
75 | if ($style->transform && is_array($style->transform)) {
76 | $this->_canvas->save();
77 | list($x, $y) = $frame->get_padding_box();
78 | $origin = $style->transform_origin;
79 |
80 | foreach ($style->transform as $transform) {
81 | list($function, $values) = $transform;
82 | if ($function === "matrix") {
83 | $function = "transform";
84 | }
85 |
86 | $values = array_map("floatval", $values);
87 | $values[] = $x + (float)$style->length_in_pt($origin[0], (float)$style->length_in_pt($style->width));
88 | $values[] = $y + (float)$style->length_in_pt($origin[1], (float)$style->length_in_pt($style->height));
89 |
90 | call_user_func_array(array($this->_canvas, $function), $values);
91 | }
92 | }
93 |
94 | switch ($display) {
95 |
96 | case "block":
97 | case "list-item":
98 | case "inline-block":
99 | case "table":
100 | case "inline-table":
101 | $this->_render_frame("block", $frame);
102 | break;
103 |
104 | case "inline":
105 | if ($frame->is_text_node()) {
106 | $this->_render_frame("text", $frame);
107 | } else {
108 | $this->_render_frame("inline", $frame);
109 | }
110 | break;
111 |
112 | case "table-cell":
113 | $this->_render_frame("table-cell", $frame);
114 | break;
115 |
116 | case "table-row-group":
117 | case "table-header-group":
118 | case "table-footer-group":
119 | $this->_render_frame("table-row-group", $frame);
120 | break;
121 |
122 | case "-dompdf-list-bullet":
123 | $this->_render_frame("list-bullet", $frame);
124 | break;
125 |
126 | case "-dompdf-image":
127 | $this->_render_frame("image", $frame);
128 | break;
129 |
130 | case "none":
131 | $node = $frame->get_node();
132 |
133 | if ($node->nodeName === "script") {
134 | if ($node->getAttribute("type") === "text/php" ||
135 | $node->getAttribute("language") === "php"
136 | ) {
137 | // Evaluate embedded php scripts
138 | $this->_render_frame("php", $frame);
139 | } elseif ($node->getAttribute("type") === "text/javascript" ||
140 | $node->getAttribute("language") === "javascript"
141 | ) {
142 | // Insert JavaScript
143 | $this->_render_frame("javascript", $frame);
144 | }
145 | }
146 |
147 | // Don't render children, so skip to next iter
148 | return;
149 |
150 | default:
151 | break;
152 |
153 | }
154 |
155 | // Starts the overflow: hidden box
156 | if ($style->overflow === "hidden") {
157 | list($x, $y, $w, $h) = $frame->get_padding_box();
158 |
159 | // get border radii
160 | $style = $frame->get_style();
161 | list($tl, $tr, $br, $bl) = $style->get_computed_border_radius($w, $h);
162 |
163 | if ($tl + $tr + $br + $bl > 0) {
164 | $this->_canvas->clipping_roundrectangle($x, $y, (float)$w, (float)$h, $tl, $tr, $br, $bl);
165 | } else {
166 | $this->_canvas->clipping_rectangle($x, $y, (float)$w, (float)$h);
167 | }
168 | }
169 |
170 | $stack = array();
171 |
172 | foreach ($frame->get_children() as $child) {
173 | // < 0 : nagative z-index
174 | // = 0 : no z-index, no stacking context
175 | // = 1 : stacking context without z-index
176 | // > 1 : z-index
177 | $child_style = $child->get_style();
178 | $child_z_index = $child_style->z_index;
179 | $z_index = 0;
180 |
181 | if ($child_z_index !== "auto") {
182 | $z_index = intval($child_z_index) + 1;
183 | } elseif ($child_style->float !== "none" || $child->is_positionned()) {
184 | $z_index = 1;
185 | }
186 |
187 | $stack[$z_index][] = $child;
188 | }
189 |
190 | ksort($stack);
191 |
192 | foreach ($stack as $by_index) {
193 | foreach ($by_index as $child) {
194 | $this->render($child);
195 | }
196 | }
197 |
198 | // Ends the overflow: hidden box
199 | if ($style->overflow === "hidden") {
200 | $this->_canvas->clipping_end();
201 | }
202 |
203 | if ($style->transform && is_array($style->transform)) {
204 | $this->_canvas->restore();
205 | }
206 |
207 | // Check for end frame callback
208 | $this->_check_callbacks("end_frame", $frame);
209 | }
210 |
211 | /**
212 | * Check for callbacks that need to be performed when a given event
213 | * gets triggered on a frame
214 | *
215 | * @param string $event the type of event
216 | * @param Frame $frame the frame that event is triggered on
217 | */
218 | protected function _check_callbacks($event, $frame)
219 | {
220 | if (!isset($this->_callbacks)) {
221 | $this->_callbacks = $this->_dompdf->getCallbacks();
222 | }
223 |
224 | if (is_array($this->_callbacks) && isset($this->_callbacks[$event])) {
225 | $info = array(0 => $this->_canvas, "canvas" => $this->_canvas,
226 | 1 => $frame, "frame" => $frame);
227 | $fs = $this->_callbacks[$event];
228 | foreach ($fs as $f) {
229 | if (is_callable($f)) {
230 | if (is_array($f)) {
231 | $f[0]->{$f[1]}($info);
232 | } else {
233 | $f($info);
234 | }
235 | }
236 | }
237 | }
238 | }
239 |
240 | /**
241 | * Render a single frame
242 | *
243 | * Creates Renderer objects on demand
244 | *
245 | * @param string $type type of renderer to use
246 | * @param Frame $frame the frame to render
247 | */
248 | protected function _render_frame($type, $frame)
249 | {
250 |
251 | if (!isset($this->_renderers[$type])) {
252 |
253 | switch ($type) {
254 | case "block":
255 | $this->_renderers[$type] = new Block($this->_dompdf);
256 | break;
257 |
258 | case "inline":
259 | $this->_renderers[$type] = new Renderer\Inline($this->_dompdf);
260 | break;
261 |
262 | case "text":
263 | $this->_renderers[$type] = new Text($this->_dompdf);
264 | break;
265 |
266 | case "image":
267 | $this->_renderers[$type] = new Image($this->_dompdf);
268 | break;
269 |
270 | case "table-cell":
271 | $this->_renderers[$type] = new TableCell($this->_dompdf);
272 | break;
273 |
274 | case "table-row-group":
275 | $this->_renderers[$type] = new TableRowGroup($this->_dompdf);
276 | break;
277 |
278 | case "list-bullet":
279 | $this->_renderers[$type] = new ListBullet($this->_dompdf);
280 | break;
281 |
282 | case "php":
283 | $this->_renderers[$type] = new PhpEvaluator($this->_canvas);
284 | break;
285 |
286 | case "javascript":
287 | $this->_renderers[$type] = new JavascriptEmbedder($this->_dompdf);
288 | break;
289 |
290 | }
291 | }
292 |
293 | $this->_renderers[$type]->render($frame);
294 | }
295 | }
296 |
--------------------------------------------------------------------------------
/src/Frame/FrameTree.php:
--------------------------------------------------------------------------------
1 |
17 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
18 | */
19 |
20 | /**
21 | * Represents an entire document as a tree of frames
22 | *
23 | * The FrameTree consists of {@link Frame} objects each tied to specific
24 | * DOMNode objects in a specific DomDocument. The FrameTree has the same
25 | * structure as the DomDocument, but adds additional capabalities for
26 | * styling and layout.
27 | *
28 | * @package dompdf
29 | */
30 | class FrameTree
31 | {
32 | /**
33 | * Tags to ignore while parsing the tree
34 | *
35 | * @var array
36 | */
37 | protected static $HIDDEN_TAGS = array(
38 | "area",
39 | "base",
40 | "basefont",
41 | "head",
42 | "style",
43 | "meta",
44 | "title",
45 | "colgroup",
46 | "noembed",
47 | "param",
48 | "#comment"
49 | );
50 |
51 | /**
52 | * The main DomDocument
53 | *
54 | * @see http://ca2.php.net/manual/en/ref.dom.php
55 | * @var DOMDocument
56 | */
57 | protected $_dom;
58 |
59 | /**
60 | * The root node of the FrameTree.
61 | *
62 | * @var Frame
63 | */
64 | protected $_root;
65 |
66 | /**
67 | * Subtrees of absolutely positioned elements
68 | *
69 | * @var array of Frames
70 | */
71 | protected $_absolute_frames;
72 |
73 | /**
74 | * A mapping of {@link Frame} objects to DOMNode objects
75 | *
76 | * @var array
77 | */
78 | protected $_registry;
79 |
80 | /**
81 | * Class constructor
82 | *
83 | * @param DOMDocument $dom the main DomDocument object representing the current html document
84 | */
85 | public function __construct(DomDocument $dom)
86 | {
87 | $this->_dom = $dom;
88 | $this->_root = null;
89 | $this->_registry = array();
90 | }
91 |
92 | /**
93 | * Returns the DOMDocument object representing the curent html document
94 | *
95 | * @return DOMDocument
96 | */
97 | public function get_dom()
98 | {
99 | return $this->_dom;
100 | }
101 |
102 | /**
103 | * Returns the root frame of the tree
104 | *
105 | * @return Frame
106 | */
107 | public function get_root()
108 | {
109 | return $this->_root;
110 | }
111 |
112 | /**
113 | * Returns a specific frame given its id
114 | *
115 | * @param string $id
116 | *
117 | * @return Frame|null
118 | */
119 | public function get_frame($id)
120 | {
121 | return isset($this->_registry[$id]) ? $this->_registry[$id] : null;
122 | }
123 |
124 | /**
125 | * Returns a post-order iterator for all frames in the tree
126 | *
127 | * @return FrameTreeList|Frame[]
128 | */
129 | public function get_frames()
130 | {
131 | return new FrameTreeList($this->_root);
132 | }
133 |
134 | /**
135 | * Builds the tree
136 | */
137 | public function build_tree()
138 | {
139 | $html = $this->_dom->getElementsByTagName("html")->item(0);
140 | if (is_null($html)) {
141 | $html = $this->_dom->firstChild;
142 | }
143 |
144 | if (is_null($html)) {
145 | throw new Exception("Requested HTML document contains no data.");
146 | }
147 |
148 | $this->fix_tables();
149 |
150 | $this->_root = $this->_build_tree_r($html);
151 | }
152 |
153 | /**
154 | * Adds missing TBODYs around TR
155 | */
156 | protected function fix_tables()
157 | {
158 | $xp = new DOMXPath($this->_dom);
159 |
160 | // Move table caption before the table
161 | // FIXME find a better way to deal with it...
162 | $captions = $xp->query('//table/caption');
163 | foreach ($captions as $caption) {
164 | $table = $caption->parentNode;
165 | $table->parentNode->insertBefore($caption, $table);
166 | }
167 |
168 | $firstRows = $xp->query('//table/tr[1]');
169 | /** @var DOMElement $tableChild */
170 | foreach ($firstRows as $tableChild) {
171 | $tbody = $this->_dom->createElement('tbody');
172 | $tableNode = $tableChild->parentNode;
173 | do {
174 | if ($tableChild->nodeName === 'tr') {
175 | $tmpNode = $tableChild;
176 | $tableChild = $tableChild->nextSibling;
177 | $tableNode->removeChild($tmpNode);
178 | $tbody->appendChild($tmpNode);
179 | } else {
180 | if ($tbody->hasChildNodes() === true) {
181 | $tableNode->insertBefore($tbody, $tableChild);
182 | $tbody = $this->_dom->createElement('tbody');
183 | }
184 | $tableChild = $tableChild->nextSibling;
185 | }
186 | } while ($tableChild);
187 | if ($tbody->hasChildNodes() === true) {
188 | $tableNode->appendChild($tbody);
189 | }
190 | }
191 | }
192 |
193 | // FIXME: temporary hack, preferably we will improve rendering of sequential #text nodes
194 | /**
195 | * Remove a child from a node
196 | *
197 | * Remove a child from a node. If the removed node results in two
198 | * adjacent #text nodes then combine them.
199 | *
200 | * @param DOMNode $node the current DOMNode being considered
201 | * @param array $children an array of nodes that are the children of $node
202 | * @param int $index index from the $children array of the node to remove
203 | */
204 | protected function _remove_node(DOMNode $node, array &$children, $index)
205 | {
206 | $child = $children[$index];
207 | $previousChild = $child->previousSibling;
208 | $nextChild = $child->nextSibling;
209 | $node->removeChild($child);
210 | if (isset($previousChild, $nextChild)) {
211 | if ($previousChild->nodeName === "#text" && $nextChild->nodeName === "#text") {
212 | $previousChild->nodeValue .= $nextChild->nodeValue;
213 | $this->_remove_node($node, $children, $index+1);
214 | }
215 | }
216 | array_splice($children, $index, 1);
217 | }
218 |
219 | /**
220 | * Recursively adds {@link Frame} objects to the tree
221 | *
222 | * Recursively build a tree of Frame objects based on a dom tree.
223 | * No layout information is calculated at this time, although the
224 | * tree may be adjusted (i.e. nodes and frames for generated content
225 | * and images may be created).
226 | *
227 | * @param DOMNode $node the current DOMNode being considered
228 | *
229 | * @return Frame
230 | */
231 | protected function _build_tree_r(DOMNode $node)
232 | {
233 | $frame = new Frame($node);
234 | $id = $frame->get_id();
235 | $this->_registry[$id] = $frame;
236 |
237 | if (!$node->hasChildNodes()) {
238 | return $frame;
239 | }
240 |
241 | // Store the children in an array so that the tree can be modified
242 | $children = array();
243 | $length = $node->childNodes->length;
244 | for ($i = 0; $i < $length; $i++) {
245 | $children[] = $node->childNodes->item($i);
246 | }
247 | $index = 0;
248 | // INFO: We don't advance $index if a node is removed to avoid skipping nodes
249 | while ($index < count($children)) {
250 | $child = $children[$index];
251 | $nodeName = strtolower($child->nodeName);
252 |
253 | // Skip non-displaying nodes
254 | if (in_array($nodeName, self::$HIDDEN_TAGS)) {
255 | if ($nodeName !== "head" && $nodeName !== "style") {
256 | $this->_remove_node($node, $children, $index);
257 | } else {
258 | $index++;
259 | }
260 | continue;
261 | }
262 | // Skip empty text nodes
263 | if ($nodeName === "#text" && $child->nodeValue === "") {
264 | $this->_remove_node($node, $children, $index);
265 | continue;
266 | }
267 | // Skip empty image nodes
268 | if ($nodeName === "img" && $child->getAttribute("src") === "") {
269 | $this->_remove_node($node, $children, $index);
270 | continue;
271 | }
272 |
273 | if (is_object($child)) {
274 | $frame->append_child($this->_build_tree_r($child), false);
275 | }
276 | $index++;
277 | }
278 |
279 | return $frame;
280 | }
281 |
282 | /**
283 | * @param DOMElement $node
284 | * @param DOMElement $new_node
285 | * @param string $pos
286 | *
287 | * @return mixed
288 | */
289 | public function insert_node(DOMElement $node, DOMElement $new_node, $pos)
290 | {
291 | if ($pos === "after" || !$node->firstChild) {
292 | $node->appendChild($new_node);
293 | } else {
294 | $node->insertBefore($new_node, $node->firstChild);
295 | }
296 |
297 | $this->_build_tree_r($new_node);
298 |
299 | $frame_id = $new_node->getAttribute("frame_id");
300 | $frame = $this->get_frame($frame_id);
301 |
302 | $parent_id = $node->getAttribute("frame_id");
303 | $parent = $this->get_frame($parent_id);
304 |
305 | if ($parent) {
306 | if ($pos === "before") {
307 | $parent->prepend_child($frame, false);
308 | } else {
309 | $parent->append_child($frame, false);
310 | }
311 | }
312 |
313 | return $frame_id;
314 | }
315 | }
--------------------------------------------------------------------------------
/lib/res/html.css:
--------------------------------------------------------------------------------
1 | /**
2 | * dompdf default stylesheet.
3 | *
4 | * @package dompdf
5 | * @link http://dompdf.github.com/
6 | * @author Benj Carson
7 | * @author Blake Ross
8 | * @author Fabien Ménager
9 | * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
10 | *
11 | * Portions from Mozilla
12 | * @link https://dxr.mozilla.org/mozilla-central/source/layout/style/res/html.css
13 | * @license http://mozilla.org/MPL/2.0/ Mozilla Public License, v. 2.0
14 | *
15 | * Portions from W3C
16 | * @link https://drafts.csswg.org/css-ui-3/#default-style-sheet
17 | *
18 | */
19 |
20 | @page {
21 | margin: 1.2cm;
22 | }
23 |
24 | html {
25 | display: -dompdf-page !important;
26 | counter-reset: page;
27 | }
28 |
29 | /* blocks */
30 |
31 | article,
32 | aside,
33 | details,
34 | div,
35 | dt,
36 | figcaption,
37 | footer,
38 | form,
39 | header,
40 | hgroup,
41 | main,
42 | nav,
43 | noscript,
44 | section,
45 | summary {
46 | display: block;
47 | }
48 |
49 | body {
50 | page-break-before: avoid;
51 | display: block;
52 | counter-increment: page;
53 | }
54 |
55 | p, dl, multicol {
56 | display: block;
57 | margin: 1em 0;
58 | }
59 |
60 | dd {
61 | display: block;
62 | margin-left: 40px;
63 | }
64 |
65 | blockquote, figure {
66 | display: block;
67 | margin: 1em 40px;
68 | }
69 |
70 | address {
71 | display: block;
72 | font-style: italic;
73 | }
74 |
75 | center {
76 | display: block;
77 | text-align: center;
78 | }
79 |
80 | blockquote[type=cite] {
81 | display: block;
82 | margin: 1em 0;
83 | padding-left: 1em;
84 | border-left: solid;
85 | border-color: blue;
86 | border-width: thin;
87 | }
88 |
89 | h1, h2, h3, h4, h5, h6 {
90 | display: block;
91 | font-weight: bold;
92 | }
93 |
94 | h1 {
95 | font-size: 2em;
96 | margin: .67em 0;
97 | }
98 |
99 | h2 {
100 | font-size: 1.5em;
101 | margin: .83em 0;
102 | }
103 |
104 | h3 {
105 | font-size: 1.17em;
106 | margin: 1em 0;
107 | }
108 |
109 | h4 {
110 | margin: 1.33em 0;
111 | }
112 |
113 | h5 {
114 | font-size: 0.83em;
115 | margin: 1.67em 0;
116 | }
117 |
118 | h6 {
119 | font-size: 0.67em;
120 | margin: 2.33em 0;
121 | }
122 |
123 | listing {
124 | display: block;
125 | font-family: fixed;
126 | font-size: medium;
127 | white-space: pre;
128 | margin: 1em 0;
129 | }
130 |
131 | plaintext, pre, xmp {
132 | display: block;
133 | font-family: fixed;
134 | white-space: pre;
135 | margin: 1em 0;
136 | }
137 |
138 | /* tables */
139 |
140 | table {
141 | display: table;
142 | border-spacing: 2px;
143 | border-collapse: separate;
144 | margin-top: 0;
145 | margin-bottom: 0;
146 | text-indent: 0;
147 | text-align: left; /* quirk */
148 | }
149 |
150 | table[border] {
151 | border-style: outset;
152 | border-color: gray;
153 | }
154 |
155 | /* This won't work (???) */
156 | /*
157 | table[border] td,
158 | table[border] th {
159 | border: 1pt solid grey;
160 | }*/
161 |
162 | /* make sure backgrounds are inherited in tables -- see bug 4510 */
163 | td, th, tr {
164 | background: inherit;
165 | }
166 |
167 | /* caption inherits from table not table-outer */
168 | caption {
169 | display: table-caption;
170 | text-align: center;
171 | }
172 |
173 | tr {
174 | display: table-row;
175 | vertical-align: inherit;
176 | }
177 |
178 | col {
179 | display: table-column;
180 | }
181 |
182 | colgroup {
183 | display: table-column-group;
184 | }
185 |
186 | tbody {
187 | display: table-row-group;
188 | vertical-align: middle;
189 | }
190 |
191 | thead {
192 | display: table-header-group;
193 | vertical-align: middle;
194 | }
195 |
196 | tfoot {
197 | display: table-footer-group;
198 | vertical-align: middle;
199 | }
200 |
201 | /* To simulate tbody auto-insertion */
202 | table > tr {
203 | vertical-align: middle;
204 | }
205 |
206 | td {
207 | display: table-cell;
208 | vertical-align: inherit;
209 | text-align: inherit;
210 | padding: 1px;
211 | }
212 |
213 | th {
214 | display: table-cell;
215 | vertical-align: inherit;
216 | font-weight: bold;
217 | padding: 1px;
218 | }
219 |
220 | /* inlines */
221 | q {
222 | quotes: '"' '"' "'" "'"; /* FIXME only the first level is used */
223 | }
224 |
225 | q:before {
226 | content: open-quote;
227 | }
228 |
229 | q:after {
230 | content: close-quote;
231 | }
232 |
233 | :link {
234 | color: #00c;
235 | text-decoration: underline;
236 | }
237 |
238 | b, strong {
239 | font-weight: bolder;
240 | }
241 |
242 | i, cite, em, var, dfn {
243 | font-style: italic;
244 | }
245 |
246 | tt, code, kbd, samp {
247 | font-family: fixed;
248 | }
249 |
250 | u, ins {
251 | text-decoration: underline;
252 | }
253 |
254 | s, strike, del {
255 | text-decoration: line-through;
256 | }
257 |
258 | big {
259 | font-size: larger;
260 | }
261 |
262 | small {
263 | font-size: smaller;
264 | }
265 |
266 | sub {
267 | vertical-align: sub;
268 | font-size: smaller;
269 | line-height: normal;
270 | }
271 |
272 | sup {
273 | vertical-align: super;
274 | font-size: smaller;
275 | line-height: normal;
276 | }
277 |
278 | nobr {
279 | white-space: nowrap;
280 | }
281 |
282 | mark {
283 | background: yellow;
284 | color: black;
285 | }
286 |
287 | /* titles */
288 |
289 | abbr[title], acronym[title] {
290 | text-decoration: dotted underline;
291 | }
292 |
293 | /* lists */
294 |
295 | ul, menu, dir {
296 | display: block;
297 | list-style-type: disc;
298 | margin: 1em 0;
299 | padding-left: 40px;
300 | }
301 |
302 | ol {
303 | display: block;
304 | list-style-type: decimal;
305 | margin: 1em 0;
306 | padding-left: 40px;
307 | }
308 |
309 | li {
310 | display: list-item;
311 | }
312 |
313 | /*li:before {
314 | display: -dompdf-list-bullet !important;
315 | content: counter(-dompdf-default-counter) ". ";
316 | padding-right: 0.5em;
317 | }*/
318 |
319 | /* nested lists have no top/bottom margins */
320 | :matches(ul, ol, dir, menu, dl) ul,
321 | :matches(ul, ol, dir, menu, dl) ol,
322 | :matches(ul, ol, dir, menu, dl) dir,
323 | :matches(ul, ol, dir, menu, dl) menu,
324 | :matches(ul, ol, dir, menu, dl) dl {
325 | margin-top: 0;
326 | margin-bottom: 0;
327 | }
328 |
329 | /* 2 deep unordered lists use a circle */
330 | :matches(ul, ol, dir, menu) ul,
331 | :matches(ul, ol, dir, menu) ul,
332 | :matches(ul, ol, dir, menu) ul,
333 | :matches(ul, ol, dir, menu) ul {
334 | list-style-type: circle;
335 | }
336 |
337 | /* 3 deep (or more) unordered lists use a square */
338 | :matches(ul, ol, dir, menu) :matches(ul, ol, dir, menu) ul,
339 | :matches(ul, ol, dir, menu) :matches(ul, ol, dir, menu) menu,
340 | :matches(ul, ol, dir, menu) :matches(ul, ol, dir, menu) dir {
341 | list-style-type: square;
342 | }
343 |
344 | /* forms */
345 | /* From https://drafts.csswg.org/css-ui-3/#default-style-sheet */
346 | form {
347 | display: block;
348 | }
349 |
350 | input, button, select {
351 | display: inline-block;
352 | font-family: sans-serif;
353 | }
354 |
355 | input[type=text],
356 | input[type=password],
357 | select {
358 | width: 12em;
359 | }
360 |
361 | input[type=text],
362 | input[type=password],
363 | input[type=button],
364 | input[type=submit],
365 | input[type=reset],
366 | input[type=file],
367 | button,
368 | textarea,
369 | select {
370 | background: #FFF;
371 | border: 1px solid #999;
372 | padding: 2px;
373 | margin: 2px;
374 | }
375 |
376 | input[type=button],
377 | input[type=submit],
378 | input[type=reset],
379 | input[type=file],
380 | button {
381 | background: #CCC;
382 | text-align: center;
383 | }
384 |
385 | input[type=file] {
386 | width: 8em;
387 | }
388 |
389 | input[type=text]:before,
390 | input[type=button]:before,
391 | input[type=submit]:before,
392 | input[type=reset]:before {
393 | content: attr(value);
394 | }
395 |
396 | input[type=file]:before {
397 | content: "Choose a file";
398 | }
399 |
400 | input[type=password][value]:before {
401 | font-family: "DejaVu Sans" !important;
402 | content: "\2022\2022\2022\2022\2022\2022\2022\2022";
403 | line-height: 1em;
404 | }
405 |
406 | input[type=checkbox],
407 | input[type=radio],
408 | select:after {
409 | font-family: "DejaVu Sans" !important;
410 | font-size: 18px;
411 | line-height: 1;
412 | }
413 |
414 | input[type=checkbox]:before {
415 | content: "\2610";
416 | }
417 |
418 | input[type=checkbox][checked]:before {
419 | content: "\2611";
420 | }
421 |
422 | input[type=radio]:before {
423 | content: "\25CB";
424 | }
425 |
426 | input[type=radio][checked]:before {
427 | content: "\25C9";
428 | }
429 |
430 | textarea {
431 | display: block;
432 | height: 3em;
433 | overflow: hidden;
434 | font-family: monospace;
435 | white-space: pre-wrap;
436 | word-wrap: break-word;
437 | }
438 |
439 | select {
440 | position: relative!important;
441 | overflow: hidden!important;
442 | }
443 |
444 | select:after {
445 | position: absolute;
446 | right: 0;
447 | top: 0;
448 | height: 5em;
449 | width: 1.4em;
450 | text-align: center;
451 | background: #CCC;
452 | content: "\25BE";
453 | }
454 |
455 | select option {
456 | display: none;
457 | }
458 |
459 | select option[selected] {
460 | display: inline;
461 | }
462 |
463 | fieldset {
464 | display: block;
465 | margin: 0.6em 2px 2px;
466 | padding: 0.75em;
467 | border: 1pt groove #666;
468 | position: relative;
469 | }
470 |
471 | fieldset > legend {
472 | position: absolute;
473 | top: -0.6em;
474 | left: 0.75em;
475 | padding: 0 0.3em;
476 | background: white;
477 | }
478 |
479 | legend {
480 | display: inline-block;
481 | }
482 |
483 | /* leafs */
484 |
485 | hr {
486 | display: block;
487 | height: 0;
488 | border: 1px inset;
489 | margin: 0.5em auto 0.5em auto;
490 | }
491 |
492 | hr[size="1"] {
493 | border-style: solid none none none;
494 | }
495 |
496 | iframe {
497 | border: 2px inset;
498 | }
499 |
500 | noframes {
501 | display: block;
502 | }
503 |
504 | br {
505 | display: -dompdf-br;
506 | }
507 |
508 | img, img_generated {
509 | display: -dompdf-image !important;
510 | }
511 |
512 | dompdf_generated {
513 | display: inline;
514 | }
515 |
516 | /* hidden elements */
517 | area, base, basefont, head, meta, script, style, title,
518 | noembed, param {
519 | display: none;
520 | -dompdf-keep: yes;
521 | }
522 |
--------------------------------------------------------------------------------