├── 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 | 13 | 14 |
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
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 | [![Build Status](https://travis-ci.org/dompdf/dompdf.png?branch=master)](https://travis-ci.org/dompdf/dompdf) 5 | [![Latest Stable Version](https://poser.pugx.org/dompdf/dompdf/v/stable.png)](https://packagist.org/packages/dompdf/dompdf) 6 | [![Total Downloads](https://poser.pugx.org/dompdf/dompdf/downloads.png)](https://packagist.org/packages/dompdf/dompdf) 7 | [![Latest Unstable Version](https://poser.pugx.org/dompdf/dompdf/v/unstable.png)](https://packagist.org/packages/dompdf/dompdf) 8 | [![License](https://poser.pugx.org/dompdf/dompdf/license.png)](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 [![Twitter](http://twitter-badges.s3.amazonaws.com/twitter-a.png)](http://www.twitter.com/dompdf) or 29 | [![Follow us on Google+](https://ssl.gstatic.com/images/icons/gplus-16.png)](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 | [![Donate button](https://www.paypal.com/en_US/i/btn/btn_donate_SM.gif)](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("\n

Adding 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 | --------------------------------------------------------------------------------