Merge pull request #3206 from Icinga/bugfix/pdf-export-error-3202

Upgrade dompdf to v0.8.2
This commit is contained in:
lippserd 2017-12-14 12:50:00 +01:00 committed by GitHub
commit fedf43239d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
113 changed files with 45020 additions and 40854 deletions

View File

@ -3,13 +3,32 @@
namespace Icinga\File; namespace Icinga\File;
use Dompdf\Autoloader;
use Dompdf\Dompdf; use Dompdf\Dompdf;
use Dompdf\Options; use Dompdf\Options;
use Icinga\Application\Icinga; use Icinga\Application\Icinga;
use Icinga\Exception\ProgrammingError; use Icinga\Exception\ProgrammingError;
use Icinga\Web\Url; use Icinga\Web\Url;
require_once 'dompdf/autoload.inc.php'; call_user_func(function () {
/**
* @package dompdf
* @link http://dompdf.github.com/
* @author Benj Carson <benjcarson@digitaljunkies.ca>
* @author Fabien Ménager <fabien.menager@gmail.com>
* @author Alexander A. Klimov <alexander.klimov@icinga.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
*/
$baseDir = __DIR__ . '/../../vendor/dompdf';
require_once "$baseDir/lib/html5lib/Parser.php";
require_once "$baseDir/lib/php-font-lib/src/FontLib/Autoloader.php";
require_once "$baseDir/lib/php-svg-lib/src/autoload.php";
require_once "$baseDir/src/Autoloader.php";
Autoloader::register();
});
class Pdf class Pdf
{ {

View File

@ -1,8 +1,8 @@
GLOBIGNORE=$0; rm -rf * GLOBIGNORE=$0; rm -rf *
curl https://codeload.github.com/dompdf/dompdf/tar.gz/v0.7.0 -o dompdf-0.7.0.tar.gz curl https://codeload.github.com/dompdf/dompdf/tar.gz/v0.8.2 -o dompdf-0.8.2.tar.gz
tar xzf dompdf-0.7.0.tar.gz --strip-components 1 dompdf-0.7.0/{lib,src,autoload.inc.php,LICENSE.LGPL} tar xzf dompdf-0.8.2.tar.gz --strip-components 1 dompdf-0.8.2/{lib,src,LICENSE.LGPL}
rm dompdf-0.7.0.tar.gz rm dompdf-0.8.2.tar.gz
mv LICENSE.LGPL LICENSE mv LICENSE.LGPL LICENSE
curl https://codeload.github.com/PhenX/php-font-lib/tar.gz/0.4 -o php-font-lib-0.4.tar.gz curl https://codeload.github.com/PhenX/php-font-lib/tar.gz/0.4 -o php-font-lib-0.4.tar.gz

View File

@ -1,28 +0,0 @@
<?php
/**
* @package dompdf
* @link http://dompdf.github.com/
* @author Benj Carson <benjcarson@digitaljunkies.ca>
* @author Fabien Ménager <fabien.menager@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
*/
/**
* Dompdf autoload function
*
* If you have an existing autoload function, add a call to this function
* from your existing __autoload() implementation.
*
* @param string $class
*/
require_once __DIR__ . '/lib/html5lib/Parser.php';
require_once __DIR__ . '/lib/php-font-lib/src/FontLib/Autoloader.php';
require_once __DIR__ . '/lib/php-svg-lib/src/autoload.php';
/*
* New PHP 5.3.0 namespaced autoloader
*/
require_once __DIR__ . '/src/Autoloader.php';
Dompdf\Autoloader::register();

File diff suppressed because it is too large Load Diff

View File

@ -130,7 +130,7 @@ C 167 ; WX 600 ; N section ; B 83 -70 517 580 ;
C 164 ; WX 600 ; N currency ; B 54 49 546 517 ; C 164 ; WX 600 ; N currency ; B 54 49 546 517 ;
C 39 ; WX 600 ; N quotesingle ; B 227 277 373 562 ; C 39 ; WX 600 ; N quotesingle ; B 227 277 373 562 ;
C 147 ; WX 600 ; N quotedblleft ; B 71 277 535 562 ; C 147 ; WX 600 ; N quotedblleft ; B 71 277 535 562 ;
C 170 ; WX 600 ; N guillemotleft ; B 8 70 553 446 ; C 171 ; WX 600 ; N guillemotleft ; B 8 70 553 446 ;
C 139 ; WX 600 ; N guilsinglleft ; B 141 70 459 446 ; C 139 ; WX 600 ; N guilsinglleft ; B 141 70 459 446 ;
C 155 ; WX 600 ; N guilsinglright ; B 141 70 459 446 ; C 155 ; WX 600 ; N guilsinglright ; B 141 70 459 446 ;
C -1 ; WX 600 ; N fi ; B 12 0 593 626 ; C -1 ; WX 600 ; N fi ; B 12 0 593 626 ;

View File

@ -130,7 +130,7 @@ C 167 ; WX 600 ; N section ; B 74 -70 620 580 ;
C 164 ; WX 600 ; N currency ; B 77 49 644 517 ; C 164 ; WX 600 ; N currency ; B 77 49 644 517 ;
C 39 ; WX 600 ; N quotesingle ; B 303 277 493 562 ; C 39 ; WX 600 ; N quotesingle ; B 303 277 493 562 ;
C 147 ; WX 600 ; N quotedblleft ; B 190 277 594 562 ; C 147 ; WX 600 ; N quotedblleft ; B 190 277 594 562 ;
C 170 ; WX 600 ; N guillemotleft ; B 62 70 639 446 ; C 171 ; WX 600 ; N guillemotleft ; B 62 70 639 446 ;
C 139 ; WX 600 ; N guilsinglleft ; B 195 70 545 446 ; C 139 ; WX 600 ; N guilsinglleft ; B 195 70 545 446 ;
C 155 ; WX 600 ; N guilsinglright ; B 165 70 514 446 ; C 155 ; WX 600 ; N guilsinglright ; B 165 70 514 446 ;
C -1 ; WX 600 ; N fi ; B 12 0 644 626 ; C -1 ; WX 600 ; N fi ; B 12 0 644 626 ;

View File

@ -130,7 +130,7 @@ C 167 ; WX 600 ; N section ; B 104 -78 590 580 ;
C 164 ; WX 600 ; N currency ; B 94 58 628 506 ; C 164 ; WX 600 ; N currency ; B 94 58 628 506 ;
C 39 ; WX 600 ; N quotesingle ; B 345 328 460 562 ; C 39 ; WX 600 ; N quotesingle ; B 345 328 460 562 ;
C 147 ; WX 600 ; N quotedblleft ; B 262 328 541 562 ; C 147 ; WX 600 ; N quotedblleft ; B 262 328 541 562 ;
C 170 ; WX 600 ; N guillemotleft ; B 92 70 652 446 ; C 171 ; WX 600 ; N guillemotleft ; B 92 70 652 446 ;
C 139 ; WX 600 ; N guilsinglleft ; B 204 70 540 446 ; C 139 ; WX 600 ; N guilsinglleft ; B 204 70 540 446 ;
C 155 ; WX 600 ; N guilsinglright ; B 170 70 506 446 ; C 155 ; WX 600 ; N guilsinglright ; B 170 70 506 446 ;
C -1 ; WX 600 ; N fi ; B 3 0 619 629 ; C -1 ; WX 600 ; N fi ; B 3 0 619 629 ;

View File

@ -130,7 +130,7 @@ C 167 ; WX 600 ; N section ; B 113 -78 488 580 ;
C 164 ; WX 600 ; N currency ; B 73 58 527 506 ; C 164 ; WX 600 ; N currency ; B 73 58 527 506 ;
C 39 ; WX 600 ; N quotesingle ; B 259 328 341 562 ; C 39 ; WX 600 ; N quotesingle ; B 259 328 341 562 ;
C 147 ; WX 600 ; N quotedblleft ; B 143 328 471 562 ; C 147 ; WX 600 ; N quotedblleft ; B 143 328 471 562 ;
C 170 ; WX 600 ; N guillemotleft ; B 37 70 563 446 ; C 171 ; WX 600 ; N guillemotleft ; B 37 70 563 446 ;
C 139 ; WX 600 ; N guilsinglleft ; B 149 70 451 446 ; C 139 ; WX 600 ; N guilsinglleft ; B 149 70 451 446 ;
C 155 ; WX 600 ; N guilsinglright ; B 149 70 451 446 ; C 155 ; WX 600 ; N guilsinglright ; B 149 70 451 446 ;
C -1 ; WX 600 ; N fi ; B 3 0 597 629 ; C -1 ; WX 600 ; N fi ; B 3 0 597 629 ;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -130,7 +130,7 @@ C 167 ; WX 556 ; N section ; B 34 -184 522 727 ;
C 164 ; WX 556 ; N currency ; B -3 76 559 636 ; C 164 ; WX 556 ; N currency ; B -3 76 559 636 ;
C 39 ; WX 238 ; N quotesingle ; B 70 447 168 718 ; C 39 ; WX 238 ; N quotesingle ; B 70 447 168 718 ;
C 147 ; WX 500 ; N quotedblleft ; B 64 454 436 727 ; C 147 ; WX 500 ; N quotedblleft ; B 64 454 436 727 ;
C 170 ; WX 556 ; N guillemotleft ; B 88 76 468 484 ; C 171 ; WX 556 ; N guillemotleft ; B 88 76 468 484 ;
C 139 ; WX 333 ; N guilsinglleft ; B 83 76 250 484 ; C 139 ; WX 333 ; N guilsinglleft ; B 83 76 250 484 ;
C 155 ; WX 333 ; N guilsinglright ; B 83 76 250 484 ; C 155 ; WX 333 ; N guilsinglright ; B 83 76 250 484 ;
C -1 ; WX 611 ; N fi ; B 10 0 542 727 ; C -1 ; WX 611 ; N fi ; B 10 0 542 727 ;

View File

@ -130,7 +130,7 @@ C 167 ; WX 556 ; N section ; B 61 -184 598 727 ;
C 164 ; WX 556 ; N currency ; B 27 76 680 636 ; C 164 ; WX 556 ; N currency ; B 27 76 680 636 ;
C 39 ; WX 238 ; N quotesingle ; B 165 447 321 718 ; C 39 ; WX 238 ; N quotesingle ; B 165 447 321 718 ;
C 147 ; WX 500 ; N quotedblleft ; B 160 454 588 727 ; C 147 ; WX 500 ; N quotedblleft ; B 160 454 588 727 ;
C 170 ; WX 556 ; N guillemotleft ; B 135 76 571 484 ; C 171 ; WX 556 ; N guillemotleft ; B 135 76 571 484 ;
C 139 ; WX 333 ; N guilsinglleft ; B 130 76 353 484 ; C 139 ; WX 333 ; N guilsinglleft ; B 130 76 353 484 ;
C 155 ; WX 333 ; N guilsinglright ; B 99 76 322 484 ; C 155 ; WX 333 ; N guilsinglright ; B 99 76 322 484 ;
C -1 ; WX 611 ; N fi ; B 87 0 696 727 ; C -1 ; WX 611 ; N fi ; B 87 0 696 727 ;

View File

@ -130,7 +130,7 @@ C 167 ; WX 556 ; N section ; B 76 -191 584 737 ;
C 164 ; WX 556 ; N currency ; B 60 99 646 603 ; C 164 ; WX 556 ; N currency ; B 60 99 646 603 ;
C 39 ; WX 191 ; N quotesingle ; B 157 463 285 718 ; C 39 ; WX 191 ; N quotesingle ; B 157 463 285 718 ;
C 147 ; WX 333 ; N quotedblleft ; B 138 470 461 725 ; C 147 ; WX 333 ; N quotedblleft ; B 138 470 461 725 ;
C 170 ; WX 556 ; N guillemotleft ; B 146 108 554 446 ; C 171 ; WX 556 ; N guillemotleft ; B 146 108 554 446 ;
C 139 ; WX 333 ; N guilsinglleft ; B 137 108 340 446 ; C 139 ; WX 333 ; N guilsinglleft ; B 137 108 340 446 ;
C 155 ; WX 333 ; N guilsinglright ; B 111 108 314 446 ; C 155 ; WX 333 ; N guilsinglright ; B 111 108 314 446 ;
C -1 ; WX 500 ; N fi ; B 86 0 587 728 ; C -1 ; WX 500 ; N fi ; B 86 0 587 728 ;

View File

@ -130,7 +130,7 @@ C 167 ; WX 556 ; N section ; B 43 -191 512 737 ;
C 164 ; WX 556 ; N currency ; B 28 99 528 603 ; C 164 ; WX 556 ; N currency ; B 28 99 528 603 ;
C 39 ; WX 191 ; N quotesingle ; B 59 463 132 718 ; C 39 ; WX 191 ; N quotesingle ; B 59 463 132 718 ;
C 147 ; WX 333 ; N quotedblleft ; B 38 470 307 725 ; C 147 ; WX 333 ; N quotedblleft ; B 38 470 307 725 ;
C 170 ; WX 556 ; N guillemotleft ; B 97 108 459 446 ; C 171 ; WX 556 ; N guillemotleft ; B 97 108 459 446 ;
C 139 ; WX 333 ; N guilsinglleft ; B 88 108 245 446 ; C 139 ; WX 333 ; N guilsinglleft ; B 88 108 245 446 ;
C 155 ; WX 333 ; N guilsinglright ; B 88 108 245 446 ; C 155 ; WX 333 ; N guilsinglright ; B 88 108 245 446 ;
C -1 ; WX 500 ; N fi ; B 14 0 434 728 ; C -1 ; WX 500 ; N fi ; B 14 0 434 728 ;

View File

@ -130,7 +130,7 @@ C 167 ; WX 500 ; N section ; B 57 -132 443 691 ;
C 164 ; WX 500 ; N currency ; B -26 61 526 613 ; C 164 ; WX 500 ; N currency ; B -26 61 526 613 ;
C 39 ; WX 278 ; N quotesingle ; B 75 404 204 691 ; C 39 ; WX 278 ; N quotesingle ; B 75 404 204 691 ;
C 147 ; WX 500 ; N quotedblleft ; B 32 356 486 691 ; C 147 ; WX 500 ; N quotedblleft ; B 32 356 486 691 ;
C 170 ; WX 500 ; N guillemotleft ; B 23 36 473 415 ; C 171 ; WX 500 ; N guillemotleft ; B 23 36 473 415 ;
C 139 ; WX 333 ; N guilsinglleft ; B 51 36 305 415 ; C 139 ; WX 333 ; N guilsinglleft ; B 51 36 305 415 ;
C 155 ; WX 333 ; N guilsinglright ; B 28 36 282 415 ; C 155 ; WX 333 ; N guilsinglright ; B 28 36 282 415 ;
C -1 ; WX 556 ; N fi ; B 14 0 536 691 ; C -1 ; WX 556 ; N fi ; B 14 0 536 691 ;

View File

@ -130,7 +130,7 @@ C 167 ; WX 500 ; N section ; B 36 -143 459 685 ;
C 164 ; WX 500 ; N currency ; B -26 34 526 586 ; C 164 ; WX 500 ; N currency ; B -26 34 526 586 ;
C 39 ; WX 278 ; N quotesingle ; B 128 398 268 685 ; C 39 ; WX 278 ; N quotesingle ; B 128 398 268 685 ;
C 147 ; WX 500 ; N quotedblleft ; B 53 369 513 685 ; C 147 ; WX 500 ; N quotedblleft ; B 53 369 513 685 ;
C 170 ; WX 500 ; N guillemotleft ; B 12 32 468 415 ; C 171 ; WX 500 ; N guillemotleft ; B 12 32 468 415 ;
C 139 ; WX 333 ; N guilsinglleft ; B 32 32 303 415 ; C 139 ; WX 333 ; N guilsinglleft ; B 32 32 303 415 ;
C 155 ; WX 333 ; N guilsinglright ; B 10 32 281 415 ; C 155 ; WX 333 ; N guilsinglright ; B 10 32 281 415 ;
C -1 ; WX 556 ; N fi ; B -188 -205 514 703 ; C -1 ; WX 556 ; N fi ; B -188 -205 514 703 ;

View File

@ -130,7 +130,7 @@ C 167 ; WX 500 ; N section ; B 53 -162 461 666 ;
C 164 ; WX 500 ; N currency ; B -22 53 522 597 ; C 164 ; WX 500 ; N currency ; B -22 53 522 597 ;
C 39 ; WX 214 ; N quotesingle ; B 132 421 241 666 ; C 39 ; WX 214 ; N quotesingle ; B 132 421 241 666 ;
C 147 ; WX 556 ; N quotedblleft ; B 166 436 514 666 ; C 147 ; WX 556 ; N quotedblleft ; B 166 436 514 666 ;
C 170 ; WX 500 ; N guillemotleft ; B 53 37 445 403 ; C 171 ; WX 500 ; N guillemotleft ; B 53 37 445 403 ;
C 139 ; WX 333 ; N guilsinglleft ; B 51 37 281 403 ; C 139 ; WX 333 ; N guilsinglleft ; B 51 37 281 403 ;
C 155 ; WX 333 ; N guilsinglright ; B 52 37 282 403 ; C 155 ; WX 333 ; N guilsinglright ; B 52 37 282 403 ;
C -1 ; WX 500 ; N fi ; B -141 -207 481 681 ; C -1 ; WX 500 ; N fi ; B -141 -207 481 681 ;

View File

@ -130,7 +130,7 @@ C 167 ; WX 500 ; N section ; B 70 -148 426 676 ;
C 164 ; WX 500 ; N currency ; B -22 58 522 602 ; C 164 ; WX 500 ; N currency ; B -22 58 522 602 ;
C 39 ; WX 180 ; N quotesingle ; B 48 431 133 676 ; C 39 ; WX 180 ; N quotesingle ; B 48 431 133 676 ;
C 147 ; WX 444 ; N quotedblleft ; B 43 433 414 676 ; C 147 ; WX 444 ; N quotedblleft ; B 43 433 414 676 ;
C 170 ; WX 500 ; N guillemotleft ; B 42 33 456 416 ; C 171 ; WX 500 ; N guillemotleft ; B 42 33 456 416 ;
C 139 ; WX 333 ; N guilsinglleft ; B 63 33 285 416 ; C 139 ; WX 333 ; N guilsinglleft ; B 63 33 285 416 ;
C 155 ; WX 333 ; N guilsinglright ; B 48 33 270 416 ; C 155 ; WX 333 ; N guilsinglright ; B 48 33 270 416 ;
C -1 ; WX 556 ; N fi ; B 31 0 521 683 ; C -1 ; WX 556 ; N fi ; B 31 0 521 683 ;

View File

@ -92,5 +92,4 @@ return array(
'italic' => $distFontDir . 'DejaVuSerif-Italic', 'italic' => $distFontDir . 'DejaVuSerif-Italic',
'normal' => $distFontDir . 'DejaVuSerif' 'normal' => $distFontDir . 'DejaVuSerif'
) )
) );
?>

View File

@ -56,8 +56,11 @@ class HTML5_Data
* reference. * reference.
*/ */
public static function getRealCodepoint($ref) { public static function getRealCodepoint($ref) {
if (!isset(self::$realCodepointTable[$ref])) return false; if (!isset(self::$realCodepointTable[$ref])) {
else return self::$realCodepointTable[$ref]; return false;
} else {
return self::$realCodepointTable[$ref];
}
} }
public static function getNamedCharacterReferences() { public static function getNamedCharacterReferences() {
@ -82,7 +85,7 @@ class HTML5_Data
return "\xEF\xBF\xBD"; return "\xEF\xBF\xBD";
}*/ }*/
$x = $y = $z = $w = 0; $y = $z = $w = 0;
if ($code < 0x80) { if ($code < 0x80) {
// regular ASCII character // regular ASCII character
$x = $code; $x = $code;
@ -93,7 +96,7 @@ class HTML5_Data
$y = (($code & 0x7FF) >> 6) | 0xC0; $y = (($code & 0x7FF) >> 6) | 0xC0;
} else { } else {
$y = (($code & 0xFC0) >> 6) | 0x80; $y = (($code & 0xFC0) >> 6) | 0x80;
if($code < 0x10000) { if ($code < 0x10000) {
$z = (($code >> 12) & 0x0F) | 0xE0; $z = (($code >> 12) & 0x0F) | 0xE0;
} else { } else {
$z = (($code >> 12) & 0x3F) | 0x80; $z = (($code >> 12) & 0x3F) | 0x80;
@ -103,9 +106,15 @@ class HTML5_Data
} }
// set up the actual character // set up the actual character
$ret = ''; $ret = '';
if($w) $ret .= chr($w); if ($w) {
if($z) $ret .= chr($z); $ret .= chr($w);
if($y) $ret .= chr($y); }
if ($z) {
$ret .= chr($z);
}
if ($y) {
$ret .= chr($y);
}
$ret .= chr($x); $ret .= chr($x);
return $ret; return $ret;

View File

@ -51,7 +51,8 @@ class HTML5_InputStream {
public $errors = array(); public $errors = array();
/** /**
* @param $data Data to parse * @param $data | Data to parse
* @throws Exception
*/ */
public function __construct($data) { public function __construct($data) {
@ -76,7 +77,7 @@ class HTML5_InputStream {
$data = @iconv('UTF-8', 'UTF-8//IGNORE', $data); $data = @iconv('UTF-8', 'UTF-8//IGNORE', $data);
} else { } else {
// we can make a conforming native implementation // we can make a conforming native implementation
throw new Exception('Not implemented, please install mbstring or iconv'); throw new Exception('Not implemented, please install iconv');
} }
/* One leading U+FEFF BYTE ORDER MARK character must be /* One leading U+FEFF BYTE ORDER MARK character must be
@ -161,10 +162,12 @@ class HTML5_InputStream {
/** /**
* Returns the current line that the tokenizer is at. * Returns the current line that the tokenizer is at.
*
* @return int
*/ */
public function getCurrentLine() { public function getCurrentLine() {
// Check the string isn't empty // Check the string isn't empty
if($this->EOF) { if ($this->EOF) {
// Add one to $this->char because we want the number for the next // Add one to $this->char because we want the number for the next
// byte to be processed. // byte to be processed.
return substr_count($this->data, "\n", 0, min($this->char, $this->EOF)) + 1; return substr_count($this->data, "\n", 0, min($this->char, $this->EOF)) + 1;
@ -176,6 +179,8 @@ class HTML5_InputStream {
/** /**
* Returns the current column of the current line that the tokenizer is at. * Returns the current column of the current line that the tokenizer is at.
*
* @return int
*/ */
public function getColumnOffset() { public function getColumnOffset() {
// strrpos is weird, and the offset needs to be negative for what we // strrpos is weird, and the offset needs to be negative for what we
@ -187,18 +192,18 @@ class HTML5_InputStream {
// However, for here we want the length up until the next byte to be // However, for here we want the length up until the next byte to be
// processed, so add one to the current byte ($this->char). // processed, so add one to the current byte ($this->char).
if($lastLine !== false) { if ($lastLine !== false) {
$findLengthOf = substr($this->data, $lastLine + 1, $this->char - 1 - $lastLine); $findLengthOf = substr($this->data, $lastLine + 1, $this->char - 1 - $lastLine);
} else { } else {
$findLengthOf = substr($this->data, 0, $this->char); $findLengthOf = substr($this->data, 0, $this->char);
} }
// Get the length for the string we need. // Get the length for the string we need.
if(extension_loaded('iconv')) { if (extension_loaded('iconv')) {
return iconv_strlen($findLengthOf, 'utf-8'); return iconv_strlen($findLengthOf, 'utf-8');
} elseif(extension_loaded('mbstring')) { } elseif (extension_loaded('mbstring')) {
return mb_strlen($findLengthOf, 'utf-8'); return mb_strlen($findLengthOf, 'utf-8');
} elseif(extension_loaded('xml')) { } elseif (extension_loaded('xml')) {
return strlen(utf8_decode($findLengthOf)); return strlen(utf8_decode($findLengthOf));
} else { } else {
$count = count_chars($findLengthOf); $count = count_chars($findLengthOf);
@ -212,6 +217,8 @@ class HTML5_InputStream {
/** /**
* Retrieve the currently consume character. * Retrieve the currently consume character.
* @note This performs bounds checking * @note This performs bounds checking
*
* @return bool|string
*/ */
public function char() { public function char() {
return ($this->char++ < $this->EOF) return ($this->char++ < $this->EOF)
@ -222,9 +229,11 @@ class HTML5_InputStream {
/** /**
* Get all characters until EOF. * Get all characters until EOF.
* @note This performs bounds checking * @note This performs bounds checking
*
* @return string|bool
*/ */
public function remainingChars() { public function remainingChars() {
if($this->char < $this->EOF) { if ($this->char < $this->EOF) {
$data = substr($this->data, $this->char); $data = substr($this->data, $this->char);
$this->char = $this->EOF; $this->char = $this->EOF;
return $data; return $data;
@ -236,7 +245,10 @@ class HTML5_InputStream {
/** /**
* Matches as far as possible until we reach a certain set of bytes * Matches as far as possible until we reach a certain set of bytes
* and returns the matched substring. * and returns the matched substring.
* @param $bytes Bytes to match. *
* @param $bytes | Bytes to match.
* @param null $max
* @return bool|string
*/ */
public function charsUntil($bytes, $max = null) { public function charsUntil($bytes, $max = null) {
if ($this->char < $this->EOF) { if ($this->char < $this->EOF) {
@ -256,7 +268,10 @@ class HTML5_InputStream {
/** /**
* Matches as far as possible with a certain set of bytes * Matches as far as possible with a certain set of bytes
* and returns the matched substring. * and returns the matched substring.
* @param $bytes Bytes to match. *
* @param $bytes | Bytes to match.
* @param null $max
* @return bool|string
*/ */
public function charsWhile($bytes, $max = null) { public function charsWhile($bytes, $max = null) {
if ($this->char < $this->EOF) { if ($this->char < $this->EOF) {

View File

@ -12,21 +12,22 @@ class HTML5_Parser
{ {
/** /**
* Parses a full HTML document. * Parses a full HTML document.
* @param $text HTML text to parse * @param $text | HTML text to parse
* @param $builder Custom builder implementation * @param $builder | Custom builder implementation
* @return Parsed HTML as DOMDocument * @return DOMDocument|DOMNodeList Parsed HTML as DOMDocument
*/ */
static public function parse($text, $builder = null) { static public function parse($text, $builder = null) {
$tokenizer = new HTML5_Tokenizer($text, $builder); $tokenizer = new HTML5_Tokenizer($text, $builder);
$tokenizer->parse(); $tokenizer->parse();
return $tokenizer->save(); return $tokenizer->save();
} }
/** /**
* Parses an HTML fragment. * Parses an HTML fragment.
* @param $text HTML text to parse * @param $text | HTML text to parse
* @param $context String name of context element to pretend parsing is in. * @param $context String name of context element to pretend parsing is in.
* @param $builder Custom builder implementation * @param $builder | Custom builder implementation
* @return Parsed HTML as DOMDocument * @return DOMDocument|DOMNodeList Parsed HTML as DOMDocument
*/ */
static public function parseFragment($text, $context = null, $builder = null) { static public function parseFragment($text, $context = null, $builder = null) {
$tokenizer = new HTML5_Tokenizer($text, $builder); $tokenizer = new HTML5_Tokenizer($text, $builder);

View File

@ -35,16 +35,22 @@ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
class HTML5_Tokenizer { class HTML5_Tokenizer {
/** /**
* @var HTML5_InputStream
*
* Points to an InputStream object. * Points to an InputStream object.
*/ */
protected $stream; protected $stream;
/** /**
* @var HTML5_TreeBuilder
*
* Tree builder that the tokenizer emits token to. * Tree builder that the tokenizer emits token to.
*/ */
private $tree; private $tree;
/** /**
* @var int
*
* Current content model we are parsing as. * Current content model we are parsing as.
*/ */
protected $content_model; protected $content_model;
@ -82,15 +88,22 @@ class HTML5_Tokenizer {
const WHITESPACE = "\t\n\x0c "; const WHITESPACE = "\t\n\x0c ";
/** /**
* @param $data Data to parse * @param $data | Data to parse
* @param HTML5_TreeBuilder|null $builder
*/ */
public function __construct($data, $builder = null) { public function __construct($data, $builder = null) {
$this->stream = new HTML5_InputStream($data); $this->stream = new HTML5_InputStream($data);
if (!$builder) $this->tree = new HTML5_TreeBuilder; if (!$builder) {
else $this->tree = $builder; $this->tree = new HTML5_TreeBuilder;
} else {
$this->tree = $builder;
}
$this->content_model = self::PCDATA; $this->content_model = self::PCDATA;
} }
/**
* @param null $context
*/
public function parseFragment($context = null) { public function parseFragment($context = null) {
$this->tree->setupContext($context); $this->tree->setupContext($context);
if ($this->tree->content_model) { if ($this->tree->content_model) {
@ -118,7 +131,7 @@ class HTML5_Tokenizer {
$escape = false; $escape = false;
//echo "\n\n"; //echo "\n\n";
while($state !== null) { while($state !== null) {
/*echo $state . ' '; /*echo $state . ' ';
switch ($this->content_model) { switch ($this->content_model) {
case self::PCDATA: echo 'PCDATA'; break; case self::PCDATA: echo 'PCDATA'; break;
@ -128,17 +141,19 @@ class HTML5_Tokenizer {
} }
if ($escape) echo " escape"; if ($escape) echo " escape";
echo "\n";*/ echo "\n";*/
switch($state) { switch($state) {
case 'data': case 'data':
/* Consume the next input character */ /* Consume the next input character */
$char = $this->stream->char(); $char = $this->stream->char();
$lastFourChars .= $char; $lastFourChars .= $char;
if (strlen($lastFourChars) > 4) $lastFourChars = substr($lastFourChars, -4); if (strlen($lastFourChars) > 4) {
$lastFourChars = substr($lastFourChars, -4);
}
// see below for meaning // see below for meaning
$hyp_cond = $hyp_cond =
!$escape && !$escape &&
( (
$this->content_model === self::RCDATA || $this->content_model === self::RCDATA ||
@ -159,14 +174,14 @@ class HTML5_Tokenizer {
) && ) &&
!$escape !$escape
); );
$gt_cond = $gt_cond =
$escape && $escape &&
( (
$this->content_model === self::RCDATA || $this->content_model === self::RCDATA ||
$this->content_model === self::CDATA $this->content_model === self::CDATA
); );
if($char === '&' && $amp_cond) { if ($char === '&' && $amp_cond === true) {
/* U+0026 AMPERSAND (&) /* U+0026 AMPERSAND (&)
When the content model flag is set to one of the PCDATA or RCDATA When the content model flag is set to one of the PCDATA or RCDATA
states and the escape flag is false: switch to the states and the escape flag is false: switch to the
@ -174,9 +189,9 @@ class HTML5_Tokenizer {
the "anything else" entry below. */ the "anything else" entry below. */
$state = 'character reference data'; $state = 'character reference data';
} elseif( } elseif (
$char === '-' && $char === '-' &&
$hyp_cond && $hyp_cond === true &&
$lastFourChars === '<!--' $lastFourChars === '<!--'
) { ) {
/* /*
@ -198,7 +213,7 @@ class HTML5_Tokenizer {
// We do the "any case" part as part of "anything else". // We do the "any case" part as part of "anything else".
/* U+003C LESS-THAN SIGN (<) */ /* U+003C LESS-THAN SIGN (<) */
} elseif($char === '<' && $lt_cond) { } elseif ($char === '<' && $lt_cond === true) {
/* When the content model flag is set to the PCDATA state: switch /* When the content model flag is set to the PCDATA state: switch
to the tag open state. to the tag open state.
@ -210,9 +225,9 @@ class HTML5_Tokenizer {
$state = 'tag open'; $state = 'tag open';
/* U+003E GREATER-THAN SIGN (>) */ /* U+003E GREATER-THAN SIGN (>) */
} elseif( } elseif (
$char === '>' && $char === '>' &&
$gt_cond && $gt_cond === true &&
substr($lastFourChars, 1) === '-->' substr($lastFourChars, 1) === '-->'
) { ) {
/* If the content model flag is set to either the RCDATA state or /* If the content model flag is set to either the RCDATA state or
@ -230,15 +245,15 @@ class HTML5_Tokenizer {
)); ));
// We do the "any case" part as part of "anything else". // We do the "any case" part as part of "anything else".
} elseif($char === false) { } elseif ($char === false) {
/* EOF /* EOF
Emit an end-of-file token. */ Emit an end-of-file token. */
$state = null; $state = null;
$this->tree->emitToken(array( $this->tree->emitToken(array(
'type' => self::EOF 'type' => self::EOF
)); ));
} elseif($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') { } elseif ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
// Directly after emitting a token you switch back to the "data // Directly after emitting a token you switch back to the "data
// state". At that point spaceCharacters are important so they are // state". At that point spaceCharacters are important so they are
// emitted separately. // emitted separately.
@ -248,19 +263,28 @@ class HTML5_Tokenizer {
'data' => $char . $chars 'data' => $char . $chars
)); ));
$lastFourChars .= $chars; $lastFourChars .= $chars;
if (strlen($lastFourChars) > 4) $lastFourChars = substr($lastFourChars, -4); if (strlen($lastFourChars) > 4) {
$lastFourChars = substr($lastFourChars, -4);
}
} else { } else {
/* Anything else /* Anything else
THIS IS AN OPTIMIZATION: Get as many character that THIS IS AN OPTIMIZATION: Get as many character that
otherwise would also be treated as a character token and emit it otherwise would also be treated as a character token and emit it
as a single character token. Stay in the data state. */ as a single character token. Stay in the data state. */
$mask = ''; $mask = '';
if ($hyp_cond) $mask .= '-'; if ($hyp_cond === true) {
if ($amp_cond) $mask .= '&'; $mask .= '-';
if ($lt_cond) $mask .= '<'; }
if ($gt_cond) $mask .= '>'; if ($amp_cond === true) {
$mask .= '&';
}
if ($lt_cond === true) {
$mask .= '<';
}
if ($gt_cond === true) {
$mask .= '>';
}
if ($mask === '') { if ($mask === '') {
$chars = $this->stream->remainingChars(); $chars = $this->stream->remainingChars();
@ -274,7 +298,9 @@ class HTML5_Tokenizer {
)); ));
$lastFourChars .= $chars; $lastFourChars .= $chars;
if (strlen($lastFourChars) > 4) $lastFourChars = substr($lastFourChars, -4); if (strlen($lastFourChars) > 4) {
$lastFourChars = substr($lastFourChars, -4);
}
$state = 'data'; $state = 'data';
} }
@ -304,7 +330,7 @@ class HTML5_Tokenizer {
case 'tag open': case 'tag open':
$char = $this->stream->char(); $char = $this->stream->char();
switch($this->content_model) { switch ($this->content_model) {
case self::RCDATA: case self::RCDATA:
case self::CDATA: case self::CDATA:
/* Consume the next input character. If it is a /* Consume the next input character. If it is a
@ -314,9 +340,8 @@ class HTML5_Tokenizer {
character in the data state. */ character in the data state. */
// We consumed above. // We consumed above.
if($char === '/') { if ($char === '/') {
$state = 'close tag open'; $state = 'close tag open';
} else { } else {
$this->emitToken(array( $this->emitToken(array(
'type' => self::CHARACTER, 'type' => self::CHARACTER,
@ -334,17 +359,17 @@ class HTML5_Tokenizer {
Consume the next input character: */ Consume the next input character: */
// We consumed above. // We consumed above.
if($char === '!') { if ($char === '!') {
/* U+0021 EXCLAMATION MARK (!) /* U+0021 EXCLAMATION MARK (!)
Switch to the markup declaration open state. */ Switch to the markup declaration open state. */
$state = 'markup declaration open'; $state = 'markup declaration open';
} elseif($char === '/') { } elseif ($char === '/') {
/* U+002F SOLIDUS (/) /* U+002F SOLIDUS (/)
Switch to the close tag open state. */ Switch to the close tag open state. */
$state = 'close tag open'; $state = 'close tag open';
} elseif('A' <= $char && $char <= 'Z') { } elseif ('A' <= $char && $char <= 'Z') {
/* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z /* U+0041 LATIN LETTER A through to U+005A LATIN LETTER Z
Create a new start tag token, set its tag name to the lowercase Create a new start tag token, set its tag name to the lowercase
version of the input character (add 0x0020 to the character's code version of the input character (add 0x0020 to the character's code
@ -358,7 +383,7 @@ class HTML5_Tokenizer {
$state = 'tag name'; $state = 'tag name';
} elseif('a' <= $char && $char <= 'z') { } elseif ('a' <= $char && $char <= 'z') {
/* U+0061 LATIN SMALL LETTER A through to U+007A LATIN SMALL LETTER Z /* U+0061 LATIN SMALL LETTER A through to U+007A LATIN SMALL LETTER Z
Create a new start tag token, set its tag name to the input Create a new start tag token, set its tag name to the input
character, then switch to the tag name state. (Don't emit character, then switch to the tag name state. (Don't emit
@ -372,7 +397,7 @@ class HTML5_Tokenizer {
$state = 'tag name'; $state = 'tag name';
} elseif($char === '>') { } elseif ($char === '>') {
/* U+003E GREATER-THAN SIGN (>) /* U+003E GREATER-THAN SIGN (>)
Parse error. Emit a U+003C LESS-THAN SIGN character token and a Parse error. Emit a U+003C LESS-THAN SIGN character token and a
U+003E GREATER-THAN SIGN character token. Switch to the data state. */ U+003E GREATER-THAN SIGN character token. Switch to the data state. */
@ -387,7 +412,7 @@ class HTML5_Tokenizer {
$state = 'data'; $state = 'data';
} elseif($char === '?') { } elseif ($char === '?') {
/* U+003F QUESTION MARK (?) /* U+003F QUESTION MARK (?)
Parse error. Switch to the bogus comment state. */ Parse error. Switch to the bogus comment state. */
$this->emitToken(array( $this->emitToken(array(
@ -508,7 +533,7 @@ class HTML5_Tokenizer {
$state = 'tag name'; $state = 'tag name';
} elseif($char === '>') { } elseif ($char === '>') {
/* U+003E GREATER-THAN SIGN (>) /* U+003E GREATER-THAN SIGN (>)
Parse error. Switch to the data state. */ Parse error. Switch to the data state. */
$this->emitToken(array( $this->emitToken(array(
@ -517,7 +542,7 @@ class HTML5_Tokenizer {
)); ));
$state = 'data'; $state = 'data';
} elseif($char === false) { } elseif ($char === false) {
/* EOF /* EOF
Parse error. Emit a U+003C LESS-THAN SIGN character token and a U+002F Parse error. Emit a U+003C LESS-THAN SIGN character token and a U+002F
SOLIDUS character token. Reconsume the EOF character in the data state. */ SOLIDUS character token. Reconsume the EOF character in the data state. */
@ -552,7 +577,7 @@ class HTML5_Tokenizer {
/* Consume the next input character: */ /* Consume the next input character: */
$char = $this->stream->char(); $char = $this->stream->char();
if($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') { if ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
/* U+0009 CHARACTER TABULATION /* U+0009 CHARACTER TABULATION
U+000A LINE FEED (LF) U+000A LINE FEED (LF)
U+000C FORM FEED (FF) U+000C FORM FEED (FF)
@ -560,18 +585,18 @@ class HTML5_Tokenizer {
Switch to the before attribute name state. */ Switch to the before attribute name state. */
$state = 'before attribute name'; $state = 'before attribute name';
} elseif($char === '/') { } elseif ($char === '/') {
/* U+002F SOLIDUS (/) /* U+002F SOLIDUS (/)
Switch to the self-closing start tag state. */ Switch to the self-closing start tag state. */
$state = 'self-closing start tag'; $state = 'self-closing start tag';
} elseif($char === '>') { } elseif ($char === '>') {
/* U+003E GREATER-THAN SIGN (>) /* U+003E GREATER-THAN SIGN (>)
Emit the current tag token. Switch to the data state. */ Emit the current tag token. Switch to the data state. */
$this->emitToken($this->token); $this->emitToken($this->token);
$state = 'data'; $state = 'data';
} elseif('A' <= $char && $char <= 'Z') { } elseif ('A' <= $char && $char <= 'Z') {
/* U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z /* U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z
Append the lowercase version of the current input Append the lowercase version of the current input
character (add 0x0020 to the character's code point) to character (add 0x0020 to the character's code point) to
@ -581,7 +606,7 @@ class HTML5_Tokenizer {
$this->token['name'] .= strtolower($char . $chars); $this->token['name'] .= strtolower($char . $chars);
$state = 'tag name'; $state = 'tag name';
} elseif($char === false) { } elseif ($char === false) {
/* EOF /* EOF
Parse error. Reconsume the EOF character in the data state. */ Parse error. Reconsume the EOF character in the data state. */
$this->emitToken(array( $this->emitToken(array(
@ -608,7 +633,7 @@ class HTML5_Tokenizer {
$char = $this->stream->char(); $char = $this->stream->char();
// this conditional is optimized, check bottom // this conditional is optimized, check bottom
if($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') { if ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
/* U+0009 CHARACTER TABULATION /* U+0009 CHARACTER TABULATION
U+000A LINE FEED (LF) U+000A LINE FEED (LF)
U+000C FORM FEED (FF) U+000C FORM FEED (FF)
@ -616,18 +641,18 @@ class HTML5_Tokenizer {
Stay in the before attribute name state. */ Stay in the before attribute name state. */
$state = 'before attribute name'; $state = 'before attribute name';
} elseif($char === '/') { } elseif ($char === '/') {
/* U+002F SOLIDUS (/) /* U+002F SOLIDUS (/)
Switch to the self-closing start tag state. */ Switch to the self-closing start tag state. */
$state = 'self-closing start tag'; $state = 'self-closing start tag';
} elseif($char === '>') { } elseif ($char === '>') {
/* U+003E GREATER-THAN SIGN (>) /* U+003E GREATER-THAN SIGN (>)
Emit the current tag token. Switch to the data state. */ Emit the current tag token. Switch to the data state. */
$this->emitToken($this->token); $this->emitToken($this->token);
$state = 'data'; $state = 'data';
} elseif('A' <= $char && $char <= 'Z') { } elseif ('A' <= $char && $char <= 'Z') {
/* U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z /* U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z
Start a new attribute in the current tag token. Set that Start a new attribute in the current tag token. Set that
attribute's name to the lowercase version of the current attribute's name to the lowercase version of the current
@ -641,7 +666,7 @@ class HTML5_Tokenizer {
$state = 'attribute name'; $state = 'attribute name';
} elseif($char === false) { } elseif ($char === false) {
/* EOF /* EOF
Parse error. Reconsume the EOF character in the data state. */ Parse error. Reconsume the EOF character in the data state. */
$this->emitToken(array( $this->emitToken(array(
@ -659,7 +684,7 @@ class HTML5_Tokenizer {
U+003D EQUALS SIGN (=) U+003D EQUALS SIGN (=)
Parse error. Treat it as per the "anything else" entry Parse error. Treat it as per the "anything else" entry
below. */ below. */
if($char === '"' || $char === "'" || $char === '<' || $char === '=') { if ($char === '"' || $char === "'" || $char === '<' || $char === '=') {
$this->emitToken(array( $this->emitToken(array(
'type' => self::PARSEERROR, 'type' => self::PARSEERROR,
'data' => 'invalid-character-in-attribute-name' 'data' => 'invalid-character-in-attribute-name'
@ -684,7 +709,7 @@ class HTML5_Tokenizer {
$char = $this->stream->char(); $char = $this->stream->char();
// this conditional is optimized, check bottom // this conditional is optimized, check bottom
if($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') { if ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
/* U+0009 CHARACTER TABULATION /* U+0009 CHARACTER TABULATION
U+000A LINE FEED (LF) U+000A LINE FEED (LF)
U+000C FORM FEED (FF) U+000C FORM FEED (FF)
@ -692,23 +717,23 @@ class HTML5_Tokenizer {
Switch to the after attribute name state. */ Switch to the after attribute name state. */
$state = 'after attribute name'; $state = 'after attribute name';
} elseif($char === '/') { } elseif ($char === '/') {
/* U+002F SOLIDUS (/) /* U+002F SOLIDUS (/)
Switch to the self-closing start tag state. */ Switch to the self-closing start tag state. */
$state = 'self-closing start tag'; $state = 'self-closing start tag';
} elseif($char === '=') { } elseif ($char === '=') {
/* U+003D EQUALS SIGN (=) /* U+003D EQUALS SIGN (=)
Switch to the before attribute value state. */ Switch to the before attribute value state. */
$state = 'before attribute value'; $state = 'before attribute value';
} elseif($char === '>') { } elseif ($char === '>') {
/* U+003E GREATER-THAN SIGN (>) /* U+003E GREATER-THAN SIGN (>)
Emit the current tag token. Switch to the data state. */ Emit the current tag token. Switch to the data state. */
$this->emitToken($this->token); $this->emitToken($this->token);
$state = 'data'; $state = 'data';
} elseif('A' <= $char && $char <= 'Z') { } elseif ('A' <= $char && $char <= 'Z') {
/* U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z /* U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z
Append the lowercase version of the current input Append the lowercase version of the current input
character (add 0x0020 to the character's code point) to character (add 0x0020 to the character's code point) to
@ -721,7 +746,7 @@ class HTML5_Tokenizer {
$state = 'attribute name'; $state = 'attribute name';
} elseif($char === false) { } elseif ($char === false) {
/* EOF /* EOF
Parse error. Reconsume the EOF character in the data state. */ Parse error. Reconsume the EOF character in the data state. */
$this->emitToken(array( $this->emitToken(array(
@ -738,7 +763,7 @@ class HTML5_Tokenizer {
U+003C LESS-THAN SIGN (<) U+003C LESS-THAN SIGN (<)
Parse error. Treat it as per the "anything else" Parse error. Treat it as per the "anything else"
entry below. */ entry below. */
if($char === '"' || $char === "'" || $char === '<') { if ($char === '"' || $char === "'" || $char === '<') {
$this->emitToken(array( $this->emitToken(array(
'type' => self::PARSEERROR, 'type' => self::PARSEERROR,
'data' => 'invalid-character-in-attribute-name' 'data' => 'invalid-character-in-attribute-name'
@ -771,7 +796,7 @@ class HTML5_Tokenizer {
$char = $this->stream->char(); $char = $this->stream->char();
// this is an optimized conditional, check the bottom // this is an optimized conditional, check the bottom
if($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') { if ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
/* U+0009 CHARACTER TABULATION /* U+0009 CHARACTER TABULATION
U+000A LINE FEED (LF) U+000A LINE FEED (LF)
U+000C FORM FEED (FF) U+000C FORM FEED (FF)
@ -779,23 +804,23 @@ class HTML5_Tokenizer {
Stay in the after attribute name state. */ Stay in the after attribute name state. */
$state = 'after attribute name'; $state = 'after attribute name';
} elseif($char === '/') { } elseif ($char === '/') {
/* U+002F SOLIDUS (/) /* U+002F SOLIDUS (/)
Switch to the self-closing start tag state. */ Switch to the self-closing start tag state. */
$state = 'self-closing start tag'; $state = 'self-closing start tag';
} elseif($char === '=') { } elseif ($char === '=') {
/* U+003D EQUALS SIGN (=) /* U+003D EQUALS SIGN (=)
Switch to the before attribute value state. */ Switch to the before attribute value state. */
$state = 'before attribute value'; $state = 'before attribute value';
} elseif($char === '>') { } elseif ($char === '>') {
/* U+003E GREATER-THAN SIGN (>) /* U+003E GREATER-THAN SIGN (>)
Emit the current tag token. Switch to the data state. */ Emit the current tag token. Switch to the data state. */
$this->emitToken($this->token); $this->emitToken($this->token);
$state = 'data'; $state = 'data';
} elseif('A' <= $char && $char <= 'Z') { } elseif ('A' <= $char && $char <= 'Z') {
/* U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z /* U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z
Start a new attribute in the current tag token. Set that Start a new attribute in the current tag token. Set that
attribute's name to the lowercase version of the current attribute's name to the lowercase version of the current
@ -809,7 +834,7 @@ class HTML5_Tokenizer {
$state = 'attribute name'; $state = 'attribute name';
} elseif($char === false) { } elseif ($char === false) {
/* EOF /* EOF
Parse error. Reconsume the EOF character in the data state. */ Parse error. Reconsume the EOF character in the data state. */
$this->emitToken(array( $this->emitToken(array(
@ -826,7 +851,7 @@ class HTML5_Tokenizer {
U+003C LESS-THAN SIGN(<) U+003C LESS-THAN SIGN(<)
Parse error. Treat it as per the "anything else" Parse error. Treat it as per the "anything else"
entry below. */ entry below. */
if($char === '"' || $char === "'" || $char === "<") { if ($char === '"' || $char === "'" || $char === "<") {
$this->emitToken(array( $this->emitToken(array(
'type' => self::PARSEERROR, 'type' => self::PARSEERROR,
'data' => 'invalid-character-after-attribute-name' 'data' => 'invalid-character-after-attribute-name'
@ -851,7 +876,7 @@ class HTML5_Tokenizer {
$char = $this->stream->char(); $char = $this->stream->char();
// this is an optimized conditional // this is an optimized conditional
if($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') { if ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
/* U+0009 CHARACTER TABULATION /* U+0009 CHARACTER TABULATION
U+000A LINE FEED (LF) U+000A LINE FEED (LF)
U+000C FORM FEED (FF) U+000C FORM FEED (FF)
@ -859,24 +884,24 @@ class HTML5_Tokenizer {
Stay in the before attribute value state. */ Stay in the before attribute value state. */
$state = 'before attribute value'; $state = 'before attribute value';
} elseif($char === '"') { } elseif ($char === '"') {
/* U+0022 QUOTATION MARK (") /* U+0022 QUOTATION MARK (")
Switch to the attribute value (double-quoted) state. */ Switch to the attribute value (double-quoted) state. */
$state = 'attribute value (double-quoted)'; $state = 'attribute value (double-quoted)';
} elseif($char === '&') { } elseif ($char === '&') {
/* U+0026 AMPERSAND (&) /* U+0026 AMPERSAND (&)
Switch to the attribute value (unquoted) state and reconsume Switch to the attribute value (unquoted) state and reconsume
this input character. */ this input character. */
$this->stream->unget(); $this->stream->unget();
$state = 'attribute value (unquoted)'; $state = 'attribute value (unquoted)';
} elseif($char === '\'') { } elseif ($char === '\'') {
/* U+0027 APOSTROPHE (') /* U+0027 APOSTROPHE (')
Switch to the attribute value (single-quoted) state. */ Switch to the attribute value (single-quoted) state. */
$state = 'attribute value (single-quoted)'; $state = 'attribute value (single-quoted)';
} elseif($char === '>') { } elseif ($char === '>') {
/* U+003E GREATER-THAN SIGN (>) /* U+003E GREATER-THAN SIGN (>)
Parse error. Emit the current tag token. Switch to the data state. */ Parse error. Emit the current tag token. Switch to the data state. */
$this->emitToken(array( $this->emitToken(array(
@ -886,7 +911,7 @@ class HTML5_Tokenizer {
$this->emitToken($this->token); $this->emitToken($this->token);
$state = 'data'; $state = 'data';
} elseif($char === false) { } elseif ($char === false) {
/* EOF /* EOF
Parse error. Reconsume the EOF character in the data state. */ Parse error. Reconsume the EOF character in the data state. */
$this->emitToken(array( $this->emitToken(array(
@ -900,7 +925,7 @@ class HTML5_Tokenizer {
/* U+003D EQUALS SIGN (=) /* U+003D EQUALS SIGN (=)
* U+003C LESS-THAN SIGN (<) * U+003C LESS-THAN SIGN (<)
Parse error. Treat it as per the "anything else" entry below. */ Parse error. Treat it as per the "anything else" entry below. */
if($char === '=' || $char === '<') { if ($char === '=' || $char === '<') {
$this->emitToken(array( $this->emitToken(array(
'type' => self::PARSEERROR, 'type' => self::PARSEERROR,
'data' => 'equals-in-unquoted-attribute-value' 'data' => 'equals-in-unquoted-attribute-value'
@ -921,19 +946,19 @@ class HTML5_Tokenizer {
// Consume the next input character: // Consume the next input character:
$char = $this->stream->char(); $char = $this->stream->char();
if($char === '"') { if ($char === '"') {
/* U+0022 QUOTATION MARK (") /* U+0022 QUOTATION MARK (")
Switch to the after attribute value (quoted) state. */ Switch to the after attribute value (quoted) state. */
$state = 'after attribute value (quoted)'; $state = 'after attribute value (quoted)';
} elseif($char === '&') { } elseif ($char === '&') {
/* U+0026 AMPERSAND (&) /* U+0026 AMPERSAND (&)
Switch to the character reference in attribute value Switch to the character reference in attribute value
state, with the additional allowed character state, with the additional allowed character
being U+0022 QUOTATION MARK ("). */ being U+0022 QUOTATION MARK ("). */
$this->characterReferenceInAttributeValue('"'); $this->characterReferenceInAttributeValue('"');
} elseif($char === false) { } elseif ($char === false) {
/* EOF /* EOF
Parse error. Reconsume the EOF character in the data state. */ Parse error. Reconsume the EOF character in the data state. */
$this->emitToken(array( $this->emitToken(array(
@ -961,17 +986,17 @@ class HTML5_Tokenizer {
// Consume the next input character: // Consume the next input character:
$char = $this->stream->char(); $char = $this->stream->char();
if($char === "'") { if ($char === "'") {
/* U+0022 QUOTATION MARK (') /* U+0022 QUOTATION MARK (')
Switch to the after attribute value state. */ Switch to the after attribute value state. */
$state = 'after attribute value (quoted)'; $state = 'after attribute value (quoted)';
} elseif($char === '&') { } elseif ($char === '&') {
/* U+0026 AMPERSAND (&) /* U+0026 AMPERSAND (&)
Switch to the entity in attribute value state. */ Switch to the entity in attribute value state. */
$this->characterReferenceInAttributeValue("'"); $this->characterReferenceInAttributeValue("'");
} elseif($char === false) { } elseif ($char === false) {
/* EOF /* EOF
Parse error. Reconsume the EOF character in the data state. */ Parse error. Reconsume the EOF character in the data state. */
$this->emitToken(array( $this->emitToken(array(
@ -999,7 +1024,7 @@ class HTML5_Tokenizer {
// Consume the next input character: // Consume the next input character:
$char = $this->stream->char(); $char = $this->stream->char();
if($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') { if ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
/* U+0009 CHARACTER TABULATION /* U+0009 CHARACTER TABULATION
U+000A LINE FEED (LF) U+000A LINE FEED (LF)
U+000C FORM FEED (FF) U+000C FORM FEED (FF)
@ -1007,14 +1032,14 @@ class HTML5_Tokenizer {
Switch to the before attribute name state. */ Switch to the before attribute name state. */
$state = 'before attribute name'; $state = 'before attribute name';
} elseif($char === '&') { } elseif ($char === '&') {
/* U+0026 AMPERSAND (&) /* U+0026 AMPERSAND (&)
Switch to the entity in attribute value state, with the Switch to the entity in attribute value state, with the
additional allowed character being U+003E additional allowed character being U+003E
GREATER-THAN SIGN (>). */ GREATER-THAN SIGN (>). */
$this->characterReferenceInAttributeValue('>'); $this->characterReferenceInAttributeValue('>');
} elseif($char === '>') { } elseif ($char === '>') {
/* U+003E GREATER-THAN SIGN (>) /* U+003E GREATER-THAN SIGN (>)
Emit the current tag token. Switch to the data state. */ Emit the current tag token. Switch to the data state. */
$this->emitToken($this->token); $this->emitToken($this->token);
@ -1037,7 +1062,7 @@ class HTML5_Tokenizer {
U+003D EQUALS SIGN (=) U+003D EQUALS SIGN (=)
Parse error. Treat it as per the "anything else" Parse error. Treat it as per the "anything else"
entry below. */ entry below. */
if($char === '"' || $char === "'" || $char === '=' || $char == '<') { if ($char === '"' || $char === "'" || $char === '=' || $char == '<') {
$this->emitToken(array( $this->emitToken(array(
'type' => self::PARSEERROR, 'type' => self::PARSEERROR,
'data' => 'unexpected-character-in-unquoted-attribute-value' 'data' => 'unexpected-character-in-unquoted-attribute-value'
@ -1060,7 +1085,7 @@ class HTML5_Tokenizer {
/* Consume the next input character: */ /* Consume the next input character: */
$char = $this->stream->char(); $char = $this->stream->char();
if($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') { if ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
/* U+0009 CHARACTER TABULATION /* U+0009 CHARACTER TABULATION
U+000A LINE FEED (LF) U+000A LINE FEED (LF)
U+000C FORM FEED (FF) U+000C FORM FEED (FF)
@ -1169,7 +1194,7 @@ class HTML5_Tokenizer {
/* If the next two characters are both U+002D HYPHEN-MINUS (-) /* If the next two characters are both U+002D HYPHEN-MINUS (-)
characters, consume those two characters, create a comment token whose characters, consume those two characters, create a comment token whose
data is the empty string, and switch to the comment state. */ data is the empty string, and switch to the comment state. */
if($hyphens === '--') { if ($hyphens === '--') {
$state = 'comment start'; $state = 'comment start';
$this->token = array( $this->token = array(
'data' => '', 'data' => '',
@ -1179,7 +1204,7 @@ class HTML5_Tokenizer {
/* Otherwise if the next seven characters are a case-insensitive match /* Otherwise if the next seven characters are a case-insensitive match
for the word "DOCTYPE", then consume those characters and switch to the for the word "DOCTYPE", then consume those characters and switch to the
DOCTYPE state. */ DOCTYPE state. */
} elseif(strtoupper($alpha) === 'DOCTYPE') { } elseif (strtoupper($alpha) === 'DOCTYPE') {
$state = 'DOCTYPE'; $state = 'DOCTYPE';
// XXX not implemented // XXX not implemented
@ -1283,12 +1308,12 @@ class HTML5_Tokenizer {
/* Consume the next input character: */ /* Consume the next input character: */
$char = $this->stream->char(); $char = $this->stream->char();
if($char === '-') { if ($char === '-') {
/* U+002D HYPHEN-MINUS (-) /* U+002D HYPHEN-MINUS (-)
Switch to the comment end dash state */ Switch to the comment end dash state */
$state = 'comment end dash'; $state = 'comment end dash';
} elseif($char === false) { } elseif ($char === false) {
/* EOF /* EOF
Parse error. Emit the comment token. Reconsume the EOF character Parse error. Emit the comment token. Reconsume the EOF character
in the data state. */ in the data state. */
@ -1314,12 +1339,12 @@ class HTML5_Tokenizer {
/* Consume the next input character: */ /* Consume the next input character: */
$char = $this->stream->char(); $char = $this->stream->char();
if($char === '-') { if ($char === '-') {
/* U+002D HYPHEN-MINUS (-) /* U+002D HYPHEN-MINUS (-)
Switch to the comment end state */ Switch to the comment end state */
$state = 'comment end'; $state = 'comment end';
} elseif($char === false) { } elseif ($char === false) {
/* EOF /* EOF
Parse error. Emit the comment token. Reconsume the EOF character Parse error. Emit the comment token. Reconsume the EOF character
in the data state. */ in the data state. */
@ -1344,13 +1369,13 @@ class HTML5_Tokenizer {
/* Consume the next input character: */ /* Consume the next input character: */
$char = $this->stream->char(); $char = $this->stream->char();
if($char === '>') { if ($char === '>') {
/* U+003E GREATER-THAN SIGN (>) /* U+003E GREATER-THAN SIGN (>)
Emit the comment token. Switch to the data state. */ Emit the comment token. Switch to the data state. */
$this->emitToken($this->token); $this->emitToken($this->token);
$state = 'data'; $state = 'data';
} elseif($char === '-') { } elseif ($char === '-') {
/* U+002D HYPHEN-MINUS (-) /* U+002D HYPHEN-MINUS (-)
Parse error. Append a U+002D HYPHEN-MINUS (-) character Parse error. Append a U+002D HYPHEN-MINUS (-) character
to the comment token's data. Stay in the comment end to the comment token's data. Stay in the comment end
@ -1361,7 +1386,7 @@ class HTML5_Tokenizer {
)); ));
$this->token['data'] .= '-'; $this->token['data'] .= '-';
} elseif($char === "\t" || $char === "\n" || $char === "\x0a" || $char === ' ') { } elseif ($char === "\t" || $char === "\n" || $char === "\x0a" || $char === ' ') {
$this->emitToken(array( $this->emitToken(array(
'type' => self::PARSEERROR, 'type' => self::PARSEERROR,
'data' => 'unexpected-space-after-double-dash-in-comment' 'data' => 'unexpected-space-after-double-dash-in-comment'
@ -1369,14 +1394,14 @@ class HTML5_Tokenizer {
$this->token['data'] .= '--' . $char; $this->token['data'] .= '--' . $char;
$state = 'comment end space'; $state = 'comment end space';
} elseif($char === '!') { } elseif ($char === '!') {
$this->emitToken(array( $this->emitToken(array(
'type' => self::PARSEERROR, 'type' => self::PARSEERROR,
'data' => 'unexpected-bang-after-double-dash-in-comment' 'data' => 'unexpected-bang-after-double-dash-in-comment'
)); ));
$state = 'comment end bang'; $state = 'comment end bang';
} elseif($char === false) { } elseif ($char === false) {
/* EOF /* EOF
Parse error. Emit the comment token. Reconsume the Parse error. Emit the comment token. Reconsume the
EOF character in the data state. */ EOF character in the data state. */
@ -1451,15 +1476,15 @@ class HTML5_Tokenizer {
/* Consume the next input character: */ /* Consume the next input character: */
$char = $this->stream->char(); $char = $this->stream->char();
if($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') { if ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
/* U+0009 CHARACTER TABULATION /* U+0009 CHARACTER TABULATION
U+000A LINE FEED (LF) U+000A LINE FEED (LF)
U+000C FORM FEED (FF) U+000C FORM FEED (FF)
U+0020 SPACE U+0020 SPACE
Switch to the before DOCTYPE name state. */ Switch to the before DOCTYPE name state. */
$state = 'before DOCTYPE name'; $state = 'before DOCTYPE name';
} elseif($char === false) { } elseif ($char === false) {
/* EOF /* EOF
Parse error. Create a new DOCTYPE token. Set its Parse error. Create a new DOCTYPE token. Set its
force-quirks flag to on. Emit the token. Reconsume the force-quirks flag to on. Emit the token. Reconsume the
@ -1494,14 +1519,14 @@ class HTML5_Tokenizer {
/* Consume the next input character: */ /* Consume the next input character: */
$char = $this->stream->char(); $char = $this->stream->char();
if($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') { if ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
/* U+0009 CHARACTER TABULATION /* U+0009 CHARACTER TABULATION
U+000A LINE FEED (LF) U+000A LINE FEED (LF)
U+000C FORM FEED (FF) U+000C FORM FEED (FF)
U+0020 SPACE U+0020 SPACE
Stay in the before DOCTYPE name state. */ Stay in the before DOCTYPE name state. */
} elseif($char === '>') { } elseif ($char === '>') {
/* U+003E GREATER-THAN SIGN (>) /* U+003E GREATER-THAN SIGN (>)
Parse error. Create a new DOCTYPE token. Set its Parse error. Create a new DOCTYPE token. Set its
force-quirks flag to on. Emit the token. Switch to the force-quirks flag to on. Emit the token. Switch to the
@ -1519,7 +1544,7 @@ class HTML5_Tokenizer {
$state = 'data'; $state = 'data';
} elseif('A' <= $char && $char <= 'Z') { } elseif ('A' <= $char && $char <= 'Z') {
/* U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z /* U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z
Create a new DOCTYPE token. Set the token's name to the Create a new DOCTYPE token. Set the token's name to the
lowercase version of the input character (add 0x0020 to lowercase version of the input character (add 0x0020 to
@ -1533,7 +1558,7 @@ class HTML5_Tokenizer {
$state = 'DOCTYPE name'; $state = 'DOCTYPE name';
} elseif($char === false) { } elseif ($char === false) {
/* EOF /* EOF
Parse error. Create a new DOCTYPE token. Set its Parse error. Create a new DOCTYPE token. Set its
force-quirks flag to on. Emit the token. Reconsume the force-quirks flag to on. Emit the token. Reconsume the
@ -1570,7 +1595,7 @@ class HTML5_Tokenizer {
/* Consume the next input character: */ /* Consume the next input character: */
$char = $this->stream->char(); $char = $this->stream->char();
if($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') { if ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
/* U+0009 CHARACTER TABULATION /* U+0009 CHARACTER TABULATION
U+000A LINE FEED (LF) U+000A LINE FEED (LF)
U+000C FORM FEED (FF) U+000C FORM FEED (FF)
@ -1578,20 +1603,20 @@ class HTML5_Tokenizer {
Switch to the after DOCTYPE name state. */ Switch to the after DOCTYPE name state. */
$state = 'after DOCTYPE name'; $state = 'after DOCTYPE name';
} elseif($char === '>') { } elseif ($char === '>') {
/* U+003E GREATER-THAN SIGN (>) /* U+003E GREATER-THAN SIGN (>)
Emit the current DOCTYPE token. Switch to the data state. */ Emit the current DOCTYPE token. Switch to the data state. */
$this->emitToken($this->token); $this->emitToken($this->token);
$state = 'data'; $state = 'data';
} elseif('A' <= $char && $char <= 'Z') { } elseif ('A' <= $char && $char <= 'Z') {
/* U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z /* U+0041 LATIN CAPITAL LETTER A through to U+005A LATIN CAPITAL LETTER Z
Append the lowercase version of the input character Append the lowercase version of the input character
(add 0x0020 to the character's code point) to the current (add 0x0020 to the character's code point) to the current
DOCTYPE token's name. Stay in the DOCTYPE name state. */ DOCTYPE token's name. Stay in the DOCTYPE name state. */
$this->token['name'] .= strtolower($char); $this->token['name'] .= strtolower($char);
} elseif($char === false) { } elseif ($char === false) {
/* EOF /* EOF
Parse error. Set the DOCTYPE token's force-quirks flag Parse error. Set the DOCTYPE token's force-quirks flag
to on. Emit that DOCTYPE token. Reconsume the EOF to on. Emit that DOCTYPE token. Reconsume the EOF
@ -1624,20 +1649,20 @@ class HTML5_Tokenizer {
/* Consume the next input character: */ /* Consume the next input character: */
$char = $this->stream->char(); $char = $this->stream->char();
if($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') { if ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
/* U+0009 CHARACTER TABULATION /* U+0009 CHARACTER TABULATION
U+000A LINE FEED (LF) U+000A LINE FEED (LF)
U+000C FORM FEED (FF) U+000C FORM FEED (FF)
U+0020 SPACE U+0020 SPACE
Stay in the after DOCTYPE name state. */ Stay in the after DOCTYPE name state. */
} elseif($char === '>') { } elseif ($char === '>') {
/* U+003E GREATER-THAN SIGN (>) /* U+003E GREATER-THAN SIGN (>)
Emit the current DOCTYPE token. Switch to the data state. */ Emit the current DOCTYPE token. Switch to the data state. */
$this->emitToken($this->token); $this->emitToken($this->token);
$state = 'data'; $state = 'data';
} elseif($char === false) { } elseif ($char === false) {
/* EOF /* EOF
Parse error. Set the DOCTYPE token's force-quirks flag Parse error. Set the DOCTYPE token's force-quirks flag
to on. Emit that DOCTYPE token. Reconsume the EOF to on. Emit that DOCTYPE token. Reconsume the EOF
@ -1688,7 +1713,7 @@ class HTML5_Tokenizer {
/* Consume the next input character: */ /* Consume the next input character: */
$char = $this->stream->char(); $char = $this->stream->char();
if($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') { if ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
/* U+0009 CHARACTER TABULATION /* U+0009 CHARACTER TABULATION
U+000A LINE FEED (LF) U+000A LINE FEED (LF)
U+000C FORM FEED (FF) U+000C FORM FEED (FF)
@ -1828,7 +1853,7 @@ class HTML5_Tokenizer {
/* Consume the next input character: */ /* Consume the next input character: */
$char = $this->stream->char(); $char = $this->stream->char();
if($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') { if ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
/* U+0009 CHARACTER TABULATION /* U+0009 CHARACTER TABULATION
U+000A LINE FEED (LF) U+000A LINE FEED (LF)
U+000C FORM FEED (FF) U+000C FORM FEED (FF)
@ -1882,7 +1907,7 @@ class HTML5_Tokenizer {
/* Consume the next input character: */ /* Consume the next input character: */
$char = $this->stream->char(); $char = $this->stream->char();
if($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') { if ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
/* U+0009 CHARACTER TABULATION /* U+0009 CHARACTER TABULATION
U+000A LINE FEED (LF) U+000A LINE FEED (LF)
U+000C FORM FEED (FF) U+000C FORM FEED (FF)
@ -2022,7 +2047,7 @@ class HTML5_Tokenizer {
/* Consume the next input character: */ /* Consume the next input character: */
$char = $this->stream->char(); $char = $this->stream->char();
if($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') { if ($char === "\t" || $char === "\n" || $char === "\x0c" || $char === ' ') {
/* U+0009 CHARACTER TABULATION /* U+0009 CHARACTER TABULATION
U+000A LINE FEED (LF) U+000A LINE FEED (LF)
U+000C FORM FEED (FF) U+000C FORM FEED (FF)
@ -2068,7 +2093,7 @@ class HTML5_Tokenizer {
$this->emitToken($this->token); $this->emitToken($this->token);
$state = 'data'; $state = 'data';
} elseif($char === false) { } elseif ($char === false) {
/* EOF /* EOF
Emit the DOCTYPE token. Reconsume the EOF character in Emit the DOCTYPE token. Reconsume the EOF character in
the data state. */ the data state. */
@ -2083,32 +2108,42 @@ class HTML5_Tokenizer {
break; break;
// case 'cdataSection': // case 'cdataSection':
} }
} }
} }
/** /**
* Returns a serialized representation of the tree. * Returns a serialized representation of the tree.
*
* @return DOMDocument|DOMNodeList
*/ */
public function save() { public function save() {
return $this->tree->save(); return $this->tree->save();
} }
/** /**
* @return HTML5_TreeBuilder The tree * @return HTML5_TreeBuilder The tree
*/ */
public function getTree() { public function getTree()
return $this->tree; {
} return $this->tree;
}
/** /**
* Returns the input stream. * Returns the input stream.
*
* @return HTML5_InputStream
*/ */
public function stream() { public function stream() {
return $this->stream; return $this->stream;
} }
/**
* @param bool $allowed
* @param bool $inattr
* @return string
*/
private function consumeCharacterReference($allowed = false, $inattr = false) { private function consumeCharacterReference($allowed = false, $inattr = false) {
// This goes quite far against spec, and is far closer to the Python // This goes quite far against spec, and is far closer to the Python
// impl., mainly because we don't do the large unconsuming the spec // impl., mainly because we don't do the large unconsuming the spec
@ -2223,8 +2258,8 @@ class HTML5_Tokenizer {
)); ));
return HTML5_Data::utf8chr($new_codepoint); return HTML5_Data::utf8chr($new_codepoint);
} else { } else {
/* Otherwise, if the number is greater than 0x10FFFF, then /* Otherwise, if the number is greater than 0x10FFFF, then
* this is a parse error. Return a U+FFFD REPLACEMENT * this is a parse error. Return a U+FFFD REPLACEMENT
* CHARACTER. */ * CHARACTER. */
if ($codepoint > 0x10FFFF) { if ($codepoint > 0x10FFFF) {
$this->emitToken(array( $this->emitToken(array(
@ -2233,16 +2268,16 @@ class HTML5_Tokenizer {
)); ));
return "\xEF\xBF\xBD"; return "\xEF\xBF\xBD";
} }
/* Otherwise, return a character token for the Unicode /* Otherwise, return a character token for the Unicode
* character whose code point is that number. If the * character whose code point is that number. If the
* number is in the range 0x0001 to 0x0008, 0x000E to * number is in the range 0x0001 to 0x0008, 0x000E to
* 0x001F, 0x007F to 0x009F, 0xD800 to 0xDFFF, 0xFDD0 to * 0x001F, 0x007F to 0x009F, 0xD800 to 0xDFFF, 0xFDD0 to
* 0xFDEF, or is one of 0x000B, 0xFFFE, 0xFFFF, 0x1FFFE, * 0xFDEF, or is one of 0x000B, 0xFFFE, 0xFFFF, 0x1FFFE,
* 0x1FFFF, 0x2FFFE, 0x2FFFF, 0x3FFFE, 0x3FFFF, 0x4FFFE, * 0x1FFFF, 0x2FFFE, 0x2FFFF, 0x3FFFE, 0x3FFFF, 0x4FFFE,
* 0x4FFFF, 0x5FFFE, 0x5FFFF, 0x6FFFE, 0x6FFFF, 0x7FFFE, * 0x4FFFF, 0x5FFFE, 0x5FFFF, 0x6FFFE, 0x6FFFF, 0x7FFFE,
* 0x7FFFF, 0x8FFFE, 0x8FFFF, 0x9FFFE, 0x9FFFF, 0xAFFFE, * 0x7FFFF, 0x8FFFE, 0x8FFFF, 0x9FFFE, 0x9FFFF, 0xAFFFE,
* 0xAFFFF, 0xBFFFE, 0xBFFFF, 0xCFFFE, 0xCFFFF, 0xDFFFE, * 0xAFFFF, 0xBFFFE, 0xBFFFF, 0xCFFFE, 0xCFFFF, 0xDFFFE,
* 0xDFFFF, 0xEFFFE, 0xEFFFF, 0xFFFFE, 0xFFFFF, 0x10FFFE, * 0xDFFFF, 0xEFFFE, 0xEFFFF, 0xFFFFE, 0xFFFFF, 0x10FFFE,
* or 0x10FFFF, then this is a parse error. */ * or 0x10FFFF, then this is a parse error. */
// && has higher precedence than || // && has higher precedence than ||
if ( if (
@ -2263,7 +2298,6 @@ class HTML5_Tokenizer {
return HTML5_Data::utf8chr($codepoint); return HTML5_Data::utf8chr($codepoint);
} }
} }
} else { } else {
/* Anything else */ /* Anything else */
@ -2275,7 +2309,7 @@ class HTML5_Tokenizer {
// matches the start of one of the identifiers in the first column. // matches the start of one of the identifiers in the first column.
$refs = HTML5_Data::getNamedCharacterReferences(); $refs = HTML5_Data::getNamedCharacterReferences();
// Get the longest string which is the start of an identifier // Get the longest string which is the start of an identifier
// ($chars) as well as the longest identifier which matches ($id) // ($chars) as well as the longest identifier which matches ($id)
// and its codepoint ($codepoint). // and its codepoint ($codepoint).
@ -2289,7 +2323,7 @@ class HTML5_Tokenizer {
} }
$chars .= $char = $this->stream->char(); $chars .= $char = $this->stream->char();
} }
// Unconsume the one character we just took which caused the while // Unconsume the one character we just took which caused the while
// statement to fail. This could be anything and could cause state // statement to fail. This could be anything and could cause state
// changes (as if it matches the while loop it must be // changes (as if it matches the while loop it must be
@ -2353,6 +2387,9 @@ class HTML5_Tokenizer {
} }
} }
/**
* @param bool $allowed
*/
private function characterReferenceInAttributeValue($allowed = false) { private function characterReferenceInAttributeValue($allowed = false) {
/* Attempt to consume a character reference. */ /* Attempt to consume a character reference. */
$entity = $this->consumeCharacterReference($allowed, true); $entity = $this->consumeCharacterReference($allowed, true);
@ -2375,15 +2412,19 @@ class HTML5_Tokenizer {
/** /**
* Emits a token, passing it on to the tree builder. * Emits a token, passing it on to the tree builder.
*
* @param $token
* @param bool $checkStream
* @param bool $dry
*/ */
protected function emitToken($token, $checkStream = true, $dry = false) { protected function emitToken($token, $checkStream = true, $dry = false) {
if ($checkStream) { if ($checkStream === true) {
// Emit errors from input stream. // Emit errors from input stream.
while ($this->stream->errors) { while ($this->stream->errors) {
$this->emitToken(array_shift($this->stream->errors), false); $this->emitToken(array_shift($this->stream->errors), false);
} }
} }
if($token['type'] === self::ENDTAG && !empty($token['attr'])) { if ($token['type'] === self::ENDTAG && !empty($token['attr'])) {
for ($i = 0; $i < count($token['attr']); $i++) { for ($i = 0; $i < count($token['attr']); $i++) {
$this->emitToken(array( $this->emitToken(array(
'type' => self::PARSEERROR, 'type' => self::PARSEERROR,
@ -2391,13 +2432,13 @@ class HTML5_Tokenizer {
)); ));
} }
} }
if($token['type'] === self::ENDTAG && !empty($token['self-closing'])) { if ($token['type'] === self::ENDTAG && !empty($token['self-closing'])) {
$this->emitToken(array( $this->emitToken(array(
'type' => self::PARSEERROR, 'type' => self::PARSEERROR,
'data' => 'self-closing-flag-on-end-tag', 'data' => 'self-closing-flag-on-end-tag',
)); ));
} }
if($token['type'] === self::STARTTAG) { if ($token['type'] === self::STARTTAG) {
// This could be changed to actually pass the tree-builder a hash // This could be changed to actually pass the tree-builder a hash
$hash = array(); $hash = array();
foreach ($token['attr'] as $keypair) { foreach ($token['attr'] as $keypair) {
@ -2412,16 +2453,16 @@ class HTML5_Tokenizer {
} }
} }
if(!$dry) { if ($dry === false) {
// the current structure of attributes is not a terribly good one // the current structure of attributes is not a terribly good one
$this->tree->emitToken($token); $this->tree->emitToken($token);
} }
if(!$dry && is_int($this->tree->content_model)) { if ($dry === false && is_int($this->tree->content_model)) {
$this->content_model = $this->tree->content_model; $this->content_model = $this->tree->content_model;
$this->tree->content_model = null; $this->tree->content_model = null;
} elseif($token['type'] === self::ENDTAG) { } elseif ($token['type'] === self::ENDTAG) {
$this->content_model = self::PCDATA; $this->content_model = self::PCDATA;
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -1,33 +1,48 @@
/** /**
* dompdf default stylesheet. * dompdf default stylesheet.
*
* The Original Code is mozilla.org code.
* *
* The Initial Developer of the Original Code is Netscape Communications Corporation.
* Portions created by the Initial Developer are Copyright (C) 1998 the Initial Developer.
* All Rights Reserved.
*
* @package dompdf * @package dompdf
* @link http://dompdf.github.com/ * @link http://dompdf.github.com/
* @author Benj Carson <benjcarson@digitaljunkies.ca> * @author Benj Carson <benjcarson@digitaljunkies.ca>
* @author Blake Ross <BlakeR1234@aol.com> * @author Blake Ross <BlakeR1234@aol.com>
* @author Fabien Ménager <fabien.menager@gmail.com> * @author Fabien Ménager <fabien.menager@gmail.com>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
* @version $Id$ *
* Portions from Mozilla
* @link https://dxr.mozilla.org/mozilla-central/source/layout/style/res/html.css
* @license http://mozilla.org/MPL/2.0/ Mozilla Public License, v. 2.0
*
* Portions from W3C
* @link https://drafts.csswg.org/css-ui-3/#default-style-sheet
*
*/ */
@page { @page {
margin: 1.2cm; margin: 1.2cm;
} }
html { html {
display: -dompdf-page; display: -dompdf-page !important;
counter-reset: page; counter-reset: page;
} }
/* blocks */ /* blocks */
div, map, dt, isindex { article,
aside,
details,
div,
dt,
figcaption,
footer,
form,
header,
hgroup,
main,
nav,
noscript,
section,
summary {
display: block; display: block;
} }
@ -47,7 +62,7 @@ dd {
margin-left: 40px; margin-left: 40px;
} }
blockquote { blockquote, figure {
display: block; display: block;
margin: 1em 40px; margin: 1em 40px;
} }
@ -64,7 +79,7 @@ center {
blockquote[type=cite] { blockquote[type=cite] {
display: block; display: block;
margin: 1em 0px; margin: 1em 0;
padding-left: 1em; padding-left: 1em;
border-left: solid; border-left: solid;
border-color: blue; border-color: blue;
@ -113,25 +128,18 @@ listing {
margin: 1em 0; margin: 1em 0;
} }
plaintext, xmp, pre { plaintext, pre, xmp {
display: block; display: block;
font-family: fixed; font-family: fixed;
white-space: pre; white-space: pre;
margin: 1em 0; margin: 1em 0;
} }
article, aside, details,
figcaption, figure,
footer, header, hgroup,
nav, section {
display: block;
}
/* tables */ /* tables */
table { table {
display: table; display: table;
border-spacing: 2px; border-spacing: 2px;
border-collapse: separate; border-collapse: separate;
margin-top: 0; margin-top: 0;
margin-bottom: 0; margin-bottom: 0;
@ -155,10 +163,10 @@ table[border] th {
td, th, tr { td, th, tr {
background: inherit; background: inherit;
} }
/* caption inherits from table not table-outer */ /* caption inherits from table not table-outer */
caption { caption {
display: block; display: table-caption;
text-align: center; text-align: center;
} }
@ -195,10 +203,10 @@ table > tr {
vertical-align: middle; vertical-align: middle;
} }
td { td {
display: table-cell; display: table-cell;
vertical-align: inherit; vertical-align: inherit;
text-align: inherit; text-align: inherit;
padding: 1px; padding: 1px;
} }
@ -207,7 +215,6 @@ th {
vertical-align: inherit; vertical-align: inherit;
font-weight: bold; font-weight: bold;
padding: 1px; padding: 1px;
text-align: center;
} }
/* inlines */ /* inlines */
@ -248,10 +255,6 @@ s, strike, del {
text-decoration: line-through; text-decoration: line-through;
} }
blink {
text-decoration: blink;
}
big { big {
font-size: larger; font-size: larger;
} }
@ -276,6 +279,17 @@ nobr {
white-space: nowrap; white-space: nowrap;
} }
mark {
background: yellow;
color: black;
}
/* titles */
abbr[title], acronym[title] {
text-decoration: dotted underline;
}
/* lists */ /* lists */
ul, menu, dir { ul, menu, dir {
@ -303,40 +317,32 @@ li {
}*/ }*/
/* nested lists have no top/bottom margins */ /* nested lists have no top/bottom margins */
ul ul, ul ol, ul dir, ul menu, ul dl, :matches(ul, ol, dir, menu, dl) ul,
ol ul, ol ol, ol dir, ol menu, ol dl, :matches(ul, ol, dir, menu, dl) ol,
dir ul, dir ol, dir dir, dir menu, dir dl, :matches(ul, ol, dir, menu, dl) dir,
menu ul, menu ol, menu dir, menu menu, menu dl, :matches(ul, ol, dir, menu, dl) menu,
dl ul, dl ol, dl dir, dl menu, dl dl { :matches(ul, ol, dir, menu, dl) dl {
margin-top: 0; margin-top: 0;
margin-bottom: 0; margin-bottom: 0;
} }
/* 2 deep unordered lists use a circle */ /* 2 deep unordered lists use a circle */
ol ul, ul ul, menu ul, dir ul, :matches(ul, ol, dir, menu) ul,
ol menu, ul menu, menu menu, dir menu, :matches(ul, ol, dir, menu) ul,
ol dir, ul dir, menu dir, dir dir { :matches(ul, ol, dir, menu) ul,
:matches(ul, ol, dir, menu) ul {
list-style-type: circle; list-style-type: circle;
} }
/* 3 deep (or more) unordered lists use a square */ /* 3 deep (or more) unordered lists use a square */
ol ol ul, ol ul ul, ol menu ul, ol dir ul, :matches(ul, ol, dir, menu) :matches(ul, ol, dir, menu) ul,
ol ol menu, ol ul menu, ol menu menu, ol dir menu, :matches(ul, ol, dir, menu) :matches(ul, ol, dir, menu) menu,
ol ol dir, ol ul dir, ol menu dir, ol dir dir, :matches(ul, ol, dir, menu) :matches(ul, ol, dir, menu) dir {
ul ol ul, ul ul ul, ul menu ul, ul dir ul,
ul ol menu, ul ul menu, ul menu menu, ul dir menu,
ul ol dir, ul ul dir, ul menu dir, ul dir dir,
menu ol ul, menu ul ul, menu menu ul, menu dir ul,
menu ol menu, menu ul menu, menu menu menu, menu dir menu,
menu ol dir, menu ul dir, menu menu dir, menu dir dir,
dir ol ul, dir ul ul, dir menu ul, dir dir ul,
dir ol menu, dir ul menu, dir menu menu, dir dir menu,
dir ol dir, dir ul dir, dir menu dir, dir dir dir {
list-style-type: square; list-style-type: square;
} }
/* forms */ /* forms */
/* From http://dev.w3.org/csswg/css3-ui/#appearance */ /* From https://drafts.csswg.org/css-ui-3/#default-style-sheet */
form { form {
display: block; display: block;
} }
@ -373,10 +379,13 @@ input[type=reset],
input[type=file], input[type=file],
button { button {
background: #CCC; background: #CCC;
width: 8em;
text-align: center; text-align: center;
} }
input[type=file] {
width: 8em;
}
input[type=text]:before, input[type=text]:before,
input[type=button]:before, input[type=button]:before,
input[type=submit]:before, input[type=submit]:before,
@ -385,11 +394,11 @@ input[type=reset]:before {
} }
input[type=file]:before { input[type=file]:before {
content: "Chose a file"; content: "Choose a file";
} }
input[type=password][value]:before { input[type=password][value]:before {
font-family: "DejaVu Sans"; font-family: "DejaVu Sans" !important;
content: "\2022\2022\2022\2022\2022\2022\2022\2022"; content: "\2022\2022\2022\2022\2022\2022\2022\2022";
line-height: 1em; line-height: 1em;
} }
@ -397,7 +406,7 @@ input[type=password][value]:before {
input[type=checkbox], input[type=checkbox],
input[type=radio], input[type=radio],
select:after { select:after {
font-family: "DejaVu Sans"; font-family: "DejaVu Sans" !important;
font-size: 18px; font-size: 18px;
line-height: 1; line-height: 1;
} }
@ -480,20 +489,24 @@ hr {
margin: 0.5em auto 0.5em auto; margin: 0.5em auto 0.5em auto;
} }
hr[size="1"] {
border-style: solid none none none;
}
iframe { iframe {
border: 2px inset; border: 2px inset;
} }
noframes { noframes {
display: none; display: block;
} }
br { br {
display: -dompdf-br; display: -dompdf-br;
} }
img, img_generated { img, img_generated {
display: -dompdf-image; display: -dompdf-image !important;
} }
dompdf_generated { dompdf_generated {
@ -502,7 +515,7 @@ dompdf_generated {
/* hidden elements */ /* hidden elements */
area, base, basefont, head, meta, script, style, title, area, base, basefont, head, meta, script, style, title,
noembed, noscript, param { noembed, param {
display: none; display: none;
-dompdf-keep: yes; -dompdf-keep: yes;
} }

View File

@ -89,6 +89,7 @@ class CPDF implements Canvas
"sra3" => array(0, 0, 907.09, 1275.59), "sra3" => array(0, 0, 907.09, 1275.59),
"sra4" => array(0, 0, 637.80, 907.09), "sra4" => array(0, 0, 637.80, 907.09),
"letter" => array(0, 0, 612.00, 792.00), "letter" => array(0, 0, 612.00, 792.00),
"half-letter" => array(0, 0, 396.00, 612.00),
"legal" => array(0, 0, 612.00, 1008.00), "legal" => array(0, 0, 612.00, 1008.00),
"ledger" => array(0, 0, 1224.00, 792.00), "ledger" => array(0, 0, 1224.00, 792.00),
"tabloid" => array(0, 0, 792.00, 1224.00), "tabloid" => array(0, 0, 792.00, 1224.00),
@ -164,6 +165,13 @@ class CPDF implements Canvas
*/ */
private $_image_cache; private $_image_cache;
/**
* Currently-applied opacity level (0 - 1)
*
* @var float
*/
private $_current_opacity = 1;
/** /**
* Class constructor * Class constructor
* *
@ -171,7 +179,7 @@ class CPDF implements Canvas
* @param string $orientation The orientation of the document (either 'landscape' or 'portrait') * @param string $orientation The orientation of the document (either 'landscape' or 'portrait')
* @param Dompdf $dompdf The Dompdf instance * @param Dompdf $dompdf The Dompdf instance
*/ */
function __construct($paper = "letter", $orientation = "portrait", Dompdf $dompdf) public function __construct($paper = "letter", $orientation = "portrait", Dompdf $dompdf)
{ {
if (is_array($paper)) { if (is_array($paper)) {
$size = $paper; $size = $paper;
@ -190,8 +198,8 @@ class CPDF implements Canvas
$this->_pdf = new \Cpdf( $this->_pdf = new \Cpdf(
$size, $size,
true, true,
$dompdf->get_option("font_cache"), $dompdf->getOptions()->getFontCache(),
$dompdf->get_option("temp_dir") $dompdf->getOptions()->getTempDir()
); );
$this->_pdf->addInfo("Producer", sprintf("%s + CPDF", $dompdf->version)); $this->_pdf->addInfo("Producer", sprintf("%s + CPDF", $dompdf->version));
@ -210,7 +218,10 @@ class CPDF implements Canvas
$this->_image_cache = array(); $this->_image_cache = array();
} }
function get_dompdf() /**
* @return Dompdf
*/
public function get_dompdf()
{ {
return $this->_dompdf; return $this->_dompdf;
} }
@ -220,7 +231,7 @@ class CPDF implements Canvas
* *
* Deletes all temporary image files * Deletes all temporary image files
*/ */
function __destruct() public function __destruct()
{ {
foreach ($this->_image_cache as $img) { foreach ($this->_image_cache as $img) {
// The file might be already deleted by 3rd party tmp cleaner, // The file might be already deleted by 3rd party tmp cleaner,
@ -231,8 +242,12 @@ class CPDF implements Canvas
continue; continue;
} }
if ($this->_dompdf->get_option("debugPng")) print '[__destruct unlink ' . $img . ']'; if ($this->_dompdf->getOptions()->getDebugPng()) {
if (!$this->_dompdf->get_option("debugKeepTemp")) unlink($img); print '[__destruct unlink ' . $img . ']';
}
if (!$this->_dompdf->getOptions()->getDebugKeepTemp()) {
unlink($img);
}
} }
} }
@ -241,7 +256,7 @@ class CPDF implements Canvas
* *
* @return \Cpdf * @return \Cpdf
*/ */
function get_cpdf() public function get_cpdf()
{ {
return $this->_pdf; return $this->_pdf;
} }
@ -252,7 +267,7 @@ class CPDF implements Canvas
* @param string $label label of the value (Creator, Producer, etc.) * @param string $label label of the value (Creator, Producer, etc.)
* @param string $value the text to set * @param string $value the text to set
*/ */
function add_info($label, $value) public function add_info($label, $value)
{ {
$this->_pdf->addInfo($label, $value); $this->_pdf->addInfo($label, $value);
} }
@ -266,12 +281,12 @@ class CPDF implements Canvas
* *
* The return value is an integer ID for the new object. * The return value is an integer ID for the new object.
* *
* @see CPDF_Adapter::close_object() * @see CPDF::close_object()
* @see CPDF_Adapter::add_object() * @see CPDF::add_object()
* *
* @return int * @return int
*/ */
function open_object() public function open_object()
{ {
$ret = $this->_pdf->openObject(); $ret = $this->_pdf->openObject();
$this->_pdf->saveState(); $this->_pdf->saveState();
@ -281,10 +296,10 @@ class CPDF implements Canvas
/** /**
* Reopens an existing 'object' * Reopens an existing 'object'
* *
* @see CPDF_Adapter::open_object() * @see CPDF::open_object()
* @param int $object the ID of a previously opened object * @param int $object the ID of a previously opened object
*/ */
function reopen_object($object) public function reopen_object($object)
{ {
$this->_pdf->reopenObject($object); $this->_pdf->reopenObject($object);
$this->_pdf->saveState(); $this->_pdf->saveState();
@ -293,9 +308,9 @@ class CPDF implements Canvas
/** /**
* Closes the current 'object' * Closes the current 'object'
* *
* @see CPDF_Adapter::open_object() * @see CPDF::open_object()
*/ */
function close_object() public function close_object()
{ {
$this->_pdf->restoreState(); $this->_pdf->restoreState();
$this->_pdf->closeObject(); $this->_pdf->closeObject();
@ -319,7 +334,7 @@ class CPDF implements Canvas
* @param int $object * @param int $object
* @param string $where * @param string $where
*/ */
function add_object($object, $where = 'all') public function add_object($object, $where = 'all')
{ {
$this->_pdf->addObject($object, $where); $this->_pdf->addObject($object, $where);
} }
@ -332,7 +347,7 @@ class CPDF implements Canvas
* *
* @param int $object * @param int $object
*/ */
function stop_object($object) public function stop_object($object)
{ {
$this->_pdf->stopObject($object); $this->_pdf->stopObject($object);
} }
@ -340,7 +355,7 @@ class CPDF implements Canvas
/** /**
* @access private * @access private
*/ */
function serialize_object($id) public function serialize_object($id)
{ {
// Serialize the pdf object's current state for retrieval later // Serialize the pdf object's current state for retrieval later
return $this->_pdf->serializeObject($id); return $this->_pdf->serializeObject($id);
@ -349,7 +364,7 @@ class CPDF implements Canvas
/** /**
* @access private * @access private
*/ */
function reopen_serialized_object($obj) public function reopen_serialized_object($obj)
{ {
return $this->_pdf->restoreSerializedObject($obj); return $this->_pdf->restoreSerializedObject($obj);
} }
@ -360,7 +375,7 @@ class CPDF implements Canvas
* Returns the PDF's width in points * Returns the PDF's width in points
* @return float * @return float
*/ */
function get_width() public function get_width()
{ {
return $this->_width; return $this->_width;
} }
@ -369,7 +384,7 @@ class CPDF implements Canvas
* Returns the PDF's height in points * Returns the PDF's height in points
* @return float * @return float
*/ */
function get_height() public function get_height()
{ {
return $this->_height; return $this->_height;
} }
@ -378,7 +393,7 @@ class CPDF implements Canvas
* Returns the current page number * Returns the current page number
* @return int * @return int
*/ */
function get_page_number() public function get_page_number()
{ {
return $this->_page_number; return $this->_page_number;
} }
@ -387,7 +402,7 @@ class CPDF implements Canvas
* Returns the total number of pages in the document * Returns the total number of pages in the document
* @return int * @return int
*/ */
function get_page_count() public function get_page_count()
{ {
return $this->_page_count; return $this->_page_count;
} }
@ -397,7 +412,7 @@ class CPDF implements Canvas
* *
* @param int $num * @param int $num
*/ */
function set_page_number($num) public function set_page_number($num)
{ {
$this->_page_number = $num; $this->_page_number = $num;
} }
@ -407,7 +422,7 @@ class CPDF implements Canvas
* *
* @param int $count * @param int $count
*/ */
function set_page_count($count) public function set_page_count($count)
{ {
$this->_page_count = $count; $this->_page_count = $count;
} }
@ -421,6 +436,11 @@ class CPDF implements Canvas
protected function _set_stroke_color($color) protected function _set_stroke_color($color)
{ {
$this->_pdf->setStrokeColor($color); $this->_pdf->setStrokeColor($color);
$alpha = isset($color["alpha"]) ? $color["alpha"] : 1;
if ($this->_current_opacity != 1) {
$alpha *= $this->_current_opacity;
}
$this->_set_line_transparency("Normal", $alpha);
} }
/** /**
@ -432,6 +452,11 @@ class CPDF implements Canvas
protected function _set_fill_color($color) protected function _set_fill_color($color)
{ {
$this->_pdf->setColor($color); $this->_pdf->setColor($color);
$alpha = isset($color["alpha"]) ? $color["alpha"] : 1;
if ($this->_current_opacity) {
$alpha *= $this->_current_opacity;
}
$this->_set_fill_transparency("Normal", $alpha);
} }
/** /**
@ -491,13 +516,14 @@ class CPDF implements Canvas
* @param $opacity * @param $opacity
* @param $mode * @param $mode
*/ */
function set_opacity($opacity, $mode = "Normal") public function set_opacity($opacity, $mode = "Normal")
{ {
$this->_set_line_transparency($mode, $opacity); $this->_set_line_transparency($mode, $opacity);
$this->_set_fill_transparency($mode, $opacity); $this->_set_fill_transparency($mode, $opacity);
$this->_current_opacity = $opacity;
} }
function set_default_view($view, $options = array()) public function set_default_view($view, $options = array())
{ {
array_unshift($options, $view); array_unshift($options, $view);
call_user_func_array(array($this->_pdf, "openHere"), $options); call_user_func_array(array($this->_pdf, "openHere"), $options);
@ -514,26 +540,47 @@ class CPDF implements Canvas
return $this->_height - $y; return $this->_height - $y;
} }
// Canvas implementation /**
function line($x1, $y1, $x2, $y2, $color, $width, $style = array()) * Canvas implementation
*
* @param float $x1
* @param float $y1
* @param float $x2
* @param float $y2
* @param array $color
* @param float $width
* @param array $style
*/
public function line($x1, $y1, $x2, $y2, $color, $width, $style = array())
{ {
$this->_set_stroke_color($color); $this->_set_stroke_color($color);
$this->_set_line_style($width, "butt", "", $style); $this->_set_line_style($width, "butt", "", $style);
$this->_pdf->line($x1, $this->y($y1), $this->_pdf->line($x1, $this->y($y1),
$x2, $this->y($y2)); $x2, $this->y($y2));
$this->_set_line_transparency("Normal", $this->_current_opacity);
} }
function arc($x, $y, $r1, $r2, $astart, $aend, $color, $width, $style = array()) /**
* @param float $x
* @param float $y
* @param float $r1
* @param float $r2
* @param float $astart
* @param float $aend
* @param array $color
* @param float $width
* @param array $style
*/
public function arc($x, $y, $r1, $r2, $astart, $aend, $color, $width, $style = array())
{ {
$this->_set_stroke_color($color); $this->_set_stroke_color($color);
$this->_set_line_style($width, "butt", "", $style); $this->_set_line_style($width, "butt", "", $style);
$this->_pdf->ellipse($x, $this->y($y), $r1, $r2, 0, 8, $astart, $aend, false, false, true, false); $this->_pdf->ellipse($x, $this->y($y), $r1, $r2, 0, 8, $astart, $aend, false, false, true, false);
$this->_set_line_transparency("Normal", $this->_current_opacity);
} }
//........................................................................
/** /**
* Convert a GIF or BMP image to a PNG image * Convert a GIF or BMP image to a PNG image
* *
@ -560,7 +607,7 @@ class CPDF implements Canvas
if ($im) { if ($im) {
imageinterlace($im, false); imageinterlace($im, false);
$tmp_dir = $this->_dompdf->get_option("temp_dir"); $tmp_dir = $this->_dompdf->getOptions()->getTempDir();
$tmp_name = tempnam($tmp_dir, "{$type}dompdf_img_"); $tmp_name = tempnam($tmp_dir, "{$type}dompdf_img_");
@unlink($tmp_name); @unlink($tmp_name);
$filename = "$tmp_name.png"; $filename = "$tmp_name.png";
@ -577,70 +624,149 @@ class CPDF implements Canvas
return $filename; return $filename;
} }
function rectangle($x1, $y1, $w, $h, $color, $width, $style = array()) /**
* @param float $x1
* @param float $y1
* @param float $w
* @param float $h
* @param array $color
* @param float $width
* @param array $style
*/
public function rectangle($x1, $y1, $w, $h, $color, $width, $style = array())
{ {
$this->_set_stroke_color($color); $this->_set_stroke_color($color);
$this->_set_line_style($width, "butt", "", $style); $this->_set_line_style($width, "butt", "", $style);
$this->_pdf->rectangle($x1, $this->y($y1) - $h, $w, $h); $this->_pdf->rectangle($x1, $this->y($y1) - $h, $w, $h);
$this->_set_line_transparency("Normal", $this->_current_opacity);
} }
function filled_rectangle($x1, $y1, $w, $h, $color) /**
* @param float $x1
* @param float $y1
* @param float $w
* @param float $h
* @param array $color
*/
public function filled_rectangle($x1, $y1, $w, $h, $color)
{ {
$this->_set_fill_color($color); $this->_set_fill_color($color);
$this->_pdf->filledRectangle($x1, $this->y($y1) - $h, $w, $h); $this->_pdf->filledRectangle($x1, $this->y($y1) - $h, $w, $h);
$this->_set_fill_transparency("Normal", $this->_current_opacity);
} }
function clipping_rectangle($x1, $y1, $w, $h) /**
* @param float $x1
* @param float $y1
* @param float $w
* @param float $h
*/
public function clipping_rectangle($x1, $y1, $w, $h)
{ {
$this->_pdf->clippingRectangle($x1, $this->y($y1) - $h, $w, $h); $this->_pdf->clippingRectangle($x1, $this->y($y1) - $h, $w, $h);
} }
function clipping_roundrectangle($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL) /**
* @param float $x1
* @param float $y1
* @param float $w
* @param float $h
* @param float $rTL
* @param float $rTR
* @param float $rBR
* @param float $rBL
*/
public function clipping_roundrectangle($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL)
{ {
$this->_pdf->clippingRectangleRounded($x1, $this->y($y1) - $h, $w, $h, $rTL, $rTR, $rBR, $rBL); $this->_pdf->clippingRectangleRounded($x1, $this->y($y1) - $h, $w, $h, $rTL, $rTR, $rBR, $rBL);
} }
function clipping_end() /**
*
*/
public function clipping_end()
{ {
$this->_pdf->clippingEnd(); $this->_pdf->clippingEnd();
} }
function save() /**
*
*/
public function save()
{ {
$this->_pdf->saveState(); $this->_pdf->saveState();
} }
function restore() /**
*
*/
public function restore()
{ {
$this->_pdf->restoreState(); $this->_pdf->restoreState();
} }
function rotate($angle, $x, $y) /**
* @param $angle
* @param $x
* @param $y
*/
public function rotate($angle, $x, $y)
{ {
$this->_pdf->rotate($angle, $x, $y); $this->_pdf->rotate($angle, $x, $y);
} }
function skew($angle_x, $angle_y, $x, $y) /**
* @param $angle_x
* @param $angle_y
* @param $x
* @param $y
*/
public function skew($angle_x, $angle_y, $x, $y)
{ {
$this->_pdf->skew($angle_x, $angle_y, $x, $y); $this->_pdf->skew($angle_x, $angle_y, $x, $y);
} }
function scale($s_x, $s_y, $x, $y) /**
* @param $s_x
* @param $s_y
* @param $x
* @param $y
*/
public function scale($s_x, $s_y, $x, $y)
{ {
$this->_pdf->scale($s_x, $s_y, $x, $y); $this->_pdf->scale($s_x, $s_y, $x, $y);
} }
function translate($t_x, $t_y) /**
* @param $t_x
* @param $t_y
*/
public function translate($t_x, $t_y)
{ {
$this->_pdf->translate($t_x, $t_y); $this->_pdf->translate($t_x, $t_y);
} }
function transform($a, $b, $c, $d, $e, $f) /**
* @param $a
* @param $b
* @param $c
* @param $d
* @param $e
* @param $f
*/
public function transform($a, $b, $c, $d, $e, $f)
{ {
$this->_pdf->transform(array($a, $b, $c, $d, $e, $f)); $this->_pdf->transform(array($a, $b, $c, $d, $e, $f));
} }
function polygon($points, $color, $width = null, $style = array(), $fill = false) /**
* @param array $points
* @param array $color
* @param null $width
* @param array $style
* @param bool $fill
*/
public function polygon($points, $color, $width = null, $style = array(), $fill = false)
{ {
$this->_set_fill_color($color); $this->_set_fill_color($color);
$this->_set_stroke_color($color); $this->_set_stroke_color($color);
@ -651,9 +777,21 @@ class CPDF implements Canvas
} }
$this->_pdf->polygon($points, count($points) / 2, $fill); $this->_pdf->polygon($points, count($points) / 2, $fill);
$this->_set_fill_transparency("Normal", $this->_current_opacity);
$this->_set_line_transparency("Normal", $this->_current_opacity);
} }
function circle($x, $y, $r1, $color, $width = null, $style = null, $fill = false) /**
* @param float $x
* @param float $y
* @param float $r1
* @param array $color
* @param null $width
* @param null $style
* @param bool $fill
*/
public function circle($x, $y, $r1, $color, $width = null, $style = null, $fill = false)
{ {
$this->_set_fill_color($color); $this->_set_fill_color($color);
$this->_set_stroke_color($color); $this->_set_stroke_color($color);
@ -663,23 +801,39 @@ class CPDF implements Canvas
} }
$this->_pdf->ellipse($x, $this->y($y), $r1, 0, 0, 8, 0, 360, 1, $fill); $this->_pdf->ellipse($x, $this->y($y), $r1, 0, 0, 8, 0, 360, 1, $fill);
$this->_set_fill_transparency("Normal", $this->_current_opacity);
$this->_set_line_transparency("Normal", $this->_current_opacity);
} }
function image($img, $x, $y, $w, $h, $resolution = "normal") /**
* @param string $img
* @param float $x
* @param float $y
* @param int $w
* @param int $h
* @param string $resolution
*/
public function image($img, $x, $y, $w, $h, $resolution = "normal")
{ {
list($width, $height, $type) = Helpers::dompdf_getimagesize($img, $this->get_dompdf()->getHttpContext()); list($width, $height, $type) = Helpers::dompdf_getimagesize($img, $this->get_dompdf()->getHttpContext());
$debug_png = $this->_dompdf->get_option("debug_png"); $debug_png = $this->_dompdf->getOptions()->getDebugPng();
if ($debug_png) print "[image:$img|$width|$height|$type]"; if ($debug_png) {
print "[image:$img|$width|$height|$type]";
}
switch ($type) { switch ($type) {
case "jpeg": case "jpeg":
if ($debug_png) print '!!!jpg!!!'; if ($debug_png) {
print '!!!jpg!!!';
}
$this->_pdf->addJpegFromFile($img, $x, $this->y($y) - $h, $w, $h); $this->_pdf->addJpegFromFile($img, $x, $this->y($y) - $h, $w, $h);
break; break;
case "gif": case "gif":
/** @noinspection PhpMissingBreakStatementInspection */
case "bmp": case "bmp":
if ($debug_png) print '!!!bmp or gif!!!'; if ($debug_png) print '!!!bmp or gif!!!';
// @todo use cache for BMP and GIF // @todo use cache for BMP and GIF
@ -702,11 +856,22 @@ class CPDF implements Canvas
} }
} }
function text($x, $y, $text, $font, $size, $color = array(0, 0, 0), $word_space = 0.0, $char_space = 0.0, $angle = 0.0) /**
* @param float $x
* @param float $y
* @param string $text
* @param string $font
* @param float $size
* @param array $color
* @param float $word_space
* @param float $char_space
* @param float $angle
*/
public function text($x, $y, $text, $font, $size, $color = array(0, 0, 0), $word_space = 0.0, $char_space = 0.0, $angle = 0.0)
{ {
$pdf = $this->_pdf; $pdf = $this->_pdf;
$pdf->setColor($color); $this->_set_fill_color($color);
$font .= ".afm"; $font .= ".afm";
$pdf->selectFont($font); $pdf->selectFont($font);
@ -741,11 +906,14 @@ class CPDF implements Canvas
// //
//$pdf->addText($x, $this->y($y) - ($pdf->fonts[$pdf->currentFont]['FontBBox'][3]*$size)/1000, $size, $text, $angle, $word_space, $char_space); //$pdf->addText($x, $this->y($y) - ($pdf->fonts[$pdf->currentFont]['FontBBox'][3]*$size)/1000, $size, $text, $angle, $word_space, $char_space);
$pdf->addText($x, $this->y($y) - $pdf->getFontHeight($size), $size, $text, $angle, $word_space, $char_space); $pdf->addText($x, $this->y($y) - $pdf->getFontHeight($size), $size, $text, $angle, $word_space, $char_space);
$this->_set_fill_transparency("Normal", $this->_current_opacity);
} }
//........................................................................ /**
* @param string $code
function javascript($code) */
public function javascript($code)
{ {
$this->_pdf->addJavascript($code); $this->_pdf->addJavascript($code);
} }
@ -757,13 +925,11 @@ class CPDF implements Canvas
* *
* @param string $anchorname The name of the named destination * @param string $anchorname The name of the named destination
*/ */
function add_named_dest($anchorname) public function add_named_dest($anchorname)
{ {
$this->_pdf->addDestination($anchorname, "Fit"); $this->_pdf->addDestination($anchorname, "Fit");
} }
//........................................................................
/** /**
* Add a link to the pdf * Add a link to the pdf
* *
@ -773,9 +939,8 @@ class CPDF implements Canvas
* @param float $width The width of the link * @param float $width The width of the link
* @param float $height The height of the link * @param float $height The height of the link
*/ */
function add_link($url, $x, $y, $width, $height) public function add_link($url, $x, $y, $width, $height)
{ {
$y = $this->y($y) - $height; $y = $this->y($y) - $height;
if (strpos($url, '#') === 0) { if (strpos($url, '#') === 0) {
@ -784,40 +949,61 @@ class CPDF implements Canvas
if ($name) { if ($name) {
$this->_pdf->addInternalLink($name, $x, $y, $x + $width, $y + $height); $this->_pdf->addInternalLink($name, $x, $y, $x + $width, $y + $height);
} }
} else { } else {
$this->_pdf->addLink(rawurldecode($url), $x, $y, $x + $width, $y + $height); $this->_pdf->addLink(rawurldecode($url), $x, $y, $x + $width, $y + $height);
} }
} }
function get_text_width($text, $font, $size, $word_spacing = 0, $char_spacing = 0) /**
* @param string $text
* @param string $font
* @param float $size
* @param int $word_spacing
* @param int $char_spacing
* @return float|int
*/
public function get_text_width($text, $font, $size, $word_spacing = 0, $char_spacing = 0)
{ {
$this->_pdf->selectFont($font); $this->_pdf->selectFont($font);
return $this->_pdf->getTextWidth($size, $text, $word_spacing, $char_spacing); return $this->_pdf->getTextWidth($size, $text, $word_spacing, $char_spacing);
} }
function register_string_subset($font, $string) /**
* @param $font
* @param $string
*/
public function register_string_subset($font, $string)
{ {
$this->_pdf->registerText($font, $string); $this->_pdf->registerText($font, $string);
} }
function get_font_height($font, $size) /**
* @param string $font
* @param float $size
* @return float|int
*/
public function get_font_height($font, $size)
{ {
$this->_pdf->selectFont($font); $this->_pdf->selectFont($font);
$ratio = $this->_dompdf->get_option("font_height_ratio"); $ratio = $this->_dompdf->getOptions()->getFontHeightRatio();
return $this->_pdf->getFontHeight($size) * $ratio; return $this->_pdf->getFontHeight($size) * $ratio;
} }
/*function get_font_x_height($font, $size) { /*function get_font_x_height($font, $size) {
$this->_pdf->selectFont($font); $this->_pdf->selectFont($font);
$ratio = $this->_dompdf->get_option("font_height_ratio"); $ratio = $this->_dompdf->getOptions()->getFontHeightRatio();
return $this->_pdf->getFontXHeight($size) * $ratio; return $this->_pdf->getFontXHeight($size) * $ratio;
}*/ }*/
function get_font_baseline($font, $size) /**
* @param string $font
* @param float $size
* @return float
*/
public function get_font_baseline($font, $size)
{ {
$ratio = $this->_dompdf->get_option("font_height_ratio"); $ratio = $this->_dompdf->getOptions()->getFontHeightRatio();
return $this->get_font_height($font, $size) / $ratio; return $this->get_font_height($font, $size) / $ratio;
} }
@ -839,7 +1025,7 @@ class CPDF implements Canvas
* @param float $char_space char spacing adjustment * @param float $char_space char spacing adjustment
* @param float $angle angle to write the text at, measured CW starting from the x-axis * @param float $angle angle to write the text at, measured CW starting from the x-axis
*/ */
function page_text($x, $y, $text, $font, $size, $color = array(0, 0, 0), $word_space = 0.0, $char_space = 0.0, $angle = 0.0) public function page_text($x, $y, $text, $font, $size, $color = array(0, 0, 0), $word_space = 0.0, $char_space = 0.0, $angle = 0.0)
{ {
$_t = "text"; $_t = "text";
$this->_page_text[] = compact("_t", "x", "y", "text", "font", "size", "color", "word_space", "char_space", "angle"); $this->_page_text[] = compact("_t", "x", "y", "text", "font", "size", "color", "word_space", "char_space", "angle");
@ -856,13 +1042,16 @@ class CPDF implements Canvas
* @param string $code the script code * @param string $code the script code
* @param string $type the language type for script * @param string $type the language type for script
*/ */
function page_script($code, $type = "text/php") public function page_script($code, $type = "text/php")
{ {
$_t = "script"; $_t = "script";
$this->_page_text[] = compact("_t", "code", "type"); $this->_page_text[] = compact("_t", "code", "type");
} }
function new_page() /**
* @return int
*/
public function new_page()
{ {
$this->_page_number++; $this->_page_number++;
$this->_page_count++; $this->_page_count++;
@ -877,7 +1066,6 @@ class CPDF implements Canvas
*/ */
protected function _add_page_text() protected function _add_page_text()
{ {
if (!count($this->_page_text)) { if (!count($this->_page_text)) {
return; return;
} }
@ -913,31 +1101,50 @@ class CPDF implements Canvas
} }
/** /**
* Streams the PDF directly to the browser * Streams the PDF to the client.
* *
* @param string $filename the name of the PDF file * @param string $filename The filename to present to the client.
* @param array $options associative array, 'Attachment' => 0 or 1, 'compress' => 1 or 0 * @param array $options Associative array: 'compress' => 1 or 0 (default 1); 'Attachment' => 1 or 0 (default 1).
*/ */
function stream($filename, $options = null) public function stream($filename = "document.pdf", $options = array())
{ {
// Add page text if (headers_sent()) {
die("Unable to stream pdf: headers already sent");
}
if (!isset($options["compress"])) $options["compress"] = true;
if (!isset($options["Attachment"])) $options["Attachment"] = true;
$this->_add_page_text(); $this->_add_page_text();
$options["Content-Disposition"] = $filename; $debug = !$options['compress'];
$this->_pdf->stream($options); $tmp = ltrim($this->_pdf->output($debug));
header("Cache-Control: private");
header("Content-Type: application/pdf");
header("Content-Length: " . mb_strlen($tmp, "8bit"));
$filename = str_replace(array("\n", "'"), "", basename($filename, ".pdf")) . ".pdf";
$attachment = $options["Attachment"] ? "attachment" : "inline";
header(Helpers::buildContentDispositionHeader($attachment, $filename));
echo $tmp;
flush();
} }
/** /**
* Returns the PDF as a string * Returns the PDF as a string.
* *
* @param array $options Output options * @param array $options Associative array: 'compress' => 1 or 0 (default 1).
* @return string * @return string
*/ */
function output($options = null) public function output($options = array())
{ {
if (!isset($options["compress"])) $options["compress"] = true;
$this->_add_page_text(); $this->_add_page_text();
$debug = isset($options["compress"]) && $options["compress"] != 1; $debug = !$options['compress'];
return $this->_pdf->output($debug); return $this->_pdf->output($debug);
} }
@ -947,7 +1154,7 @@ class CPDF implements Canvas
* *
* @return string * @return string
*/ */
function get_messages() public function get_messages()
{ {
return $this->_pdf->messages; return $this->_pdf->messages;
} }

View File

@ -138,7 +138,7 @@ class GD implements Canvas
* @param float $aa_factor Anti-aliasing factor, 1 for no AA * @param float $aa_factor Anti-aliasing factor, 1 for no AA
* @param array $bg_color Image background color: array(r,g,b,a), 0 <= r,g,b,a <= 1 * @param array $bg_color Image background color: array(r,g,b,a), 0 <= r,g,b,a <= 1
*/ */
function __construct($size = 'letter', $orientation = "portrait", Dompdf $dompdf, $aa_factor = 1.0, $bg_color = array(1, 1, 1, 0)) public function __construct($size = 'letter', $orientation = "portrait", Dompdf $dompdf, $aa_factor = 1.0, $bg_color = array(1, 1, 1, 0))
{ {
if (!is_array($size)) { if (!is_array($size)) {
@ -157,7 +157,7 @@ class GD implements Canvas
$this->_dompdf = $dompdf; $this->_dompdf = $dompdf;
$this->dpi = $this->get_dompdf()->get_option('dpi'); $this->dpi = $this->get_dompdf()->getOptions()->getDpi();
if ($aa_factor < 1) { if ($aa_factor < 1) {
$aa_factor = 1; $aa_factor = 1;
@ -184,7 +184,10 @@ class GD implements Canvas
$this->new_page(); $this->new_page();
} }
function get_dompdf() /**
* @return Dompdf
*/
public function get_dompdf()
{ {
return $this->_dompdf; return $this->_dompdf;
} }
@ -194,7 +197,7 @@ class GD implements Canvas
* *
* @return resource * @return resource
*/ */
function get_image() public function get_image()
{ {
return $this->_img; return $this->_img;
} }
@ -204,7 +207,7 @@ class GD implements Canvas
* *
* @return float * @return float
*/ */
function get_width() public function get_width()
{ {
return $this->_width / $this->_aa_factor; return $this->_width / $this->_aa_factor;
} }
@ -214,7 +217,7 @@ class GD implements Canvas
* *
* @return float * @return float
*/ */
function get_height() public function get_height()
{ {
return $this->_height / $this->_aa_factor; return $this->_height / $this->_aa_factor;
} }
@ -223,7 +226,7 @@ class GD implements Canvas
* Returns the current page number * Returns the current page number
* @return int * @return int
*/ */
function get_page_number() public function get_page_number()
{ {
return $this->_page_number; return $this->_page_number;
} }
@ -232,7 +235,7 @@ class GD implements Canvas
* Returns the total number of pages in the document * Returns the total number of pages in the document
* @return int * @return int
*/ */
function get_page_count() public function get_page_count()
{ {
return $this->_page_count; return $this->_page_count;
} }
@ -242,7 +245,7 @@ class GD implements Canvas
* *
* @param int $num * @param int $num
*/ */
function set_page_number($num) public function set_page_number($num)
{ {
$this->_page_number = $num; $this->_page_number = $num;
} }
@ -252,7 +255,7 @@ class GD implements Canvas
* *
* @param int $count * @param int $count
*/ */
function set_page_count($count) public function set_page_count($count)
{ {
$this->_page_count = $count; $this->_page_count = $count;
} }
@ -263,7 +266,7 @@ class GD implements Canvas
* @param $opacity * @param $opacity
* @param $mode * @param $mode
*/ */
function set_opacity($opacity, $mode = "Normal") public function set_opacity($opacity, $mode = "Normal")
{ {
// FIXME // FIXME
} }
@ -277,21 +280,18 @@ class GD implements Canvas
*/ */
private function _allocate_color($color) private function _allocate_color($color)
{ {
$a = isset($color["alpha"]) ? $color["alpha"] : 1;
if (isset($color["c"])) { if (isset($color["c"])) {
$color = Helpers::cmyk_to_rgb($color); $color = Helpers::cmyk_to_rgb($color);
} }
// Full opacity if no alpha set list($r, $g, $b) = $color;
if (!isset($color[3]))
$color[3] = 0;
list($r, $g, $b, $a) = $color;
$r *= 255; $r *= 255;
$g *= 255; $g *= 255;
$b *= 255; $b *= 255;
$a *= 127; $a = 127 - ($a * 127);
// Clip values // Clip values
$r = $r > 255 ? 255 : $r; $r = $r > 255 ? 255 : $r;
@ -306,16 +306,17 @@ class GD implements Canvas
$key = sprintf("#%02X%02X%02X%02X", $r, $g, $b, $a); $key = sprintf("#%02X%02X%02X%02X", $r, $g, $b, $a);
if (isset($this->_colors[$key])) if (isset($this->_colors[$key])) {
return $this->_colors[$key]; return $this->_colors[$key];
}
if ($a != 0) if ($a != 0) {
$this->_colors[$key] = imagecolorallocatealpha($this->get_image(), $r, $g, $b, $a); $this->_colors[$key] = imagecolorallocatealpha($this->get_image(), $r, $g, $b, $a);
else } else {
$this->_colors[$key] = imagecolorallocate($this->get_image(), $r, $g, $b); $this->_colors[$key] = imagecolorallocate($this->get_image(), $r, $g, $b);
}
return $this->_colors[$key]; return $this->_colors[$key];
} }
/** /**
@ -355,7 +356,7 @@ class GD implements Canvas
* @param float $width * @param float $width
* @param array $style * @param array $style
*/ */
function line($x1, $y1, $x2, $y2, $color, $width, $style = null) public function line($x1, $y1, $x2, $y2, $color, $width, $style = null)
{ {
// Scale by the AA factor and DPI // Scale by the AA factor and DPI
@ -379,28 +380,26 @@ class GD implements Canvas
for ($i = 0; $i < $style[0] * $this->_aa_factor; $i++) { for ($i = 0; $i < $style[0] * $this->_aa_factor; $i++) {
$gd_style[] = $this->_bg_color; $gd_style[] = $this->_bg_color;
} }
} else { } else {
$i = 0; $i = 0;
foreach ($style as $length) { foreach ($style as $length) {
if ($i % 2 == 0) { if ($i % 2 == 0) {
// 'On' pattern // 'On' pattern
for ($i = 0; $i < $style[0] * $this->_aa_factor; $i++) for ($i = 0; $i < $style[0] * $this->_aa_factor; $i++) {
$gd_style[] = $c; $gd_style[] = $c;
}
} else { } else {
// Off pattern // Off pattern
for ($i = 0; $i < $style[0] * $this->_aa_factor; $i++) for ($i = 0; $i < $style[0] * $this->_aa_factor; $i++) {
$gd_style[] = $this->_bg_color; $gd_style[] = $this->_bg_color;
}
} }
$i++; $i++;
} }
} }
if(!empty($gd_style)) { if (!empty($gd_style)) {
imagesetstyle($this->get_image(), $gd_style); imagesetstyle($this->get_image(), $gd_style);
$c = IMG_COLOR_STYLED; $c = IMG_COLOR_STYLED;
} }
@ -409,10 +408,20 @@ class GD implements Canvas
imagesetthickness($this->get_image(), $width); imagesetthickness($this->get_image(), $width);
imageline($this->get_image(), $x1, $y1, $x2, $y2, $c); imageline($this->get_image(), $x1, $y1, $x2, $y2, $c);
} }
function arc($x1, $y1, $r1, $r2, $astart, $aend, $color, $width, $style = array()) /**
* @param float $x1
* @param float $y1
* @param float $r1
* @param float $r2
* @param float $astart
* @param float $aend
* @param array $color
* @param float $width
* @param array $style
*/
public function arc($x1, $y1, $r1, $r2, $astart, $aend, $color, $width, $style = array())
{ {
// @todo // @todo
} }
@ -432,7 +441,7 @@ class GD implements Canvas
* @param float $width * @param float $width
* @param array $style * @param array $style
*/ */
function rectangle($x1, $y1, $w, $h, $color, $width, $style = null) public function rectangle($x1, $y1, $w, $h, $color, $width, $style = null)
{ {
// Scale by the AA factor and DPI // Scale by the AA factor and DPI
@ -454,7 +463,7 @@ class GD implements Canvas
} }
} }
if(!empty($gd_style)) { if (!empty($gd_style)) {
imagesetstyle($this->get_image(), $gd_style); imagesetstyle($this->get_image(), $gd_style);
$c = IMG_COLOR_STYLED; $c = IMG_COLOR_STYLED;
} }
@ -463,7 +472,6 @@ class GD implements Canvas
imagesetthickness($this->get_image(), $width); imagesetthickness($this->get_image(), $width);
imagerectangle($this->get_image(), $x1, $y1, $x1 + $w, $y1 + $h, $c); imagerectangle($this->get_image(), $x1, $y1, $x1 + $w, $y1 + $h, $c);
} }
/** /**
@ -477,9 +485,8 @@ class GD implements Canvas
* @param float $h * @param float $h
* @param array $color * @param array $color
*/ */
function filled_rectangle($x1, $y1, $w, $h, $color) public function filled_rectangle($x1, $y1, $w, $h, $color)
{ {
// Scale by the AA factor and DPI // Scale by the AA factor and DPI
$x1 = $this->_upscale($x1); $x1 = $this->_upscale($x1);
$y1 = $this->_upscale($y1); $y1 = $this->_upscale($y1);
@ -489,7 +496,6 @@ class GD implements Canvas
$c = $this->_allocate_color($color); $c = $this->_allocate_color($color);
imagefilledrectangle($this->get_image(), $x1, $y1, $x1 + $w, $y1 + $h, $c); imagefilledrectangle($this->get_image(), $x1, $y1, $x1 + $w, $y1 + $h, $c);
} }
/** /**
@ -500,12 +506,12 @@ class GD implements Canvas
* @param float $w * @param float $w
* @param float $h * @param float $h
*/ */
function clipping_rectangle($x1, $y1, $w, $h) public function clipping_rectangle($x1, $y1, $w, $h)
{ {
// @todo // @todo
} }
function clipping_roundrectangle($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL) public function clipping_roundrectangle($x1, $y1, $w, $h, $rTL, $rTR, $rBR, $rBL)
{ {
// @todo // @todo
} }
@ -513,42 +519,77 @@ class GD implements Canvas
/** /**
* Ends the last clipping shape * Ends the last clipping shape
*/ */
function clipping_end() public function clipping_end()
{ {
// @todo // @todo
} }
function save() /**
*
*/
public function save()
{ {
$this->get_dompdf()->set_option('dpi', 72); $this->get_dompdf()->getOptions()->setDpi(72);
} }
function restore() /**
*
*/
public function restore()
{ {
$this->get_dompdf()->set_option('dpi', $this->dpi); $this->get_dompdf()->getOptions()->setDpi($this->dpi);
} }
function rotate($angle, $x, $y) /**
* @param $angle
* @param $x
* @param $y
*/
public function rotate($angle, $x, $y)
{ {
// @todo // @todo
} }
function skew($angle_x, $angle_y, $x, $y) /**
* @param $angle_x
* @param $angle_y
* @param $x
* @param $y
*/
public function skew($angle_x, $angle_y, $x, $y)
{ {
// @todo // @todo
} }
function scale($s_x, $s_y, $x, $y) /**
* @param $s_x
* @param $s_y
* @param $x
* @param $y
*/
public function scale($s_x, $s_y, $x, $y)
{ {
// @todo // @todo
} }
function translate($t_x, $t_y) /**
* @param $t_x
* @param $t_y
*/
public function translate($t_x, $t_y)
{ {
// @todo // @todo
} }
function transform($a, $b, $c, $d, $e, $f) /**
* @param $a
* @param $b
* @param $c
* @param $d
* @param $e
* @param $f
*/
public function transform($a, $b, $c, $d, $e, $f)
{ {
// @todo // @todo
} }
@ -577,12 +618,13 @@ class GD implements Canvas
* @param array $style * @param array $style
* @param bool $fill Fills the polygon if true * @param bool $fill Fills the polygon if true
*/ */
function polygon($points, $color, $width = null, $style = null, $fill = false) public function polygon($points, $color, $width = null, $style = null, $fill = false)
{ {
// Scale each point by the AA factor and DPI // Scale each point by the AA factor and DPI
foreach (array_keys($points) as $i) foreach (array_keys($points) as $i) {
$points[$i] = $this->_upscale($points[$i]); $points[$i] = $this->_upscale($points[$i]);
}
$c = $this->_allocate_color($color); $c = $this->_allocate_color($color);
@ -596,7 +638,7 @@ class GD implements Canvas
} }
} }
if(!empty($gd_style)) { if (!empty($gd_style)) {
imagesetstyle($this->get_image(), $gd_style); imagesetstyle($this->get_image(), $gd_style);
$c = IMG_COLOR_STYLED; $c = IMG_COLOR_STYLED;
} }
@ -604,11 +646,11 @@ class GD implements Canvas
imagesetthickness($this->get_image(), $width); imagesetthickness($this->get_image(), $width);
if ($fill) if ($fill) {
imagefilledpolygon($this->get_image(), $points, count($points) / 2, $c); imagefilledpolygon($this->get_image(), $points, count($points) / 2, $c);
else } else {
imagepolygon($this->get_image(), $points, count($points) / 2, $c); imagepolygon($this->get_image(), $points, count($points) / 2, $c);
}
} }
/** /**
@ -626,9 +668,8 @@ class GD implements Canvas
* @param array $style * @param array $style
* @param bool $fill Fills the circle if true * @param bool $fill Fills the circle if true
*/ */
function circle($x, $y, $r, $color, $width = null, $style = null, $fill = false) public function circle($x, $y, $r, $color, $width = null, $style = null, $fill = false)
{ {
// Scale by the AA factor and DPI // Scale by the AA factor and DPI
$x = $this->_upscale($x); $x = $this->_upscale($x);
$y = $this->_upscale($y); $y = $this->_upscale($y);
@ -646,7 +687,7 @@ class GD implements Canvas
} }
} }
if(!empty($gd_style)) { if (!empty($gd_style)) {
imagesetstyle($this->get_image(), $gd_style); imagesetstyle($this->get_image(), $gd_style);
$c = IMG_COLOR_STYLED; $c = IMG_COLOR_STYLED;
} }
@ -654,11 +695,11 @@ class GD implements Canvas
imagesetthickness($this->get_image(), $width); imagesetthickness($this->get_image(), $width);
if ($fill) if ($fill) {
imagefilledellipse($this->get_image(), $x, $y, $r, $r, $c); imagefilledellipse($this->get_image(), $x, $y, $r, $r, $c);
else } else {
imageellipse($this->get_image(), $x, $y, $r, $r, $c); imageellipse($this->get_image(), $x, $y, $r, $r, $c);
}
} }
/** /**
@ -672,11 +713,12 @@ class GD implements Canvas
* @param int $w width (in pixels) * @param int $w width (in pixels)
* @param int $h height (in pixels) * @param int $h height (in pixels)
* @param string $resolution * @param string $resolution
*
* @return void * @return void
*
* @throws \Exception
* @internal param string $img_type the type (e.g. extension) of the image * @internal param string $img_type the type (e.g. extension) of the image
*/ */
function image($img_url, $x, $y, $w, $h, $resolution = "normal") public function image($img_url, $x, $y, $w, $h, $resolution = "normal")
{ {
$img_type = Cache::detect_type($img_url, $this->get_dompdf()->getHttpContext()); $img_type = Cache::detect_type($img_url, $this->get_dompdf()->getHttpContext());
@ -684,14 +726,14 @@ class GD implements Canvas
return; return;
} }
$func = "imagecreatefrom$img_type"; $func_name = "imagecreatefrom$img_type";
if (!function_exists($func_name)) { if (!function_exists($func_name)) {
if (!method_exists("Dompdf\Helpers", $func_name)) { if (!method_exists("Dompdf\Helpers", $func_name)) {
throw new Exception("Function $func_name() not found. Cannot convert $type image: $image_url. Please install the image PHP extension."); throw new \Exception("Function $func_name() not found. Cannot convert $type image: $img_url. Please install the image PHP extension.");
} }
$func_name = "\\Dompdf\\Helpers::" . $func_name; $func_name = "\\Dompdf\\Helpers::" . $func_name;
} }
$src = @call_user_func($func_name, $image_url); $src = @call_user_func($func_name, $img_url);
if (!$src) { if (!$src) {
return; // Probably should add to $_dompdf_errors or whatever here return; // Probably should add to $_dompdf_errors or whatever here
@ -708,7 +750,6 @@ class GD implements Canvas
$img_h = imagesy($src); $img_h = imagesy($src);
imagecopyresampled($this->get_image(), $src, $x, $y, 0, 0, $w, $h, $img_w, $img_h); imagecopyresampled($this->get_image(), $src, $x, $y, 0, 0, $w, $h, $img_w, $img_h);
} }
/** /**
@ -727,9 +768,8 @@ class GD implements Canvas
* *
* @return void * @return void
*/ */
function text($x, $y, $text, $font, $size, $color = array(0, 0, 0), $word_spacing = 0.0, $char_spacing = 0.0, $angle = 0.0) public function text($x, $y, $text, $font, $size, $color = array(0, 0, 0), $word_spacing = 0.0, $char_spacing = 0.0, $angle = 0.0)
{ {
// Scale by the AA factor and DPI // Scale by the AA factor and DPI
$x = $this->_upscale($x); $x = $this->_upscale($x);
$y = $this->_upscale($y); $y = $this->_upscale($y);
@ -750,10 +790,9 @@ class GD implements Canvas
// FIXME: word spacing // FIXME: word spacing
imagettftext($this->get_image(), $size, $angle, $x, $y + $h, $c, $font, $text); imagettftext($this->get_image(), $size, $angle, $x, $y + $h, $c, $font, $text);
} }
function javascript($code) public function javascript($code)
{ {
// Not implemented // Not implemented
} }
@ -763,7 +802,7 @@ class GD implements Canvas
* *
* @param string $anchorname The name of the named destination * @param string $anchorname The name of the named destination
*/ */
function add_named_dest($anchorname) public function add_named_dest($anchorname)
{ {
// Not implemented // Not implemented
} }
@ -777,7 +816,7 @@ class GD implements Canvas
* @param float $width The width of the link * @param float $width The width of the link
* @param float $height The height of the link * @param float $height The height of the link
*/ */
function add_link($url, $x, $y, $width, $height) public function add_link($url, $x, $y, $width, $height)
{ {
// Not implemented // Not implemented
} }
@ -788,12 +827,16 @@ class GD implements Canvas
* @param string $label label of the value (Creator, Producer, etc.) * @param string $label label of the value (Creator, Producer, etc.)
* @param string $value the text to set * @param string $value the text to set
*/ */
function add_info($label, $value) public function add_info($label, $value)
{ {
// N/A // N/A
} }
function set_default_view($view, $options = array()) /**
* @param string $view
* @param array $options
*/
public function set_default_view($view, $options = array())
{ {
// N/A // N/A
} }
@ -809,7 +852,7 @@ class GD implements Canvas
* *
* @return float * @return float
*/ */
function get_text_width($text, $font, $size, $word_spacing = 0.0, $char_spacing = 0.0) public function get_text_width($text, $font, $size, $word_spacing = 0.0, $char_spacing = 0.0)
{ {
$font = $this->get_ttf_file($font); $font = $this->get_ttf_file($font);
$size = $this->_upscale($size) * self::FONT_SCALE; $size = $this->_upscale($size) * self::FONT_SCALE;
@ -829,16 +872,31 @@ class GD implements Canvas
return $this->_downscale($x2 - $x1) + 1; return $this->_downscale($x2 - $x1) + 1;
} }
function get_ttf_file($font) /**
* @param $font
* @return string
*/
public function get_ttf_file($font)
{ {
if (strpos($font, '.ttf') === false) if ( stripos($font, ".ttf") === false ) {
$font .= ".ttf"; $font .= ".ttf";
}
/*$filename = substr(strtolower(basename($font)), 0, -4); if (!file_exists($font)) {
$font_metrics = $this->_dompdf->getFontMetrics();
if ( in_array($filename, Dompdf::$native_fonts) ) { $font = $font_metrics->getFont($this->_dompdf->getOptions()->getDefaultFont()) . ".ttf";
return "arial.ttf"; if (!file_exists($font)) {
}*/ if (strpos($font, "mono")) {
$font = $font_metrics->getFont("DejaVu Mono") . ".ttf";
} elseif (strpos($font, "sans") !== false) {
$font = $font_metrics->getFont("DejaVu Sans") . ".ttf";
} elseif (strpos($font, "serif")) {
$font = $font_metrics->getFont("DejaVu Serif") . ".ttf";
} else {
$font = $font_metrics->getFont("DejaVu Sans") . ".ttf";
}
}
}
return $font; return $font;
} }
@ -850,7 +908,7 @@ class GD implements Canvas
* @param float $size * @param float $size
* @return float * @return float
*/ */
function get_font_height($font, $size) public function get_font_height($font, $size)
{ {
$size = $this->_upscale($size) * self::FONT_SCALE; $size = $this->_upscale($size) * self::FONT_SCALE;
@ -862,16 +920,21 @@ class GD implements Canvas
private function get_font_height_actual($font, $size) private function get_font_height_actual($font, $size)
{ {
$font = $this->get_ttf_file($font); $font = $this->get_ttf_file($font);
$ratio = $this->_dompdf->get_option("font_height_ratio"); $ratio = $this->_dompdf->getOptions()->getFontHeightRatio();
// FIXME: word spacing // FIXME: word spacing
list(, $y2, , , , $y1) = imagettfbbox($size, 0, $font, "MXjpqytfhl"); // Test string with ascenders, descenders and caps list(, $y2, , , , $y1) = imagettfbbox($size, 0, $font, "MXjpqytfhl"); // Test string with ascenders, descenders and caps
return ($y2 - $y1) * $ratio; return ($y2 - $y1) * $ratio;
} }
function get_font_baseline($font, $size) /**
* @param string $font
* @param float $size
* @return float
*/
public function get_font_baseline($font, $size)
{ {
$ratio = $this->_dompdf->get_option("font_height_ratio"); $ratio = $this->_dompdf->getOptions()->getFontHeightRatio();
return $this->get_font_height($font, $size) / $ratio; return $this->get_font_height($font, $size) / $ratio;
} }
@ -880,7 +943,7 @@ class GD implements Canvas
* *
* Subsequent drawing operations will appear on the new page. * Subsequent drawing operations will appear on the new page.
*/ */
function new_page() public function new_page()
{ {
$this->_page_number++; $this->_page_number++;
$this->_page_count++; $this->_page_count++;
@ -895,39 +958,99 @@ class GD implements Canvas
$this->_imgs[] = $this->_img; $this->_imgs[] = $this->_img;
} }
function open_object() public function open_object()
{ {
// N/A // N/A
} }
function close_object() public function close_object()
{ {
// N/A // N/A
} }
function add_object() public function add_object()
{ {
// N/A // N/A
} }
function page_text() public function page_text()
{ {
// N/A // N/A
} }
/** /**
* Streams the image directly to the browser * Streams the image to the client.
* *
* @param string $filename the name of the image file (ignored) * @param string $filename The filename to present to the client.
* @param array $options associative array, 'type' => jpeg|jpg|png, 'quality' => 0 - 100 (jpeg only) * @param array $options Associative array: 'type' => jpeg|jpg|png; 'quality' => 0 - 100 (JPEG only);
* 'page' => Number of the page to output (defaults to the first); 'Attachment': 1 or 0 (default 1).
*/ */
function stream($filename, $options = null) public function stream($filename, $options = array())
{ {
if (headers_sent()) {
die("Unable to stream image: headers already sent");
}
$img = $this->_imgs[0]; if (!isset($options["type"])) $options["type"] = "png";
if (!isset($options["Attachment"])) $options["Attachment"] = true;
$type = strtolower($options["type"]);
if (isset($options['page']) && isset($this->_imgs[$options['page'] - 1])) { switch ($type) {
$img = $this->_imgs[$options['page'] - 1]; case "jpg":
case "jpeg":
$contentType = "image/jpeg";
$extension = ".jpg";
break;
case "png":
default:
$contentType = "image/png";
$extension = ".png";
break;
}
header("Cache-Control: private");
header("Content-Type: $contentType");
$filename = str_replace(array("\n", "'"), "", basename($filename, ".$type")) . $extension;
$attachment = $options["Attachment"] ? "attachment" : "inline";
header(Helpers::buildContentDispositionHeader($attachment, $filename));
$this->_output($options);
flush();
}
/**
* Returns the image as a string.
*
* @param array $options Associative array: 'type' => jpeg|jpg|png; 'quality' => 0 - 100 (JPEG only);
* 'page' => Number of the page to output (defaults to the first).
* @return string
*/
public function output($options = array())
{
ob_start();
$this->_output($options);
return ob_get_clean();
}
/**
* Outputs the image stream directly.
*
* @param array $options Associative array: 'type' => jpeg|jpg|png; 'quality' => 0 - 100 (JPEG only);
* 'page' => Number of the page to output (defaults to the first).
*/
private function _output($options = array())
{
if (!isset($options["type"])) $options["type"] = "png";
if (!isset($options["page"])) $options["page"] = 1;
$type = strtolower($options["type"]);
if (isset($this->_imgs[$options["page"] - 1])) {
$img = $this->_imgs[$options["page"] - 1];
} else {
$img = $this->_imgs[0];
} }
// Perform any antialiasing // Perform any antialiasing
@ -942,114 +1065,23 @@ class GD implements Canvas
$dst = $img; $dst = $img;
} }
if (!isset($options["type"]))
$options["type"] = "png";
$type = strtolower($options["type"]);
header("Cache-Control: private");
$filename = str_replace(array("\n", "'"), "", basename($filename));
switch ($type) { switch ($type) {
case "jpg": case "jpg":
case "jpeg": case "jpeg":
$filename .= ".jpg"; if (!isset($options["quality"])) {
break;
case "png":
default:
$filename .= ".png";
break;
}
$attach = (isset($options["Attachment"]) && $options["Attachment"]) ? "attachment" : "inline";
// detect the character encoding of the incoming file
$encoding = mb_detect_encoding($filename);
$fallbackfilename = mb_convert_encoding($filename, "ISO-8859-1", $encoding);
$encodedfallbackfilename = rawurlencode($fallbackfilename);
$encodedfilename = rawurlencode($filename);
header("Content-Disposition: $attach; filename=". $encodedfallbackfilename ."; filename*=UTF-8''$encodedfilename");
switch ($type) {
case "jpg":
case "jpeg":
if (!isset($options["quality"]))
$options["quality"] = 75; $options["quality"] = 75;
}
header("Content-type: image/jpeg"); imagejpeg($dst, null, $options["quality"]);
imagejpeg($dst, '', $options["quality"]);
break; break;
case "png": case "png":
default: default:
header("Content-type: image/png");
imagepng($dst); imagepng($dst);
break; break;
} }
if ($this->_aa_factor != 1)
imagedestroy($dst);
}
/**
* Returns the PNG as a string
*
* @param array $options associative array, 'type' => jpeg|jpg|png, 'quality' => 0 - 100 (jpeg only)
* @return string
*/
function output($options = null)
{
$img = $this->_imgs[0];
if (isset($options['page']) && isset($this->_imgs[$options['page'] - 1])) {
$img = $this->_imgs[$options['page'] - 1];
}
if ($this->_aa_factor != 1) { if ($this->_aa_factor != 1) {
$dst_w = $this->_actual_width / $this->_aa_factor;
$dst_h = $this->_actual_height / $this->_aa_factor;
$dst = imagecreatetruecolor($dst_w, $dst_h);
imagecopyresampled($dst, $img, 0, 0, 0, 0,
$dst_w, $dst_h,
$this->_actual_width, $this->_actual_height);
} else {
$dst = $img;
}
if (!isset($options["type"]))
$options["type"] = "png";
$type = $options["type"];
ob_start();
switch ($type) {
case "jpg":
case "jpeg":
if (!isset($options["quality"]))
$options["quality"] = 75;
imagejpeg($dst, '', $options["quality"]);
break;
case "png":
default:
imagepng($dst);
break;
}
$image = ob_get_clean();
if ($this->_aa_factor != 1)
imagedestroy($dst); imagedestroy($dst);
}
return $image;
} }
} }

File diff suppressed because it is too large Load Diff

View File

@ -142,26 +142,51 @@ interface Canvas
/** /**
* Rotate * Rotate
*
* @param float $angle angle in degrees for counter-clockwise rotation
* @param float $x Origin abscissa
* @param float $y Origin ordinate
*/ */
function rotate($angle, $x, $y); function rotate($angle, $x, $y);
/** /**
* Skew * Skew
*
* @param float $angle_x
* @param float $angle_y
* @param float $x Origin abscissa
* @param float $y Origin ordinate
*/ */
function skew($angle_x, $angle_y, $x, $y); function skew($angle_x, $angle_y, $x, $y);
/** /**
* Scale * Scale
*
* @param float $s_x scaling factor for width as percent
* @param float $s_y scaling factor for height as percent
* @param float $x Origin abscissa
* @param float $y Origin ordinate
*/ */
function scale($s_x, $s_y, $x, $y); function scale($s_x, $s_y, $x, $y);
/** /**
* Translate * Translate
*
* @param float $t_x movement to the right
* @param float $t_y movement to the bottom
*/ */
function translate($t_x, $t_y); function translate($t_x, $t_y);
/** /**
* Transform * Transform
*
* @param $a
* @param $b
* @param $c
* @param $d
* @param $e
* @param $f
* @return
*/ */
function transform($a, $b, $c, $d, $e, $f); function transform($a, $b, $c, $d, $e, $f);
@ -236,8 +261,6 @@ interface Canvas
* @param array $color Color * @param array $color Color
* @param float $width * @param float $width
* @param array $style * @param array $style
*
* @return void
*/ */
function arc($x, $y, $r1, $r2, $astart, $aend, $color, $width, $style = array()); function arc($x, $y, $r1, $r2, $astart, $aend, $color, $width, $style = array());
@ -254,8 +277,6 @@ interface Canvas
* @param float $word_space word spacing adjustment * @param float $word_space word spacing adjustment
* @param float $char_space char spacing adjustment * @param float $char_space char spacing adjustment
* @param float $angle angle * @param float $angle angle
*
* @return void
*/ */
function text($x, $y, $text, $font, $size, $color = array(0, 0, 0), $word_space = 0.0, $char_space = 0.0, $angle = 0.0); function text($x, $y, $text, $font, $size, $color = array(0, 0, 0), $word_space = 0.0, $char_space = 0.0, $angle = 0.0);
@ -274,8 +295,6 @@ interface Canvas
* @param float $y The y position of the link * @param float $y The y position of the link
* @param float $width The width of the link * @param float $width The width of the link
* @param float $height The height of the link * @param float $height The height of the link
*
* @return void
*/ */
function add_link($url, $x, $y, $width, $height); function add_link($url, $x, $y, $width, $height);
@ -320,6 +339,21 @@ interface Canvas
*/ */
function get_font_baseline($font, $size); function get_font_baseline($font, $size);
/**
* Returns the PDF's width in points
*
* @return float
*/
function get_width();
/**
* Return the image's height in pixels
*
* @return float
*/
function get_height();
/** /**
* Returns the font x-height, in points * Returns the font x-height, in points
* *
@ -371,18 +405,18 @@ interface Canvas
function new_page(); function new_page();
/** /**
* Streams the PDF directly to the browser * Streams the PDF directly to the browser.
* *
* @param string $filename the name of the PDF file * @param string $filename The filename to present to the browser.
* @param array $options associative array, 'Attachment' => 0 or 1, 'compress' => 1 or 0 * @param array $options Associative array: 'compress' => 1 or 0 (default 1); 'Attachment' => 1 or 0 (default 1).
*/ */
function stream($filename, $options = null); function stream($filename, $options = array());
/** /**
* Returns the PDF as a string * Returns the PDF as a string.
* *
* @param array $options associative array: 'compress' => 1 or 0 * @param array $options Associative array: 'compress' => 1 or 0 (default 1).
* @return string * @return string
*/ */
function output($options = null); function output($options = array());
} }

View File

@ -34,7 +34,7 @@ class CanvasFactory
*/ */
static function get_instance(Dompdf $dompdf, $paper = null, $orientation = null, $class = null) static function get_instance(Dompdf $dompdf, $paper = null, $orientation = null, $class = null)
{ {
$backend = strtolower($dompdf->get_option('pdf_backend')); $backend = strtolower($dompdf->getOptions()->getPdfBackend());
if (isset($class) && class_exists($class, false)) { if (isset($class) && class_exists($class, false)) {
$class .= "_Adapter"; $class .= "_Adapter";

View File

@ -7,8 +7,6 @@
*/ */
namespace Dompdf; namespace Dompdf;
use Dompdf\Exception;
use Dompdf\Frame;
use Dompdf\FrameDecorator\Table as TableFrameDecorator; use Dompdf\FrameDecorator\Table as TableFrameDecorator;
use Dompdf\FrameDecorator\TableCell as TableCellFrameDecorator; use Dompdf\FrameDecorator\TableCell as TableCellFrameDecorator;
@ -513,7 +511,6 @@ class Cellmap
$display === "inline-table" || $display === "inline-table" ||
in_array($display, TableFrameDecorator::$ROW_GROUPS) in_array($display, TableFrameDecorator::$ROW_GROUPS)
) { ) {
$start_row = $this->__row; $start_row = $this->__row;
foreach ($frame->get_children() as $child) { foreach ($frame->get_children() as $child) {
// Ignore all Text frames and :before/:after pseudo-selector elements. // Ignore all Text frames and :before/:after pseudo-selector elements.
@ -535,7 +532,6 @@ class Cellmap
$this->_frames[$key]["frame"] = $frame; $this->_frames[$key]["frame"] = $frame;
if ($display !== "table-row" && $collapse) { if ($display !== "table-row" && $collapse) {
$bp = $style->get_border_properties(); $bp = $style->get_border_properties();
// Resolve the borders // Resolve the borders
@ -621,8 +617,14 @@ class Cellmap
list($h, $v) = $this->_table->get_style()->border_spacing; list($h, $v) = $this->_table->get_style()->border_spacing;
// Border spacing is effectively a margin between cells // Border spacing is effectively a margin between cells
$v = $style->length_in_pt($v) / 2; $v = $style->length_in_pt($v);
$h = $style->length_in_pt($h) / 2; if (is_numeric($v)) {
$v = $v / 2;
}
$h = $style->length_in_pt($h);
if (is_numeric($h)) {
$h = $h / 2;
}
$style->margin = "$v $h"; $style->margin = "$v $h";
// The additional 1/2 width gets added to the table proper // The additional 1/2 width gets added to the table proper
@ -741,7 +743,7 @@ class Cellmap
$this->_cells[$r][$c] = null; $this->_cells[$r][$c] = null;
unset($this->_cells[$r][$c]); unset($this->_cells[$r][$c]);
// has multiple rows? // has multiple rows?
if (isset($this->_frames[$id]) && count($this->_frames[$id]["rows"]) > 1) { if (isset($this->_frames[$id]) && count($this->_frames[$id]["rows"]) > 1) {
// remove just the desired row, but leave the frame // remove just the desired row, but leave the frame
@ -797,7 +799,7 @@ class Cellmap
$g_key = $group->get_id(); $g_key = $group->get_id();
$r_key = $last_row->get_id(); $r_key = $last_row->get_id();
$r_rows = $this->_frames[$r_key]["rows"]; $r_rows = $this->_frames[$g_key]["rows"];
$this->_frames[$g_key]["rows"] = range($this->_frames[$g_key]["rows"][0], end($r_rows)); $this->_frames[$g_key]["rows"] = range($this->_frames[$g_key]["rows"][0], end($r_rows));
} }

View File

@ -32,11 +32,11 @@ class AttributeTranslator
'left' => 'float: left;', 'left' => 'float: left;',
'right' => 'float: right;' 'right' => 'float: right;'
), ),
'border' => 'border: %0.2F px solid;', 'border' => 'border: %0.2Fpx solid;',
'height' => 'height: %s px;', 'height' => 'height: %spx;',
'hspace' => 'padding-left: %1$0.2F px; padding-right: %1$0.2F px;', 'hspace' => 'padding-left: %1$0.2Fpx; padding-right: %1$0.2Fpx;',
'vspace' => 'padding-top: %1$0.2F px; padding-bottom: %1$0.2F px;', 'vspace' => 'padding-top: %1$0.2Fpx; padding-bottom: %1$0.2Fpx;',
'width' => 'width: %s px;', 'width' => 'width: %spx;',
), ),
'table' => array( 'table' => array(
'align' => array( 'align' => array(
@ -89,6 +89,10 @@ class AttributeTranslator
'h6' => array( 'h6' => array(
'align' => 'text-align: %s;', 'align' => 'text-align: %s;',
), ),
//TODO: translate more form element attributes
'input' => array(
'size' => '!set_input_width'
),
'p' => array( 'p' => array(
'align' => 'text-align: %s;', 'align' => 'text-align: %s;',
), ),
@ -508,6 +512,23 @@ class AttributeTranslator
return ltrim($style, "; "); return ltrim($style, "; ");
} }
/**
* @param \DOMElement $node
* @param string $value
*
* @return null|string
*/
static protected function _set_input_width(\DOMElement $node, $value)
{
if (empty($value)) { return null; }
if ($node->hasAttribute("type") && in_array(strtolower($node->getAttribute("type")), array("text","password"))) {
return sprintf("width: %Fem", (((int)$value * .65)+2));
} else {
return sprintf("width: %upx;", (int)$value);
}
}
/** /**
* @param \DOMElement $node * @param \DOMElement $node
* @param string $value * @param string $value

View File

@ -10,6 +10,8 @@
namespace Dompdf\Css; namespace Dompdf\Css;
use Dompdf\Helpers;
class Color class Color
{ {
static $cssColorNames = array( static $cssColorNames = array(
@ -162,6 +164,10 @@ class Color
"yellowgreen" => "9ACD32", "yellowgreen" => "9ACD32",
); );
/**
* @param $color
* @return array|mixed|null|string
*/
static function parse($color) static function parse($color)
{ {
if (is_array($color)) { if (is_array($color)) {
@ -191,10 +197,18 @@ class Color
// #rgb format // #rgb format
if ($length == 4 && $color[0] === "#") { if ($length == 4 && $color[0] === "#") {
return $cache[$color] = self::getArray($color[1] . $color[1] . $color[2] . $color[2] . $color[3] . $color[3]); return $cache[$color] = self::getArray($color[1] . $color[1] . $color[2] . $color[2] . $color[3] . $color[3]);
} // #rgba format
else if ($length == 5 && $color[0] === "#") {
$alpha = round(hexdec($color[4] . $color[4])/255, 2);
return $cache[$color] = self::getArray($color[1] . $color[1] . $color[2] . $color[2] . $color[3] . $color[3], $alpha);
} // #rrggbb format } // #rrggbb format
else if ($length == 7 && $color[0] === "#") { else if ($length == 7 && $color[0] === "#") {
return $cache[$color] = self::getArray(mb_substr($color, 1, 6)); return $cache[$color] = self::getArray(mb_substr($color, 1, 6));
} // rgb( r,g,b ) / rgbaa( r,g,b,α ) format } // #rrggbbaa format
else if ($length == 9 && $color[0] === "#") {
$alpha = round(hexdec(mb_substr($color, 7, 2))/255, 2);
return $cache[$color] = self::getArray(mb_substr($color, 1, 8), $alpha);
} // rgb( r,g,b ) / rgba( r,g,b,α ) format
else if (mb_strpos($color, "rgb") !== false) { else if (mb_strpos($color, "rgb") !== false) {
$i = mb_strpos($color, "("); $i = mb_strpos($color, "(");
$j = mb_strpos($color, ")"); $j = mb_strpos($color, ")");
@ -208,12 +222,12 @@ class Color
// alpha transparency // alpha transparency
// FIXME: not currently using transparency // FIXME: not currently using transparency
$alpha = 1; $alpha = 1.0;
if (count($triplet) == 4) { if (count($triplet) == 4) {
$alpha = (float)(trim(array_pop($triplet))); $alpha = (float)(trim(array_pop($triplet)));
// bad value, set to fully opaque // bad value, set to fully opaque
if ($alpha > 1 || $alpha < 0) { if ($alpha > 1.0 || $alpha < 0.0) {
$alpha = 1; $alpha = 1.0;
} }
} }
@ -224,12 +238,12 @@ class Color
foreach (array_keys($triplet) as $c) { foreach (array_keys($triplet) as $c) {
$triplet[$c] = trim($triplet[$c]); $triplet[$c] = trim($triplet[$c]);
if ($triplet[$c][mb_strlen($triplet[$c]) - 1] === "%") { if (Helpers::is_percent($triplet[$c])) {
$triplet[$c] = round($triplet[$c] * 2.55); $triplet[$c] = round((float)$triplet[$c] * 2.55);
} }
} }
return $cache[$color] = self::getArray(vsprintf("%02X%02X%02X", $triplet)); return $cache[$color] = self::getArray(vsprintf("%02X%02X%02X", $triplet), $alpha);
} }
@ -260,9 +274,14 @@ class Color
return null; return null;
} }
static function getArray($color) /**
* @param $color
* @param float $alpha
* @return array
*/
static function getArray($color, $alpha = 1.0)
{ {
$c = array(null, null, null, null, "hex" => null); $c = array(null, null, null, null, "alpha" => $alpha, "hex" => null);
if (is_array($color)) { if (is_array($color)) {
$c = $color; $c = $color;
@ -270,6 +289,7 @@ class Color
$c["m"] = $c[1]; $c["m"] = $c[1];
$c["y"] = $c[2]; $c["y"] = $c[2];
$c["k"] = $c[3]; $c["k"] = $c[3];
$c["alpha"] = $alpha;
$c["hex"] = "cmyk($c[0],$c[1],$c[2],$c[3])"; $c["hex"] = "cmyk($c[0],$c[1],$c[2],$c[3])";
} else { } else {
$c[0] = hexdec(mb_substr($color, 0, 2)) / 0xff; $c[0] = hexdec(mb_substr($color, 0, 2)) / 0xff;
@ -278,7 +298,8 @@ class Color
$c["r"] = $c[0]; $c["r"] = $c[0];
$c["g"] = $c[1]; $c["g"] = $c[1];
$c["b"] = $c[2]; $c["b"] = $c[2];
$c["hex"] = "#$color"; $c["alpha"] = $alpha;
$c["hex"] = sprintf("#%s%02X", mb_substr($color, 0, 6), round($alpha * 255));
} }
return $c; return $c;

File diff suppressed because it is too large Load Diff

View File

@ -9,6 +9,7 @@
*/ */
namespace Dompdf\Css; namespace Dompdf\Css;
use DOMElement;
use DOMXPath; use DOMXPath;
use Dompdf\Dompdf; use Dompdf\Dompdf;
use Dompdf\Helpers; use Dompdf\Helpers;
@ -55,12 +56,29 @@ class Stylesheet
*/ */
const ORIG_AUTHOR = 3; const ORIG_AUTHOR = 3;
/*
* The highest possible specificity is 0x01000000 (and that is only for author
* stylesheets, as it is for inline styles). Origin precedence can be achieved by
* adding multiples of 0x10000000 to the actual specificity. Important
* declarations are handled in Style; though technically they should be handled
* here so that user important declarations can be made to take precedence over
* user important declarations, this doesn't matter in practice as Dompdf does
* not support user stylesheets, and user agent stylesheets can not include
* important declarations.
*/
private static $_stylesheet_origins = array( private static $_stylesheet_origins = array(
self::ORIG_UA => -0x0FFFFFFF, // user agent style sheets self::ORIG_UA => 0x00000000, // user agent declarations
self::ORIG_USER => -0x0000FFFF, // user normal style sheets self::ORIG_USER => 0x10000000, // user normal declarations
self::ORIG_AUTHOR => 0x00000000, // author normal style sheets self::ORIG_AUTHOR => 0x30000000, // author normal declarations
); );
/*
* Non-CSS presentational hints (i.e. HTML 4 attributes) are handled as if added
* to the beginning of an author stylesheet, i.e. anything in author stylesheets
* should override them.
*/
const SPEC_NON_CSS = 0x20000000;
/** /**
* Current dompdf instance * Current dompdf instance
* *
@ -135,6 +153,7 @@ class Stylesheet
*/ */
static $ACCEPTED_DEFAULT_MEDIA_TYPE = "print"; static $ACCEPTED_DEFAULT_MEDIA_TYPE = "print";
static $ACCEPTED_GENERIC_MEDIA_TYPES = array("all", "static", "visual", "bitmap", "paged", "dompdf"); static $ACCEPTED_GENERIC_MEDIA_TYPES = array("all", "static", "visual", "bitmap", "paged", "dompdf");
static $VALID_MEDIA_TYPES = array("all", "aural", "bitmap", "braille", "dompdf", "embossed", "handheld", "paged", "print", "projection", "screen", "speech", "static", "tty", "tv", "visual");
/** /**
* @var FontMetrics * @var FontMetrics
@ -253,23 +272,24 @@ class Stylesheet
throw new Exception("CSS rule must be keyed by a string."); throw new Exception("CSS rule must be keyed by a string.");
} }
if (isset($this->_styles[$key])) { if (!isset($this->_styles[$key])) {
$this->_styles[$key]->merge($style); $this->_styles[$key] = array();
} else {
$this->_styles[$key] = clone $style;
} }
$new_style = clone $style;
$this->_styles[$key]->set_origin($this->_current_origin); $new_style->set_origin($this->_current_origin);
$this->_styles[$key][] = $new_style;
} }
/** /**
* lookup a specifc Style object * lookup a specifc Style collection
* *
* lookup() returns the Style specified by $key, or null if the Style is * lookup() returns the Style collection specified by $key, or null if the Style is
* not found. * not found.
* *
* @param string $key the selector of the requested Style * @param string $key the selector of the requested Style
* @return Style * @return Style
*
* @Fixme _styles is a two dimensional array. It should produce wrong results
*/ */
function lookup($key) function lookup($key)
{ {
@ -295,9 +315,13 @@ class Stylesheet
* load and parse a CSS string * load and parse a CSS string
* *
* @param string $css * @param string $css
* @param int $origin
*/ */
function load_css(&$css) function load_css(&$css, $origin = self::ORIG_AUTHOR)
{ {
if ($origin) {
$this->_current_origin = $origin;
}
$this->_parse_css($css); $this->_parse_css($css);
} }
@ -336,14 +360,12 @@ class Stylesheet
$file = Helpers::build_url($this->_protocol, $this->_base_host, $this->_base_path, $filename); $file = Helpers::build_url($this->_protocol, $this->_base_host, $this->_base_path, $filename);
} }
set_error_handler(array("\\Dompdf\\Helpers", "record_warnings")); list($css, $http_response_header) = Helpers::getFileContent($file, $this->_dompdf->getHttpContext());
$css = file_get_contents($file, null, $this->_dompdf->get_http_context());
restore_error_handler();
$good_mime_type = true; $good_mime_type = true;
// See http://the-stickman.com/web-development/php/getting-http-response-headers-when-using-file_get_contents/ // See http://the-stickman.com/web-development/php/getting-http-response-headers-when-using-file_get_contents/
if (isset($http_response_header) && !$this->_dompdf->get_quirksmode()) { if (isset($http_response_header) && !$this->_dompdf->getQuirksmode()) {
foreach ($http_response_header as $_header) { foreach ($http_response_header as $_header) {
if (preg_match("@Content-Type:\s*([\w/]+)@i", $_header, $matches) && if (preg_match("@Content-Type:\s*([\w/]+)@i", $_header, $matches) &&
($matches[1] !== "text/css") ($matches[1] !== "text/css")
@ -367,11 +389,9 @@ class Stylesheet
* *
* @param string $selector * @param string $selector
* @param int $origin : * @param int $origin :
* - ua: user agent style sheets * - Stylesheet::ORIG_UA: user agent style sheet
* - un: user normal style sheets * - Stylesheet::ORIG_USER: user style sheet
* - an: author normal style sheets * - Stylesheet::ORIG_AUTHOR: author style sheet
* - ai: author important style sheets
* - ui: user important style sheets
* *
* @return int * @return int
*/ */
@ -398,20 +418,20 @@ class Stylesheet
//this can lead to a too small specificity //this can lead to a too small specificity
//see _css_selector_to_xpath //see _css_selector_to_xpath
if (!in_array($selector[0], array(" ", ">", ".", "#", "+", ":", "[")) /* && $selector !== "*"*/) { if (!in_array($selector[0], array(" ", ">", ".", "#", "+", ":", "[")) && $selector !== "*") {
$d++; $d++;
} }
if ($this->_dompdf->get_option('debugCss')) { if ($this->_dompdf->getOptions()->getDebugCss()) {
/*DEBUGCSS*/ /*DEBUGCSS*/
print "<pre>\n"; print "<pre>\n";
/*DEBUGCSS*/ /*DEBUGCSS*/
printf("_specificity(): 0x%08x \"%s\"\n", ($a << 24) | ($b << 16) | ($c << 8) | ($d), $selector); printf("_specificity(): 0x%08x \"%s\"\n", self::$_stylesheet_origins[$origin] + (($a << 24) | ($b << 16) | ($c << 8) | ($d)), $selector);
/*DEBUGCSS*/ /*DEBUGCSS*/
print "</pre>"; print "</pre>";
} }
return self::$_stylesheet_origins[$origin] + ($a << 24) | ($b << 16) | ($c << 8) | ($d); return self::$_stylesheet_origins[$origin] + (($a << 24) | ($b << 16) | ($c << 8) | ($d));
} }
/** /**
@ -427,16 +447,19 @@ class Stylesheet
{ {
// Collapse white space and strip whitespace around delimiters // Collapse white space and strip whitespace around delimiters
// $search = array("/\\s+/", "/\\s+([.>#+:])\\s+/"); //$search = array("/\\s+/", "/\\s+([.>#+:])\\s+/");
// $replace = array(" ", "\\1"); //$replace = array(" ", "\\1");
// $selector = preg_replace($search, $replace, trim($selector)); //$selector = preg_replace($search, $replace, trim($selector));
// Initial query (non-absolute) // Initial query (non-absolute)
$query = "//"; $query = "//";
// Will contain :before and :after if they must be created // Will contain :before and :after
$pseudo_elements = array(); $pseudo_elements = array();
// Will contain :link, etc
$pseudo_classes = array();
// Parse the selector // Parse the selector
//$s = preg_split("/([ :>.#+])/", $selector, -1, PREG_SPLIT_DELIM_CAPTURE); //$s = preg_split("/([ :>.#+])/", $selector, -1, PREG_SPLIT_DELIM_CAPTURE);
@ -466,18 +489,22 @@ class Stylesheet
// Eat characters up to the next delimiter // Eat characters up to the next delimiter
$tok = ""; $tok = "";
$in_attr = false; $in_attr = false;
$in_func = false;
while ($i < $len) { while ($i < $len) {
$c = $selector[$i]; $c = $selector[$i];
$c_prev = $selector[$i - 1]; $c_prev = $selector[$i - 1];
if (!$in_attr && in_array($c, $delimiters)) { if (!$in_func && !$in_attr && in_array($c, $delimiters) && !(($c == $c_prev) == ":")) {
break; break;
} }
if ($c_prev === "[") { if ($c_prev === "[") {
$in_attr = true; $in_attr = true;
} }
if ($c_prev === "(") {
$in_func = true;
}
$tok .= $selector[$i++]; $tok .= $selector[$i++];
@ -485,6 +512,10 @@ class Stylesheet
$in_attr = false; $in_attr = false;
break; break;
} }
if ($in_func && $c === ")") {
$in_func = false;
break;
}
} }
switch ($s) { switch ($s) {
@ -546,7 +577,7 @@ class Stylesheet
case ":": case ":":
$i2 = $i - strlen($tok) - 2; // the char before ":" $i2 = $i - strlen($tok) - 2; // the char before ":"
if (!isset($selector[$i2]) || in_array($selector[$i2], $delimiters)) { if (($i2 < 0 || !isset($selector[$i2]) || (in_array($selector[$i2], $delimiters) && $selector[$i2] != ":")) && substr($query, -1) != "*") {
$query .= "*"; $query .= "*";
} }
@ -576,12 +607,17 @@ class Stylesheet
break; break;
// an+b, n, odd, and even // an+b, n, odd, and even
/** @noinspection PhpMissingBreakStatementInspection */
case "nth-last-of-type": case "nth-last-of-type":
case "nth-last-child":
$last = true; $last = true;
case "nth-of-type": case "nth-of-type":
case "nth-child": //FIXME: this fix-up is pretty ugly, would parsing the selector in reverse work better generally?
$descendant_delimeter = strrpos($query, "::");
$isChild = substr($query, $descendant_delimeter-5, 5) == "child";
$el = substr($query, $descendant_delimeter+2);
$query = substr($query, 0, strrpos($query, "/")) . ($isChild ? "/" : "//") . $el;
$pseudo_classes[$tok] = true;
$p = $i + 1; $p = $i + 1;
$nth = trim(mb_substr($selector, $p, strpos($selector, ")", $i) - $p)); $nth = trim(mb_substr($selector, $p, strpos($selector, ")", $i) - $p));
@ -602,16 +638,73 @@ class Stylesheet
$query .= "[$condition]"; $query .= "[$condition]";
$tok = ""; $tok = "";
break; break;
/** @noinspection PhpMissingBreakStatementInspection */
case "nth-last-child":
$last = true;
case "nth-child":
//FIXME: this fix-up is pretty ugly, would parsing the selector in reverse work better generally?
$descendant_delimeter = strrpos($query, "::");
$isChild = substr($query, $descendant_delimeter-5, 5) == "child";
$el = substr($query, $descendant_delimeter+2);
$query = substr($query, 0, strrpos($query, "/")) . ($isChild ? "/" : "//") . "*";
$pseudo_classes[$tok] = true;
$p = $i + 1;
$nth = trim(mb_substr($selector, $p, strpos($selector, ")", $i) - $p));
// 1
if (preg_match("/^\d+$/", $nth)) {
$condition = "position() = $nth";
} // odd
elseif ($nth === "odd") {
$condition = "(position() mod 2) = 1";
} // even
elseif ($nth === "even") {
$condition = "(position() mod 2) = 0";
} // an+b
else {
$condition = $this->_selector_an_plus_b($nth, $last);
}
$query .= "[$condition]";
if ($el != "*") {
$query .= "[name() = '$el']";
}
$tok = "";
break;
//TODO: bit of a hack attempt at matches support, currently only matches against elements
case "matches":
$pseudo_classes[$tok] = true;
$p = $i + 1;
$matchList = trim(mb_substr($selector, $p, strpos($selector, ")", $i) - $p));
// Tag names are case-insensitive
$elements = array_map("trim", explode(",", strtolower($matchList)));
foreach ($elements as &$element) {
$element = "name() = '$element'";
}
$query .= "[" . implode(" or ", $elements) . "]";
$tok = "";
break;
case "link": case "link":
$query .= "[@href]"; $query .= "[@href]";
$tok = ""; $tok = "";
break; break;
case "first-line": // TODO case "first-line":
case "first-letter": // TODO case ":first-line":
case "first-letter":
case ":first-letter":
// TODO
$el = trim($tok, ":");
$pseudo_elements[$el] = true;
break;
// N/A // N/A
case "focus":
case "active": case "active":
case "hover": case "hover":
case "visited": case "visited":
@ -621,11 +714,13 @@ class Stylesheet
/* Pseudo-elements */ /* Pseudo-elements */
case "before": case "before":
case ":before":
case "after": case "after":
if ($first_pass) { case ":after":
$pseudo_elements[$tok] = $tok; $pos = trim($tok, ":");
} else { $pseudo_elements[$pos] = true;
$query .= "/*[@$tok]"; if (!$first_pass) {
$query .= "/*[@$pos]";
} }
$tok = ""; $tok = "";
@ -646,6 +741,12 @@ class Stylesheet
$query .= "[not(@disabled)]"; $query .= "[not(@disabled)]";
$tok = ""; $tok = "";
break; break;
// the selector is not handled, until we support all possible selectors force an empty set (silent failure)
default:
$query = "/../.."; // go up two levels because generated content starts on the body element
$tok = "";
break;
} }
break; break;
@ -787,7 +888,13 @@ class Stylesheet
return array("query" => $query, "pseudo_elements" => $pseudo_elements); return array("query" => $query, "pseudo_elements" => $pseudo_elements);
} }
// https://github.com/tenderlove/nokogiri/blob/master/lib/nokogiri/css/xpath_visitor.rb /**
* https://github.com/tenderlove/nokogiri/blob/master/lib/nokogiri/css/xpath_visitor.rb
*
* @param $expr
* @param bool $last
* @return string
*/
protected function _selector_an_plus_b($expr, $last = false) protected function _selector_an_plus_b($expr, $last = false)
{ {
$expr = preg_replace("/\s/", "", $expr); $expr = preg_replace("/\s/", "", $expr);
@ -837,77 +944,103 @@ class Stylesheet
$styles = array(); $styles = array();
$xp = new DOMXPath($tree->get_dom()); $xp = new DOMXPath($tree->get_dom());
$DEBUGCSS = $this->_dompdf->getOptions()->getDebugCss();
// Add generated content // Add generated content
foreach ($this->_styles as $selector => $style) { foreach ($this->_styles as $selector => $selector_styles) {
if (strpos($selector, ":before") === false && strpos($selector, ":after") === false) { /** @var Style $style */
continue; foreach ($selector_styles as $style) {
} if (strpos($selector, ":before") === false && strpos($selector, ":after") === false) {
continue;
}
$query = $this->_css_selector_to_xpath($selector, true); $query = $this->_css_selector_to_xpath($selector, true);
// Retrieve the nodes, limit to body for generated content // Retrieve the nodes, limit to body for generated content
$nodes = @$xp->query('.' . $query["query"]); //TODO: If we use a context node can we remove the leading dot?
if ($nodes == null) { $nodes = @$xp->query('.' . $query["query"]);
Helpers::record_warnings(E_USER_WARNING, "The CSS selector '$selector' is not valid", __FILE__, __LINE__); if ($nodes == null) {
continue; Helpers::record_warnings(E_USER_WARNING, "The CSS selector '$selector' is not valid", __FILE__, __LINE__);
} continue;
}
foreach ($nodes as $node) { /** @var \DOMElement $node */
foreach ($query["pseudo_elements"] as $pos) { foreach ($nodes as $node) {
// Do not add a new pseudo element if another one already matched // Only DOMElements get styles
if ($node->hasAttribute("dompdf_{$pos}_frame_id")) { if ($node->nodeType != XML_ELEMENT_NODE) {
continue; continue;
} }
if (($src = $this->_image($style->content)) !== "none") { foreach (array_keys($query["pseudo_elements"], true, true) as $pos) {
$new_node = $node->ownerDocument->createElement("img_generated"); // Do not add a new pseudo element if another one already matched
$new_node->setAttribute("src", $src); if ($node->hasAttribute("dompdf_{$pos}_frame_id")) {
} else { continue;
$new_node = $node->ownerDocument->createElement("dompdf_generated"); }
}
$new_node->setAttribute($pos, $pos); if (($src = $this->_image($style->get_prop('content'))) !== "none") {
$new_frame_id = $tree->insert_node($node, $new_node, $pos); $new_node = $node->ownerDocument->createElement("img_generated");
$node->setAttribute("dompdf_{$pos}_frame_id", $new_frame_id); $new_node->setAttribute("src", $src);
} else {
$new_node = $node->ownerDocument->createElement("dompdf_generated");
}
$new_node->setAttribute($pos, $pos);
$new_frame_id = $tree->insert_node($node, $new_node, $pos);
$node->setAttribute("dompdf_{$pos}_frame_id", $new_frame_id);
}
} }
} }
} }
// Apply all styles in stylesheet // Apply all styles in stylesheet
foreach ($this->_styles as $selector => $style) { foreach ($this->_styles as $selector => $selector_styles) {
$query = $this->_css_selector_to_xpath($selector); /** @var Style $style */
foreach ($selector_styles as $style) {
$query = $this->_css_selector_to_xpath($selector);
// Retrieve the nodes // Retrieve the nodes
$nodes = @$xp->query($query["query"]); $nodes = @$xp->query($query["query"]);
if ($nodes == null) { if ($nodes == null) {
Helpers::record_warnings(E_USER_WARNING, "The CSS selector '$selector' is not valid", __FILE__, __LINE__); Helpers::record_warnings(E_USER_WARNING, "The CSS selector '$selector' is not valid", __FILE__, __LINE__);
continue;
}
foreach ($nodes as $node) {
// Retrieve the node id
// Only DOMElements get styles
if ($node->nodeType != XML_ELEMENT_NODE) {
continue; continue;
} }
$id = $node->getAttribute("frame_id"); $spec = $this->_specificity($selector, $style->get_origin());
// Assign the current style to the scratch array foreach ($nodes as $node) {
$spec = $this->_specificity($selector); // Retrieve the node id
$styles[$id][$spec][] = $style; // Only DOMElements get styles
if ($node->nodeType != XML_ELEMENT_NODE) {
continue;
}
$id = $node->getAttribute("frame_id");
// Assign the current style to the scratch array
$styles[$id][$spec][] = $style;
}
} }
} }
// Now create the styles and assign them to the appropriate frames. (We // Set the page width, height, and orientation based on the canvas paper size
$canvas = $this->_dompdf->getCanvas();
$paper_width = $canvas->get_width();
$paper_height = $canvas->get_height();
$paper_orientation = ($paper_width > $paper_height ? "landscape" : "portrait");
if ($this->_page_styles["base"] && is_array($this->_page_styles["base"]->size)) {
$paper_width = $this->_page_styles['base']->size[0];
$paper_height = $this->_page_styles['base']->size[1];
$paper_orientation = ($paper_width > $paper_height ? "landscape" : "portrait");
}
// Now create the styles and assign them to the appropriate frames. (We
// iterate over the tree using an implicit FrameTree iterator.) // iterate over the tree using an implicit FrameTree iterator.)
$root_flg = false; $root_flg = false;
foreach ($tree->get_frames() as $frame) { foreach ($tree->get_frames() as $frame) {
// Helpers::pre_r($frame->get_node()->nodeName . ":"); // Helpers::pre_r($frame->get_node()->nodeName . ":");
if (!$root_flg && $this->_page_styles["base"]) { if (!$root_flg && $this->_page_styles["base"]) {
$style = $this->_page_styles["base"]; $style = $this->_page_styles["base"];
$root_flg = true;
} else { } else {
$style = $this->create_style(); $style = $this->create_style();
} }
@ -936,8 +1069,7 @@ class Stylesheet
// Handle HTML 4.0 attributes // Handle HTML 4.0 attributes
AttributeTranslator::translate_attributes($frame); AttributeTranslator::translate_attributes($frame);
if (($str = $frame->get_node()->getAttribute(AttributeTranslator::$_style_attr)) !== "") { if (($str = $frame->get_node()->getAttribute(AttributeTranslator::$_style_attr)) !== "") {
// Lowest specificity $styles[$id][self::SPEC_NON_CSS][] = $this->_parse_properties($str);
$styles[$id][1][] = $this->_parse_properties($str);
} }
// Locate any additional style attributes // Locate any additional style attributes
@ -945,23 +1077,25 @@ class Stylesheet
// Destroy CSS comments // Destroy CSS comments
$str = preg_replace("'/\*.*?\*/'si", "", $str); $str = preg_replace("'/\*.*?\*/'si", "", $str);
$spec = $this->_specificity("!attr"); $spec = $this->_specificity("!attr", self::ORIG_AUTHOR);
$styles[$id][$spec][] = $this->_parse_properties($str); $styles[$id][$spec][] = $this->_parse_properties($str);
} }
// Grab the applicable styles // Grab the applicable styles
if (isset($styles[$id])) { if (isset($styles[$id])) {
/** @var array[][] $applied_styles */
$applied_styles = $styles[$frame->get_id()]; $applied_styles = $styles[$frame->get_id()];
// Sort by specificity // Sort by specificity
ksort($applied_styles); ksort($applied_styles);
if ($this->_dompdf->get_option('debugCss')) { if ($DEBUGCSS) {
$debug_nodename = $frame->get_node()->nodeName; $debug_nodename = $frame->get_node()->nodeName;
print "<pre>\n[$debug_nodename\n"; print "<pre>\n[$debug_nodename\n";
foreach ($applied_styles as $spec => $arr) { foreach ($applied_styles as $spec => $arr) {
printf("specificity: 0x%08x\n", $spec); printf("specificity: 0x%08x\n", $spec);
/** @var Style $s */
foreach ($arr as $s) { foreach ($arr as $s) {
print "[\n"; print "[\n";
$s->debug_print(); $s->debug_print();
@ -971,8 +1105,66 @@ class Stylesheet
} }
// Merge the new styles with the inherited styles // Merge the new styles with the inherited styles
$acceptedmedia = self::$ACCEPTED_GENERIC_MEDIA_TYPES;
$acceptedmedia[] = $this->_dompdf->getOptions()->getDefaultMediaType();
foreach ($applied_styles as $arr) { foreach ($applied_styles as $arr) {
/** @var Style $s */
foreach ($arr as $s) { foreach ($arr as $s) {
$media_queries = $s->get_media_queries();
foreach ($media_queries as $media_query) {
list($media_query_feature, $media_query_value) = $media_query;
// if any of the Style's media queries fail then do not apply the style
//TODO: When the media query logic is fully developed we should not apply the Style when any of the media queries fail or are bad, per https://www.w3.org/TR/css3-mediaqueries/#error-handling
if (in_array($media_query_feature, self::$VALID_MEDIA_TYPES)) {
if ((strlen($media_query_feature) === 0 && !in_array($media_query, $acceptedmedia)) || (in_array($media_query, $acceptedmedia) && $media_query_value == "not")) {
continue (3);
}
} else {
switch ($media_query_feature) {
case "height":
if ($paper_height !== (float)$style->length_in_pt($media_query_value)) {
continue (3);
}
break;
case "min-height":
if ($paper_height < (float)$style->length_in_pt($media_query_value)) {
continue (3);
}
break;
case "max-height":
if ($paper_height > (float)$style->length_in_pt($media_query_value)) {
continue (3);
}
break;
case "width":
if ($paper_width !== (float)$style->length_in_pt($media_query_value)) {
continue (3);
}
break;
case "min-width":
//if (min($paper_width, $media_query_width) === $paper_width) {
if ($paper_width < (float)$style->length_in_pt($media_query_value)) {
continue (3);
}
break;
case "max-width":
//if (max($paper_width, $media_query_width) === $paper_width) {
if ($paper_width > (float)$style->length_in_pt($media_query_value)) {
continue (3);
}
break;
case "orientation":
if ($paper_orientation !== $media_query_value) {
continue (3);
}
break;
default:
Helpers::record_warnings(E_USER_WARNING, "Unknown media query: $media_query_feature", __FILE__, __LINE__);
break;
}
}
}
$style->merge($s); $style->merge($s);
} }
} }
@ -981,7 +1173,7 @@ class Stylesheet
// Inherit parent's styles if required // Inherit parent's styles if required
if ($p) { if ($p) {
if ($this->_dompdf->get_option('debugCss')) { if ($DEBUGCSS) {
print "inherit:\n"; print "inherit:\n";
print "[\n"; print "[\n";
$p->get_style()->debug_print(); $p->get_style()->debug_print();
@ -991,7 +1183,7 @@ class Stylesheet
$style->inherit($p->get_style()); $style->inherit($p->get_style());
} }
if ($this->_dompdf->get_option('debugCss')) { if ($DEBUGCSS) {
print "DomElementStyle:\n"; print "DomElementStyle:\n";
print "[\n"; print "[\n";
$style->debug_print(); $style->debug_print();
@ -1006,6 +1198,17 @@ class Stylesheet
echo "</pre>";*/ echo "</pre>";*/
$frame->set_style($style); $frame->set_style($style);
if (!$root_flg && $this->_page_styles["base"]) {
$root_flg = true;
// set the page width, height, and orientation based on the parsed page style
if ($style->size !== "auto") {
list($paper_width, $paper_height) = $style->size;
}
$paper_width = $paper_width - (float)$style->length_in_pt($style->margin_left) - (float)$style->length_in_pt($style->margin_right);
$paper_height = $paper_height - (float)$style->length_in_pt($style->margin_top) - (float)$style->length_in_pt($style->margin_bottom);
$paper_orientation = ($paper_width > $paper_height ? "landscape" : "portrait");
}
} }
// We're done! Clean out the registry of all styles since we // We're done! Clean out the registry of all styles since we
@ -1049,8 +1252,8 @@ class Stylesheet
" (?(6) (?>[^}]*) }) \s*)+? \n" . " (?(6) (?>[^}]*) }) \s*)+? \n" .
" ) \n" . " ) \n" .
" }) # Balancing '}' \n" . " }) # Balancing '}' \n" .
"| # Branch to match regular rules (not preceded by '@')\n" . "| # Branch to match regular rules (not preceded by '@') \n" .
"([^{]*{[^}]*})) # Parse normal rulesets\n" . "([^{]*{[^}]*})) # Parse normal rulesets \n" .
"/xs"; "/xs";
if (preg_match_all($re, $css, $matches, PREG_SET_ORDER) === false) { if (preg_match_all($re, $css, $matches, PREG_SET_ORDER) === false) {
@ -1069,6 +1272,9 @@ class Stylesheet
// [6] => '{', within media rules // [6] => '{', within media rules
// [7] => individual rules, outside of media rules // [7] => individual rules, outside of media rules
// //
$media_query_regex = "/(?:((only|not)?\s*(" . implode("|", self::$VALID_MEDIA_TYPES) . "))|(\s*\(\s*((?:(min|max)-)?([\w\-]+))\s*(?:\:\s*(.*?)\s*)?\)))/isx";
//Helpers::pre_r($matches); //Helpers::pre_r($matches);
foreach ($matches as $match) { foreach ($matches as $match) {
$match[2] = trim($match[2]); $match[2] = trim($match[2]);
@ -1083,12 +1289,33 @@ class Stylesheet
case "media": case "media":
$acceptedmedia = self::$ACCEPTED_GENERIC_MEDIA_TYPES; $acceptedmedia = self::$ACCEPTED_GENERIC_MEDIA_TYPES;
$acceptedmedia[] = $this->_dompdf->get_option("default_media_type"); $acceptedmedia[] = $this->_dompdf->getOptions()->getDefaultMediaType();
$media = preg_split("/\s*,\s*/", mb_strtolower(trim($match[3]))); $media_queries = preg_split("/\s*,\s*/", mb_strtolower(trim($match[3])));
foreach ($media_queries as $media_query) {
if (count(array_intersect($acceptedmedia, $media))) { if (in_array($media_query, $acceptedmedia)) {
$this->_parse_sections($match[5]); //if we have a media type match go ahead and parse the stylesheet
$this->_parse_sections($match[5]);
break;
} elseif (!in_array($media_query, self::$VALID_MEDIA_TYPES)) {
// otherwise conditionally parse the stylesheet assuming there are parseable media queries
if (preg_match_all($media_query_regex, $media_query, $media_query_matches, PREG_SET_ORDER) !== false) {
$mq = array();
foreach ($media_query_matches as $media_query_match) {
if (empty($media_query_match[1]) === false) {
$media_query_feature = strtolower($media_query_match[3]);
$media_query_value = strtolower($media_query_match[2]);
$mq[] = array($media_query_feature, $media_query_value);
} else if (empty($media_query_match[4]) === false) {
$media_query_feature = strtolower($media_query_match[5]);
$media_query_value = (array_key_exists(8, $media_query_match) ? strtolower($media_query_match[8]) : null);
$mq[] = array($media_query_feature, $media_query_value);
}
}
$this->_parse_sections($match[5], $mq);
break;
}
}
} }
break; break;
@ -1125,6 +1352,7 @@ class Stylesheet
case ":right": case ":right":
case ":odd": case ":odd":
case ":even": case ":even":
/** @noinspection PhpMissingBreakStatementInspection */
case ":first": case ":first":
$key = $page_selector; $key = $page_selector;
@ -1159,16 +1387,21 @@ class Stylesheet
} }
} }
/* See also style.cls Style::_image(), refactoring?, works also for imported css files */ /**
* See also style.cls Style::_image(), refactoring?, works also for imported css files
*
* @param $val
* @return string
*/
protected function _image($val) protected function _image($val)
{ {
$DEBUGCSS = $this->_dompdf->get_option('debugCss'); $DEBUGCSS = $this->_dompdf->getOptions()->getDebugCss();
$parsed_url = "none"; $parsed_url = "none";
if (mb_strpos($val, "url") === false) { if (mb_strpos($val, "url") === false) {
$path = "none"; //Don't resolve no image -> otherwise would prefix path and no longer recognize as none $path = "none"; //Don't resolve no image -> otherwise would prefix path and no longer recognize as none
} else { } else {
$val = preg_replace("/url\(['\"]?([^'\")]+)['\"]?\)/", "\\1", trim($val)); $val = preg_replace("/url\(\s*['\"]?([^'\")]+)['\"]?\s*\)/", "\\1", trim($val));
// Resolve the url now in the context of the current stylesheet // Resolve the url now in the context of the current stylesheet
$parsed_url = Helpers::explode_url($val); $parsed_url = Helpers::explode_url($val);
@ -1217,7 +1450,7 @@ class Stylesheet
if (count($arr) > 0) { if (count($arr) > 0) {
$acceptedmedia = self::$ACCEPTED_GENERIC_MEDIA_TYPES; $acceptedmedia = self::$ACCEPTED_GENERIC_MEDIA_TYPES;
$acceptedmedia[] = $this->_dompdf->get_option("default_media_type"); $acceptedmedia[] = $this->_dompdf->getOptions()->getDefaultMediaType();
// @import url media_type [media_type...] // @import url media_type [media_type...]
foreach ($arr as $type) { foreach ($arr as $type) {
@ -1261,7 +1494,6 @@ class Stylesheet
* http://www.w3.org/TR/css3-fonts/#the-font-face-rule * http://www.w3.org/TR/css3-fonts/#the-font-face-rule
* *
* @param string $str CSS @font-face rules * @param string $str CSS @font-face rules
* @return Style
*/ */
private function _parse_font_face($str) private function _parse_font_face($str)
{ {
@ -1276,7 +1508,7 @@ class Stylesheet
$source = array( $source = array(
"local" => strtolower($src[1][$i]) === "local", "local" => strtolower($src[1][$i]) === "local",
"uri" => $src[2][$i], "uri" => $src[2][$i],
"format" => $src[4][$i], "format" => strtolower($src[4][$i]),
"path" => Helpers::build_url($this->_protocol, $this->_base_host, $this->_base_path, $src[2][$i]), "path" => Helpers::build_url($this->_protocol, $this->_base_host, $this->_base_path, $src[2][$i]),
); );
@ -1313,8 +1545,11 @@ class Stylesheet
private function _parse_properties($str) private function _parse_properties($str)
{ {
$properties = preg_split("/;(?=(?:[^\(]*\([^\)]*\))*(?![^\)]*\)))/", $str); $properties = preg_split("/;(?=(?:[^\(]*\([^\)]*\))*(?![^\)]*\)))/", $str);
$DEBUGCSS = $this->_dompdf->getOptions()->getDebugCss();
if ($this->_dompdf->get_option('debugCss')) print '[_parse_properties'; if ($DEBUGCSS) {
print '[_parse_properties';
}
// Create the style // Create the style
$style = new Style($this, Stylesheet::ORIG_AUTHOR); $style = new Style($this, Stylesheet::ORIG_AUTHOR);
@ -1342,7 +1577,7 @@ class Stylesheet
} }
$prop = trim($prop); $prop = trim($prop);
*/ */
if ($this->_dompdf->get_option('debugCss')) print '('; if ($DEBUGCSS) print '(';
$important = false; $important = false;
$prop = trim($prop); $prop = trim($prop);
@ -1357,19 +1592,19 @@ class Stylesheet
} }
if ($prop === "") { if ($prop === "") {
if ($this->_dompdf->get_option('debugCss')) print 'empty)'; if ($DEBUGCSS) print 'empty)';
continue; continue;
} }
$i = mb_strpos($prop, ":"); $i = mb_strpos($prop, ":");
if ($i === false) { if ($i === false) {
if ($this->_dompdf->get_option('debugCss')) print 'novalue' . $prop . ')'; if ($DEBUGCSS) print 'novalue' . $prop . ')';
continue; continue;
} }
$prop_name = rtrim(mb_strtolower(mb_substr($prop, 0, $i))); $prop_name = rtrim(mb_strtolower(mb_substr($prop, 0, $i)));
$value = ltrim(mb_substr($prop, $i + 1)); $value = ltrim(mb_substr($prop, $i + 1));
if ($this->_dompdf->get_option('debugCss')) print $prop_name . ':=' . $value . ($important ? '!IMPORTANT' : '') . ')'; if ($DEBUGCSS) print $prop_name . ':=' . $value . ($important ? '!IMPORTANT' : '') . ')';
//New style, anyway empty //New style, anyway empty
//if ($important || !$style->important_get($prop_name) ) { //if ($important || !$style->important_get($prop_name) ) {
//$style->$prop_name = array($value,$important); //$style->$prop_name = array($value,$important);
@ -1383,7 +1618,7 @@ class Stylesheet
$style->$prop_name = $value; $style->$prop_name = $value;
//$style->props_set($prop_name, $value); //$style->props_set($prop_name, $value);
} }
if ($this->_dompdf->get_option('debugCss')) print '_parse_properties]'; if ($DEBUGCSS) print '_parse_properties]';
return $style; return $style;
} }
@ -1392,8 +1627,9 @@ class Stylesheet
* parse selector + rulesets * parse selector + rulesets
* *
* @param string $str CSS selectors and rulesets * @param string $str CSS selectors and rulesets
* @param array $media_queries
*/ */
private function _parse_sections($str) private function _parse_sections($str, $media_queries = array())
{ {
// Pre-process: collapse all whitespace and strip whitespace around '>', // Pre-process: collapse all whitespace and strip whitespace around '>',
// '.', ':', '+', '#' // '.', ':', '+', '#'
@ -1401,14 +1637,18 @@ class Stylesheet
$patterns = array("/[\\s\n]+/", "/\\s+([>.:+#])\\s+/"); $patterns = array("/[\\s\n]+/", "/\\s+([>.:+#])\\s+/");
$replacements = array(" ", "\\1"); $replacements = array(" ", "\\1");
$str = preg_replace($patterns, $replacements, $str); $str = preg_replace($patterns, $replacements, $str);
$DEBUGCSS = $this->_dompdf->getOptions()->getDebugCss();
$sections = explode("}", $str); $sections = explode("}", $str);
if ($this->_dompdf->get_option('debugCss')) print '[_parse_sections'; if ($DEBUGCSS) print '[_parse_sections';
foreach ($sections as $sect) { foreach ($sections as $sect) {
$i = mb_strpos($sect, "{"); $i = mb_strpos($sect, "{");
if ($i === false) { continue; }
//$selectors = explode(",", mb_substr($sect, 0, $i));
$selectors = preg_split("/,(?![^\(]*\))/", mb_substr($sect, 0, $i),0, PREG_SPLIT_NO_EMPTY);
if ($DEBUGCSS) print '[section';
$selectors = explode(",", mb_substr($sect, 0, $i));
if ($this->_dompdf->get_option('debugCss')) print '[section';
$style = $this->_parse_properties(trim(mb_substr($sect, $i + 1))); $style = $this->_parse_properties(trim(mb_substr($sect, $i + 1)));
// Assign it to the selected elements // Assign it to the selected elements
@ -1416,21 +1656,32 @@ class Stylesheet
$selector = trim($selector); $selector = trim($selector);
if ($selector == "") { if ($selector == "") {
if ($this->_dompdf->get_option('debugCss')) print '#empty#'; if ($DEBUGCSS) print '#empty#';
continue; continue;
} }
if ($this->_dompdf->get_option('debugCss')) print '#' . $selector . '#'; if ($DEBUGCSS) print '#' . $selector . '#';
//if ($this->_dompdf->get_option('debugCss')) { if (strpos($selector,'p') !== false) print '!!!p!!!#'; } //if ($DEBUGCSS) { if (strpos($selector,'p') !== false) print '!!!p!!!#'; }
//FIXME: tag the selector with a hash of the media query to separate it from non-conditional styles (?), xpath comments are probably not what we want to do here
if (count($media_queries) > 0) {
$style->set_media_queries($media_queries);
}
$this->add_style($selector, $style); $this->add_style($selector, $style);
} }
if ($this->_dompdf->get_option('debugCss')) print 'section]'; if ($DEBUGCSS) {
print 'section]';
}
} }
if ($this->_dompdf->get_option('debugCss')) print '_parse_sections]'; if ($DEBUGCSS) {
print '_parse_sections]';
}
} }
/**
* @return string
*/
public static function getDefaultStylesheet() public static function getDefaultStylesheet()
{ {
$dir = realpath(__DIR__ . "/../.."); $dir = realpath(__DIR__ . "/../..");
@ -1466,8 +1717,11 @@ class Stylesheet
function __toString() function __toString()
{ {
$str = ""; $str = "";
foreach ($this->_styles as $selector => $style) { foreach ($this->_styles as $selector => $selector_styles) {
$str .= "$selector => " . $style->__toString() . "\n"; /** @var Style $style */
foreach ($selector_styles as $style) {
$str .= "$selector => " . $style->__toString() . "\n";
}
} }
return $str; return $str;

View File

@ -12,14 +12,12 @@ use DOMDocument;
use DOMNode; use DOMNode;
use Dompdf\Adapter\CPDF; use Dompdf\Adapter\CPDF;
use DOMXPath; use DOMXPath;
use Dompdf\Frame;
use Dompdf\Frame\Factory; use Dompdf\Frame\Factory;
use Dompdf\Frame\FrameTree; use Dompdf\Frame\FrameTree;
use HTML5_Tokenizer; use HTML5_Tokenizer;
use HTML5_TreeBuilder; use HTML5_TreeBuilder;
use Dompdf\Image\Cache; use Dompdf\Image\Cache;
use Dompdf\Renderer\ListBullet; use Dompdf\Renderer\ListBullet;
use Dompdf\Renderer;
use Dompdf\Css\Stylesheet; use Dompdf\Css\Stylesheet;
/** /**
@ -77,7 +75,7 @@ class Dompdf
* @var string * @var string
*/ */
private $version = 'dompdf'; private $version = 'dompdf';
/** /**
* DomDocument representing the HTML document * DomDocument representing the HTML document
* *
@ -99,12 +97,6 @@ class Dompdf
*/ */
private $css; private $css;
/**
* @var Canvas
* @deprecated
*/
private $pdf;
/** /**
* Actual PDF renderer * Actual PDF renderer
* *
@ -216,13 +208,13 @@ class Dompdf
/** /**
* Protocol whitelist * Protocol whitelist
* *
* Protocols and PHP wrappers allowed in URLs. Full support is not * Protocols and PHP wrappers allowed in URLs. Full support is not
* guarantee for the protocols/wrappers contained in this array. * guarantee for the protocols/wrappers contained in this array.
* *
* @var array * @var array
*/ */
private $allowedProtocols = array(null, "", "file://", "http://", "https://"); private $allowedProtocols = array(null, "", "file://", "http://", "https://");
/** /**
* Local file extension whitelist * Local file extension whitelist
* *
@ -231,7 +223,7 @@ class Dompdf
* @var array * @var array
*/ */
private $allowedLocalFileExtensions = array("htm", "html"); private $allowedLocalFileExtensions = array("htm", "html");
/** /**
* @var array * @var array
*/ */
@ -280,7 +272,7 @@ class Dompdf
public function __construct($options = null) public function __construct($options = null)
{ {
mb_internal_encoding('UTF-8'); mb_internal_encoding('UTF-8');
if (isset($options) && $options instanceof Options) { if (isset($options) && $options instanceof Options) {
$this->setOptions($options); $this->setOptions($options);
} elseif (is_array($options)) { } elseif (is_array($options)) {
@ -288,7 +280,7 @@ class Dompdf
} else { } else {
$this->setOptions(new Options()); $this->setOptions(new Options());
} }
$versionFile = realpath(__DIR__ . '/../VERSION'); $versionFile = realpath(__DIR__ . '/../VERSION');
if (file_exists($versionFile) && ($version = file_get_contents($versionFile)) !== false && $version !== '$Format:<%h>$') { if (file_exists($versionFile) && ($version = file_get_contents($versionFile)) !== false && $version !== '$Format:<%h>$') {
$this->version = sprintf('dompdf %s', $version); $this->version = sprintf('dompdf %s', $version);
@ -297,6 +289,7 @@ class Dompdf
$this->localeStandard = sprintf('%.1f', 1.0) == '1.0'; $this->localeStandard = sprintf('%.1f', 1.0) == '1.0';
$this->saveLocale(); $this->saveLocale();
$this->paperSize = $this->options->getDefaultPaperSize(); $this->paperSize = $this->options->getDefaultPaperSize();
$this->paperOrientation = $this->options->getDefaultPaperOrientation();
$this->setCanvas(CanvasFactory::get_instance($this, $this->paperSize, $this->paperOrientation)); $this->setCanvas(CanvasFactory::get_instance($this, $this->paperSize, $this->paperOrientation));
$this->setFontMetrics(new FontMetrics($this->getCanvas(), $this->getOptions())); $this->setFontMetrics(new FontMetrics($this->getCanvas(), $this->getOptions()));
@ -355,30 +348,29 @@ class Dompdf
if (!$this->protocol && !$this->baseHost && !$this->basePath) { if (!$this->protocol && !$this->baseHost && !$this->basePath) {
list($this->protocol, $this->baseHost, $this->basePath) = Helpers::explode_url($file); list($this->protocol, $this->baseHost, $this->basePath) = Helpers::explode_url($file);
} }
$protocol = strtolower($this->protocol);
if ( !in_array($this->protocol, $this->allowedProtocols) ) { if ( !in_array($protocol, $this->allowedProtocols) ) {
throw new Exception("Permission denied on $file. The communication protocol is not supported."); throw new Exception("Permission denied on $file. The communication protocol is not supported.");
} }
if (!$this->options->isRemoteEnabled() && ($this->protocol != "" && $this->protocol !== "file://")) { if (!$this->options->isRemoteEnabled() && ($protocol != "" && $protocol !== "file://")) {
throw new Exception("Remote file requested, but remote file download is disabled."); throw new Exception("Remote file requested, but remote file download is disabled.");
} }
if ($this->protocol == "" || $this->protocol === "file://") { if ($protocol == "" || $protocol === "file://") {
// Get the full path to $file, returns false if the file doesn't exist
$realfile = realpath($file); $realfile = realpath($file);
$chroot = $this->options->getChroot(); $chroot = realpath($this->options->getChroot());
if (strpos($realfile, $chroot) !== 0) { if ($chroot && strpos($realfile, $chroot) !== 0) {
throw new Exception("Permission denied on $file. The file could not be found under the directory specified by Options::chroot."); throw new Exception("Permission denied on $file. The file could not be found under the directory specified by Options::chroot.");
} }
$ext = pathinfo($realfile, PATHINFO_EXTENSION); $ext = strtolower(pathinfo($realfile, PATHINFO_EXTENSION));
if (!in_array($ext, $this->allowedLocalFileExtensions)) { if (!in_array($ext, $this->allowedLocalFileExtensions)) {
throw new Exception("Permission denied on $file."); throw new Exception("Permission denied on $file.");
} }
if (!$realfile) { if (!$realfile) {
throw new Exception("File '$file' not found."); throw new Exception("File '$file' not found.");
} }
@ -386,8 +378,8 @@ class Dompdf
$file = $realfile; $file = $realfile;
} }
$contents = file_get_contents($file, null, $this->httpContext); list($contents, $http_response_header) = Helpers::getFileContent($file, $this->httpContext);
$encoding = null; $encoding = 'UTF-8';
// See http://the-stickman.com/web-development/php/getting-http-response-headers-when-using-file_get_contents/ // See http://the-stickman.com/web-development/php/getting-http-response-headers-when-using-file_get_contents/
if (isset($http_response_header)) { if (isset($http_response_header)) {
@ -409,7 +401,7 @@ class Dompdf
* @param null $encoding * @param null $encoding
* @deprecated * @deprecated
*/ */
public function load_html($str, $encoding = null) public function load_html($str, $encoding = 'UTF-8')
{ {
$this->loadHtml($str, $encoding); $this->loadHtml($str, $encoding);
} }
@ -422,50 +414,42 @@ class Dompdf
* @param string $str HTML text to load * @param string $str HTML text to load
* @param string $encoding Not used yet * @param string $encoding Not used yet
*/ */
public function loadHtml($str, $encoding = null) public function loadHtml($str, $encoding = 'UTF-8')
{ {
$this->saveLocale(); $this->saveLocale();
// FIXME: Determine character encoding, switch to UTF8, update meta tag. Need better http/file stream encoding detection, currently relies on text or meta tag. // FIXME: Determine character encoding, switch to UTF8, update meta tag. Need better http/file stream encoding detection, currently relies on text or meta tag.
$known_encodings = mb_list_encodings();
mb_detect_order('auto'); mb_detect_order('auto');
if (($file_encoding = mb_detect_encoding($str, null, true)) === false) {
if (mb_detect_encoding($str) !== 'UTF-8') { $file_encoding = "auto";
$metatags = array(
'@<meta\s+http-equiv="Content-Type"\s+content="(?:[\w/]+)(?:;\s*?charset=([^\s"]+))?@i',
'@<meta\s+content="(?:[\w/]+)(?:;\s*?charset=([^\s"]+))"?\s+http-equiv="Content-Type"@i',
'@<meta [^>]*charset\s*=\s*["\']?\s*([^"\' ]+)@i',
);
foreach ($metatags as $metatag) {
if (preg_match($metatag, $str, $matches)) break;
}
if (mb_detect_encoding($str) == '') {
if (isset($matches[1])) {
$encoding = strtoupper($matches[1]);
} else {
$encoding = 'UTF-8';
}
} else {
if (isset($matches[1])) {
$encoding = strtoupper($matches[1]);
} else {
$encoding = 'auto';
}
}
if ($encoding !== 'UTF-8') {
$str = mb_convert_encoding($str, 'UTF-8', $encoding);
}
if (isset($matches[1])) {
$str = preg_replace('/charset=([^\s"]+)/i', 'charset=UTF-8', $str);
} else {
$str = str_replace('<head>', '<head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8">', $str);
}
} else {
$encoding = 'UTF-8';
} }
if (in_array(strtoupper($file_encoding), array('UTF-8','UTF8')) === false) {
$str = mb_convert_encoding($str, 'UTF-8', $file_encoding);
}
$metatags = array(
'@<meta\s+http-equiv="Content-Type"\s+content="(?:[\w/]+)(?:;\s*?charset=([^\s"]+))?@i',
'@<meta\s+content="(?:[\w/]+)(?:;\s*?charset=([^\s"]+))"?\s+http-equiv="Content-Type"@i',
'@<meta [^>]*charset\s*=\s*["\']?\s*([^"\' ]+)@i',
);
foreach ($metatags as $metatag) {
if (preg_match($metatag, $str, $matches)) {
if (isset($matches[1]) && in_array($matches[1], $known_encodings)) {
$document_encoding = $matches[1];
break;
}
}
}
if (isset($document_encoding) && in_array(strtoupper($document_encoding), array('UTF-8','UTF8')) === false) {
$str = preg_replace('/charset=([^\s"]+)/i', 'charset=UTF-8', $str);
} elseif (isset($document_encoding) === false && strpos($str, '<head>') !== false) {
$str = str_replace('<head>', '<head><meta http-equiv="Content-Type" content="text/html;charset=UTF-8">', $str);
} elseif (isset($document_encoding) === false) {
$str = '<meta http-equiv="Content-Type" content="text/html;charset=UTF-8">' . $str;
}
//FIXME: since we're not using this just yet
$encoding = 'UTF-8';
// remove BOM mark from UTF-8, it's treated as document text by DOMDocument // remove BOM mark from UTF-8, it's treated as document text by DOMDocument
// FIXME: roll this into the encoding detection using UTF-8/16/32 BOM (http://us2.php.net/manual/en/function.mb-detect-encoding.php#91051)? // FIXME: roll this into the encoding detection using UTF-8/16/32 BOM (http://us2.php.net/manual/en/function.mb-detect-encoding.php#91051)?
@ -473,14 +457,6 @@ class Dompdf
$str = substr($str, 3); $str = substr($str, 3);
} }
// if the document contains non utf-8 with a utf-8 meta tag chars and was
// detected as utf-8 by mbstring, problems could happen.
// http://devzone.zend.com/article/8855
if ($encoding !== 'UTF-8') {
$re = '/<meta ([^>]*)((?:charset=[^"\' ]+)([^>]*)|(?:charset=["\'][^"\' ]+["\']))([^>]*)>/i';
$str = preg_replace($re, '<meta $1$3>', $str);
}
// Store parsing warnings as messages // Store parsing warnings as messages
set_error_handler(array("\\Dompdf\\Helpers", "record_warnings")); set_error_handler(array("\\Dompdf\\Helpers", "record_warnings"));
@ -489,7 +465,7 @@ class Dompdf
// https://developer.mozilla.org/en/mozilla's_quirks_mode // https://developer.mozilla.org/en/mozilla's_quirks_mode
$quirksmode = false; $quirksmode = false;
if ($this->options->isHtml5ParserEnabled()) { if ($this->options->isHtml5ParserEnabled() && class_exists("HTML5_Tokenizer")) {
$tokenizer = new HTML5_Tokenizer($str); $tokenizer = new HTML5_Tokenizer($str);
$tokenizer->parse(); $tokenizer->parse();
$doc = $tokenizer->save(); $doc = $tokenizer->save();
@ -500,20 +476,29 @@ class Dompdf
$nodes = $doc->getElementsByTagName($tag_name); $nodes = $doc->getElementsByTagName($tag_name);
foreach ($nodes as $node) { foreach ($nodes as $node) {
self::remove_text_nodes($node); self::removeTextNodes($node);
} }
} }
$quirksmode = ($tokenizer->getTree()->getQuirksMode() > HTML5_TreeBuilder::NO_QUIRKS); $quirksmode = ($tokenizer->getTree()->getQuirksMode() > HTML5_TreeBuilder::NO_QUIRKS);
} else { } else {
// loadHTML assumes ISO-8859-1 unless otherwise specified, but there are // loadHTML assumes ISO-8859-1 unless otherwise specified on the HTML document header.
// bugs in how DOMDocument determines the actual encoding. Converting to
// HTML-ENTITIES prior to import appears to resolve the issue.
// http://devzone.zend.com/1538/php-dom-xml-extension-encoding-processing/ (see #4) // http://devzone.zend.com/1538/php-dom-xml-extension-encoding-processing/ (see #4)
// http://stackoverflow.com/a/11310258/264628 // http://stackoverflow.com/a/11310258/264628
$doc = new DOMDocument(); $doc = new DOMDocument("1.0", $encoding);
$doc->preserveWhiteSpace = true; $doc->preserveWhiteSpace = true;
$doc->loadHTML(mb_convert_encoding($str, 'HTML-ENTITIES', 'UTF-8')); $doc->loadHTML($str);
$doc->encoding = $encoding;
// Remove #text children nodes in nodes that shouldn't have
$tag_names = array("html", "table", "tbody", "thead", "tfoot", "tr");
foreach ($tag_names as $tag_name) {
$nodes = $doc->getElementsByTagName($tag_name);
foreach ($nodes as $node) {
self::removeTextNodes($node);
}
}
// If some text is before the doctype, we are in quirksmode // If some text is before the doctype, we are in quirksmode
if (preg_match("/^(.+)<!doctype/i", ltrim($str), $matches)) { if (preg_match("/^(.+)<!doctype/i", ltrim($str), $matches)) {
@ -599,6 +584,7 @@ class Dompdf
$xpath = new DOMXPath($this->dom); $xpath = new DOMXPath($this->dom);
$stylesheets = $xpath->query("//*[name() = 'link' or name() = 'style']"); $stylesheets = $xpath->query("//*[name() = 'link' or name() = 'style']");
/** @var \DOMElement $tag */
foreach ($stylesheets as $tag) { foreach ($stylesheets as $tag) {
switch (strtolower($tag->nodeName)) { switch (strtolower($tag->nodeName)) {
// load <link rel="STYLESHEET" ... /> tags // load <link rel="STYLESHEET" ... /> tags
@ -656,7 +642,7 @@ class Dompdf
$css = $tag->nodeValue; $css = $tag->nodeValue;
} }
$this->css->load_css($css); $this->css->load_css($css, Stylesheet::ORIG_AUTHOR);
break; break;
} }
} }
@ -716,15 +702,18 @@ class Dompdf
public function render() public function render()
{ {
$this->saveLocale(); $this->saveLocale();
$options = $this->options;
$logOutputFile = $this->options->getLogOutputFile(); $logOutputFile = $options->getLogOutputFile();
if ($logOutputFile) { if ($logOutputFile) {
if (!file_exists($logOutputFile) && is_writable(dirname($logOutputFile))) { if (!file_exists($logOutputFile) && is_writable(dirname($logOutputFile))) {
touch($logOutputFile); touch($logOutputFile);
} }
$this->startTime = microtime(true); $this->startTime = microtime(true);
ob_start(); if (is_writable($logOutputFile)) {
ob_start();
}
} }
$this->processHtml(); $this->processHtml();
@ -733,7 +722,6 @@ class Dompdf
// @page style rules : size, margins // @page style rules : size, margins
$pageStyles = $this->css->get_page_styles(); $pageStyles = $this->css->get_page_styles();
$basePageStyle = $pageStyles["base"]; $basePageStyle = $pageStyles["base"];
unset($pageStyles["base"]); unset($pageStyles["base"]);
@ -741,29 +729,43 @@ class Dompdf
$pageStyle->inherit($basePageStyle); $pageStyle->inherit($basePageStyle);
} }
$defaultOptionPaperSize = $this->getPaperSize($options->getDefaultPaperSize());
// If there is a CSS defined paper size compare to the paper size used to create the canvas to determine a
// recreation need
if (is_array($basePageStyle->size)) { if (is_array($basePageStyle->size)) {
$this->setPaper(array(0, 0, $basePageStyle->size[0], $basePageStyle->size[1])); $basePageStyleSize = $basePageStyle->size;
$this->setPaper(array(0, 0, $basePageStyleSize[0], $basePageStyleSize[1]));
} }
//TODO: We really shouldn't be doing this; properties were already set in the constructor. We should add Canvas methods to set the page size and orientation after instantiaion (see #1059).
$this->setCanvas(CanvasFactory::get_instance($this, $this->paperSize, $this->paperOrientation));
$this->setFontMetrics(new FontMetrics($this->pdf, $this->getOptions()));
if ($this->options->isFontSubsettingEnabled() && $this->pdf instanceof CPDF) { $paperSize = $this->getPaperSize();
if (
$defaultOptionPaperSize[2] !== $paperSize[2] ||
$defaultOptionPaperSize[3] !== $paperSize[3] ||
$options->getDefaultPaperOrientation() !== $this->paperOrientation
) {
$this->setCanvas(CanvasFactory::get_instance($this, $this->paperSize, $this->paperOrientation));
$this->fontMetrics->setCanvas($this->getCanvas());
}
$canvas = $this->getCanvas();
if ($options->isFontSubsettingEnabled() && $canvas instanceof CPDF) {
foreach ($this->tree->get_frames() as $frame) { foreach ($this->tree->get_frames() as $frame) {
$style = $frame->get_style(); $style = $frame->get_style();
$node = $frame->get_node(); $node = $frame->get_node();
// Handle text nodes // Handle text nodes
if ($node->nodeName === "#text") { if ($node->nodeName === "#text") {
$this->getCanvas()->register_string_subset($style->font_family, $node->nodeValue); $chars = mb_strtoupper($node->nodeValue) . mb_strtolower($node->nodeValue);
$canvas->register_string_subset($style->font_family, $chars);
continue; continue;
} }
// Handle generated content (list items) // Handle generated content (list items)
if ($style->display === "list-item") { if ($style->display === "list-item") {
$chars = ListBullet::get_counter_chars($style->list_style_type); $chars = ListBullet::get_counter_chars($style->list_style_type);
$this->getCanvas()->register_string_subset($style->font_family, $chars); $canvas->register_string_subset($style->font_family, $chars);
$canvas->register_string_subset($style->font_family, '.');
continue; continue;
} }
@ -772,17 +774,22 @@ class Dompdf
// not the actual generated content, and forces all possible counter // not the actual generated content, and forces all possible counter
// values. See notes in issue #750. // values. See notes in issue #750.
if ($frame->get_node()->nodeName == "dompdf_generated") { if ($frame->get_node()->nodeName == "dompdf_generated") {
// all possible counter values // all possible counter values, just in case
$chars = ListBullet::get_counter_chars('decimal'); $chars = ListBullet::get_counter_chars('decimal');
$this->getCanvas()->register_string_subset($style->font_family, $chars); $canvas->register_string_subset($style->font_family, $chars);
$chars = ListBullet::get_counter_chars('upper-alpha'); $chars = ListBullet::get_counter_chars('upper-alpha');
$this->getCanvas()->register_string_subset($style->font_family, $chars); $canvas->register_string_subset($style->font_family, $chars);
$chars = ListBullet::get_counter_chars('lower-alpha'); $chars = ListBullet::get_counter_chars('lower-alpha');
$this->getCanvas()->register_string_subset($style->font_family, $chars); $canvas->register_string_subset($style->font_family, $chars);
$chars = ListBullet::get_counter_chars('lower-greek'); $chars = ListBullet::get_counter_chars('lower-greek');
$this->getCanvas()->register_string_subset($style->font_family, $chars); $canvas->register_string_subset($style->font_family, $chars);
// the text of the stylesheet declaration
$this->getCanvas()->register_string_subset($style->font_family, $style->content); // the hex-decoded text of the content property, duplicated from AbstrctFrameReflower::_parse_string
$decoded_string = preg_replace_callback("/\\\\([0-9a-fA-F]{0,6})/",
function ($matches) { return \Dompdf\Helpers::unichr(hexdec($matches[1])); },
$style->content);
$chars = mb_strtoupper($style->content) . mb_strtolower($style->content) . mb_strtoupper($decoded_string) . mb_strtolower($decoded_string);
$canvas->register_string_subset($style->font_family, $chars);
continue; continue;
} }
} }
@ -804,7 +811,7 @@ class Dompdf
// Add meta information // Add meta information
$title = $this->dom->getElementsByTagName("title"); $title = $this->dom->getElementsByTagName("title");
if ($title->length) { if ($title->length) {
$this->getCanvas()->add_info("Title", trim($title->item(0)->nodeValue)); $canvas->add_info("Title", trim($title->item(0)->nodeValue));
} }
$metas = $this->dom->getElementsByTagName("meta"); $metas = $this->dom->getElementsByTagName("meta");
@ -813,21 +820,22 @@ class Dompdf
"keywords" => "Keywords", "keywords" => "Keywords",
"description" => "Subject", "description" => "Subject",
); );
/** @var \DOMElement $meta */
foreach ($metas as $meta) { foreach ($metas as $meta) {
$name = mb_strtolower($meta->getAttribute("name")); $name = mb_strtolower($meta->getAttribute("name"));
$value = trim($meta->getAttribute("content")); $value = trim($meta->getAttribute("content"));
if (isset($labels[$name])) { if (isset($labels[$name])) {
$this->pdf->add_info($labels[$name], $value); $canvas->add_info($labels[$name], $value);
continue; continue;
} }
if ($name === "dompdf.view" && $this->parseDefaultView($value)) { if ($name === "dompdf.view" && $this->parseDefaultView($value)) {
$this->getCanvas()->set_default_view($this->defaultView, $this->defaultViewOptions); $canvas->set_default_view($this->defaultView, $this->defaultViewOptions);
} }
} }
$root->set_containing_block(0, 0, $this->getCanvas()->get_width(), $this->getCanvas()->get_height()); $root->set_containing_block(0, 0,$canvas->get_width(), $canvas->get_height());
$root->set_renderer(new Renderer($this)); $root->set_renderer(new Renderer($this));
// This is where the magic happens: // This is where the magic happens:
@ -842,11 +850,19 @@ class Dompdf
foreach ($_dompdf_warnings as $msg) { foreach ($_dompdf_warnings as $msg) {
echo $msg . "\n"; echo $msg . "\n";
} }
echo $this->getCanvas()->get_cpdf()->messages;
if ($canvas instanceof CPDF) {
echo $canvas->get_cpdf()->messages;
}
echo '</pre>'; echo '</pre>';
flush(); flush();
} }
if ($logOutputFile && is_writable($logOutputFile)) {
$this->write_log();
ob_end_clean();
}
$this->restoreLocale(); $this->restoreLocale();
} }
@ -855,8 +871,9 @@ class Dompdf
*/ */
public function add_info($label, $value) public function add_info($label, $value)
{ {
if (!is_null($this->pdf)) { $canvas = $this->getCanvas();
$this->pdf->add_info($label, $value); if (!is_null($canvas)) {
$canvas->add_info($label, $value);
} }
} }
@ -867,7 +884,7 @@ class Dompdf
*/ */
private function write_log() private function write_log()
{ {
$log_output_file = $this->get_option("log_output_file"); $log_output_file = $this->getOptions()->getLogOutputFile();
if (!$log_output_file || !is_writable($log_output_file)) { if (!$log_output_file || !is_writable($log_output_file)) {
return; return;
} }
@ -884,23 +901,18 @@ class Dompdf
($this->quirksmode ? "<span style='color: #d00'> ON</span>" : "<span style='color: #0d0'>OFF</span>") . ($this->quirksmode ? "<span style='color: #d00'> ON</span>" : "<span style='color: #0d0'>OFF</span>") .
"</span><br />", $frames, $memory, $time); "</span><br />", $frames, $memory, $time);
$out .= ob_get_clean(); $out .= ob_get_contents();
ob_clean();
$log_output_file = $this->get_option("log_output_file");
file_put_contents($log_output_file, $out); file_put_contents($log_output_file, $out);
} }
/** /**
* Streams the PDF to the client * Streams the PDF to the client.
* *
* The file will open a download dialog by default. The options * The file will open a download dialog by default. The options
* parameter controls the output. Accepted options (array keys) are: * parameter controls the output. Accepted options (array keys) are:
* *
* 'Accept-Ranges' => 1 or 0 (=default): Send an 'Accept-Ranges:'
* HTTP header, see https://tools.ietf.org/html/rfc2616#section-14.5
* This header seems to have caused some problems, despite the fact
* that it is supposed to solve them, so I am leaving it off by default.
*
* 'compress' = > 1 (=default) or 0: * 'compress' = > 1 (=default) or 0:
* Apply content stream compression * Apply content stream compression
* *
@ -911,45 +923,40 @@ class Dompdf
* @param string $filename the name of the streamed file * @param string $filename the name of the streamed file
* @param array $options header options (see above) * @param array $options header options (see above)
*/ */
public function stream($filename = 'document.pdf', $options = null) public function stream($filename = "document.pdf", $options = array())
{ {
$this->saveLocale(); $this->saveLocale();
$this->write_log(); $canvas = $this->getCanvas();
if (!is_null($canvas)) {
if (!is_null($this->pdf)) { $canvas->stream($filename, $options);
$this->pdf->stream($filename, $options);
} }
$this->restoreLocale(); $this->restoreLocale();
} }
/** /**
* Returns the PDF as a string * Returns the PDF as a string.
*
* The file will open a download dialog by default. The options
* parameter controls the output. Accepted options are:
* *
* The options parameter controls the output. Accepted options are:
* *
* 'compress' = > 1 or 0 - apply content stream compression, this is * 'compress' = > 1 or 0 - apply content stream compression, this is
* on (1) by default * on (1) by default
* *
*
* @param array $options options (see above) * @param array $options options (see above)
* *
* @return string * @return string
*/ */
public function output($options = null) public function output($options = array())
{ {
$this->saveLocale(); $this->saveLocale();
$this->write_log(); $canvas = $this->getCanvas();
if (is_null($canvas)) {
if (is_null($this->pdf)) {
return null; return null;
} }
$output = $this->pdf->output($options); $output = $canvas->output($options);
$this->restoreLocale(); $this->restoreLocale();
@ -998,7 +1005,7 @@ class Dompdf
$this->options->set($key, $value); $this->options->set($key, $value);
return $this; return $this;
} }
/** /**
* @param array $options * @param array $options
* @return $this * @return $this
@ -1034,6 +1041,34 @@ class Dompdf
return $this; return $this;
} }
/**
* Gets the paper size
*
* @param null|string|array $paperSize
* @return int[] A four-element integer array
*/
public function getPaperSize($paperSize = null)
{
$size = $paperSize !== null ? $paperSize : $this->paperSize;
if (is_array($size)) {
return $size;
} else if (isset(Adapter\CPDF::$PAPER_SIZES[mb_strtolower($size)])) {
return Adapter\CPDF::$PAPER_SIZES[mb_strtolower($size)];
} else {
return Adapter\CPDF::$PAPER_SIZES["letter"];
}
}
/**
* Gets the paper orientation
*
* @return string Either "portrait" or "landscape"
*/
public function getPaperOrientation()
{
return $this->paperOrientation;
}
/** /**
* @param FrameTree $tree * @param FrameTree $tree
* @return $this * @return $this
@ -1259,7 +1294,6 @@ class Dompdf
*/ */
public function setCanvas(Canvas $canvas) public function setCanvas(Canvas $canvas)
{ {
$this->pdf = $canvas;
$this->canvas = $canvas; $this->canvas = $canvas;
return $this; return $this;
} }
@ -1280,9 +1314,6 @@ class Dompdf
*/ */
public function getCanvas() public function getCanvas()
{ {
if (null === $this->canvas && null !== $this->pdf) {
return $this->pdf;
}
return $this->canvas; return $this->canvas;
} }
@ -1455,10 +1486,10 @@ class Dompdf
{ {
return $this->fontMetrics; return $this->fontMetrics;
} }
/** /**
* PHP5 overloaded getter * PHP5 overloaded getter
* Along with {@link Dompdf::__set()} __get() provides access to all * Along with {@link Dompdf::__set()} __get() provides access to all
* properties directly. Typically __get() is not called directly outside * properties directly. Typically __get() is not called directly outside
* of this class. * of this class.
* *

View File

@ -22,9 +22,8 @@ class Exception extends \Exception
* @param string $message Error message * @param string $message Error message
* @param int $code Error code * @param int $code Error code
*/ */
function __construct($message = null, $code = 0) public function __construct($message = null, $code = 0)
{ {
parent::__construct($message, $code); parent::__construct($message, $code);
} }
} }

View File

@ -84,10 +84,10 @@ class FontMetrics
* Saves the stored font family cache * Saves the stored font family cache
* *
* The name and location of the cache file are determined by {@link * The name and location of the cache file are determined by {@link
* FontMetrics::CACHE_FILE}. This file should be writable by the * FontMetrics::CACHE_FILE}. This file should be writable by the
* webserver process. * webserver process.
* *
* @see Font_Metrics::load_font_families() * @see FontMetrics::loadFontFamilies()
*/ */
public function saveFontFamilies() public function saveFontFamilies()
{ {
@ -118,84 +118,38 @@ class FontMetrics
/** /**
* Loads the stored font family cache * Loads the stored font family cache
* *
* @see save_font_families() * @see FontMetrics::saveFontFamilies()
*/ */
public function loadFontFamilies() public function loadFontFamilies()
{ {
$fontDir = $this->getOptions()->getFontDir(); $fontDir = $this->getOptions()->getFontDir();
$rootDir = $this->getOptions()->getRootDir(); $rootDir = $this->getOptions()->getRootDir();
// FIXME: tempoarary define constants for cache files <= v0.6.2 // FIXME: tempoarary define constants for cache files <= v0.6.2
if (!defined("DOMPDF_DIR")) { define("DOMPDF_DIR", $rootDir); } if (!defined("DOMPDF_DIR")) { define("DOMPDF_DIR", $rootDir); }
if (!defined("DOMPDF_FONT_DIR")) { define("DOMPDF_FONT_DIR", $fontDir); } if (!defined("DOMPDF_FONT_DIR")) { define("DOMPDF_FONT_DIR", $fontDir); }
$file = $rootDir . "/lib/fonts/dompdf_font_family_cache.dist.php"; $file = $rootDir . "/lib/fonts/dompdf_font_family_cache.dist.php";
$distFonts = require $file; $distFonts = require $file;
// FIXME: temporary step for font cache created before the font cache fix
if (is_readable($fontDir . DIRECTORY_SEPARATOR . "dompdf_font_family_cache")) {
$oldFonts = require $fontDir . DIRECTORY_SEPARATOR . "dompdf_font_family_cache";
// If the font family cache is still in the old format
if ($oldFonts === 1) {
$cacheData = file_get_contents($fontDir . DIRECTORY_SEPARATOR . "dompdf_font_family_cache");
file_put_contents($fontDir . DIRECTORY_SEPARATOR . "dompdf_font_family_cache", "<" . "?php return $cacheData ?" . ">");
$oldFonts = require $fontDir . DIRECTORY_SEPARATOR . "dompdf_font_family_cache";
}
$distFonts += $oldFonts;
}
if (!is_readable($this->getCacheFile())) { if (!is_readable($this->getCacheFile())) {
$this->fontLookup = $distFonts; $this->fontLookup = $distFonts;
return; return;
} }
$cacheData = require $this->getCacheFile(); $cacheData = require $this->getCacheFile();
// If the font family cache is still in the old format
if ($cacheData === 1) {
$cacheData = file_get_contents($this->getCacheFile());
file_put_contents($this->getCacheFile(), "<" . "?php return $cacheData ?" . ">");
$this->fontLookup = require $this->getCacheFile();
}
$this->fontLookup = array(); $this->fontLookup = array();
foreach ($cacheData as $key => $value) { if (is_array($this->fontLookup)) {
$this->fontLookup[stripslashes($key)] = $value; foreach ($cacheData as $key => $value) {
$this->fontLookup[stripslashes($key)] = $value;
}
} }
// Merge provided fonts // Merge provided fonts
$this->fontLookup += $distFonts; $this->fontLookup += $distFonts;
} }
/**
* @param array $files
* @return array
* @deprecated
*/
public function install_fonts($files)
{
return $this->installFonts($files);
}
/**
* @param array $files
* @return array
*/
public function installFonts(array $files)
{
$names = array();
foreach ($files as $file) {
$font = Font::load($file);
$records = $font->getData("name", "records");
$type = $this->getType($records[2]);
$names[mb_strtolower($records[1])][$type] = $file;
$font->close();
}
return $names;
}
/** /**
* @param array $style * @param array $style
* @param string $remote_file * @param string $remote_file
@ -216,7 +170,6 @@ class FontMetrics
*/ */
public function registerFont($style, $remoteFile, $context = null) public function registerFont($style, $remoteFile, $context = null)
{ {
$fontDir = $this->getOptions()->getFontDir();
$fontname = mb_strtolower($style["family"]); $fontname = mb_strtolower($style["family"]);
$families = $this->getFontFamilies(); $families = $this->getFontFamilies();
@ -225,52 +178,56 @@ class FontMetrics
$entry = $families[$fontname]; $entry = $families[$fontname];
} }
$localFile = $fontDir . DIRECTORY_SEPARATOR . md5($remoteFile);
$localTempFile = $this->options->get('tempDir') . "/" . md5($remoteFile);
$cacheEntry = $localFile;
$localFile .= ".ttf";
$styleString = $this->getType("{$style['weight']} {$style['style']}"); $styleString = $this->getType("{$style['weight']} {$style['style']}");
if (isset($entry[$styleString])) {
if ( !isset($entry[$styleString]) ) { return true;
$entry[$styleString] = $cacheEntry;
// Download the remote file
$remoteFileContent = @file_get_contents($remoteFile, null, $context);
if (false === $remoteFileContent) {
return false;
}
file_put_contents($localTempFile, $remoteFileContent);
$font = Font::load($localTempFile);
if (!$font) {
unlink($localTempFile);
return false;
}
$font->parse();
$font->saveAdobeFontMetrics("$cacheEntry.ufm");
$font->close();
unlink($localTempFile);
if ( !file_exists("$cacheEntry.ufm") ) {
return false;
}
// Save the changes
file_put_contents($localFile, file_get_contents($remoteFile, null, $context));
if ( !file_exists($localFile) ) {
unlink("$cacheEntry.ufm");
return false;
}
$this->setFontFamily($fontname, $entry);
$this->saveFontFamilies();
} }
$fontDir = $this->getOptions()->getFontDir();
$remoteHash = md5($remoteFile);
$localFile = $fontDir . DIRECTORY_SEPARATOR . $remoteHash;
$localTempFile = tempnam($this->options->get("tempDir"), "dompdf-font-");
$cacheEntry = $localFile;
$localFile .= ".".strtolower(pathinfo(parse_url($remoteFile, PHP_URL_PATH),PATHINFO_EXTENSION));
$entry[$styleString] = $cacheEntry;
// Download the remote file
list($remoteFileContent, $http_response_header) = @Helpers::getFileContent($remoteFile, $context);
if (false === $remoteFileContent) {
return false;
}
file_put_contents($localTempFile, $remoteFileContent);
$font = Font::load($localTempFile);
if (!$font) {
unlink($localTempFile);
return false;
}
$font->parse();
$font->saveAdobeFontMetrics("$cacheEntry.ufm");
$font->close();
unlink($localTempFile);
if ( !file_exists("$cacheEntry.ufm") ) {
return false;
}
// Save the changes
file_put_contents($localFile, $remoteFileContent);
if ( !file_exists($localFile) ) {
unlink("$cacheEntry.ufm");
return false;
}
$this->setFontFamily($fontname, $entry);
$this->saveFontFamilies();
return true; return true;
} }
@ -494,30 +451,6 @@ class FontMetrics
return $type; return $type;
} }
/**
* @return array
* @deprecated
*/
public function get_system_fonts()
{
return $this->getSystemFonts();
}
/**
* @return array
*/
public function getSystemFonts()
{
$files = glob("/usr/share/fonts/truetype/*.ttf") +
glob("/usr/share/fonts/truetype/*/*.ttf") +
glob("/usr/share/fonts/truetype/*/*/*.ttf") +
glob("C:\\Windows\\fonts\\*.ttf") +
glob("C:\\WinNT\\fonts\\*.ttf") +
glob("/mnt/c_drive/WINDOWS/Fonts/");
return $this->installFonts($files);
}
/** /**
* @return array * @return array
* @deprecated * @deprecated
@ -588,8 +521,9 @@ class FontMetrics
*/ */
public function setCanvas(Canvas $canvas) public function setCanvas(Canvas $canvas)
{ {
$this->pdf = $canvas;
$this->canvas = $canvas; $this->canvas = $canvas;
// Still write deprecated pdf for now. It might be used by a parent class.
$this->pdf = $canvas;
return $this; return $this;
} }

View File

@ -325,6 +325,14 @@ class Frame
$this->_style = null; $this->_style = null;
unset($this->_style); unset($this->_style);
$this->_style = clone $this->_original_style; $this->_style = clone $this->_original_style;
// If this represents a generated node then child nodes represent generated content.
// Remove the children since the content will be generated next time this frame is reflowed.
if ($this->_node->nodeName === "dompdf_generated" && $this->_style->content != "normal") {
foreach ($this->get_children() as $child) {
$this->remove_child($child);
}
}
} }
/** /**
@ -426,7 +434,7 @@ class Frame
/** /**
* Containing block dimensions * Containing block dimensions
* *
* @param $i string The key of the wanted containing block's dimension (x, y, x, h) * @param $i string The key of the wanted containing block's dimension (x, y, w, h)
* *
* @return float[]|float * @return float[]|float
*/ */
@ -467,7 +475,7 @@ class Frame
{ {
$style = $this->_style; $style = $this->_style;
return $style->length_in_pt(array( return (float)$style->length_in_pt(array(
$style->height, $style->height,
$style->margin_top, $style->margin_top,
$style->margin_bottom, $style->margin_bottom,
@ -488,7 +496,7 @@ class Frame
{ {
$style = $this->_style; $style = $this->_style;
return $style->length_in_pt(array( return (float)$style->length_in_pt(array(
$style->width, $style->width,
$style->margin_left, $style->margin_left,
$style->margin_right, $style->margin_right,
@ -506,7 +514,7 @@ class Frame
{ {
$style = $this->_style; $style = $this->_style;
return $style->length_in_pt(array( return (float)$style->length_in_pt(array(
//$style->height, //$style->height,
$style->margin_top, $style->margin_top,
$style->margin_bottom, $style->margin_bottom,
@ -517,6 +525,38 @@ class Frame
), $this->_containing_block["h"]); ), $this->_containing_block["h"]);
} }
/**
* Return the content box (x,y,w,h) of the frame
*
* @return array
*/
public function get_content_box()
{
$style = $this->_style;
$cb = $this->_containing_block;
$x = $this->_position["x"] +
(float)$style->length_in_pt(array($style->margin_left,
$style->border_left_width,
$style->padding_left),
$cb["w"]);
$y = $this->_position["y"] +
(float)$style->length_in_pt(array($style->margin_top,
$style->border_top_width,
$style->padding_top),
$cb["h"]);
$w = $style->length_in_pt($style->width, $cb["w"]);
$h = $style->length_in_pt($style->height, $cb["h"]);
return array(0 => $x, "x" => $x,
1 => $y, "y" => $y,
2 => $w, "w" => $w,
3 => $h, "h" => $h);
}
/** /**
* Return the padding box (x,y,w,h) of the frame * Return the padding box (x,y,w,h) of the frame
* *
@ -528,12 +568,12 @@ class Frame
$cb = $this->_containing_block; $cb = $this->_containing_block;
$x = $this->_position["x"] + $x = $this->_position["x"] +
$style->length_in_pt(array($style->margin_left, (float)$style->length_in_pt(array($style->margin_left,
$style->border_left_width), $style->border_left_width),
$cb["w"]); $cb["w"]);
$y = $this->_position["y"] + $y = $this->_position["y"] +
$style->length_in_pt(array($style->margin_top, (float)$style->length_in_pt(array($style->margin_top,
$style->border_top_width), $style->border_top_width),
$cb["h"]); $cb["h"]);
@ -563,9 +603,9 @@ class Frame
$style = $this->_style; $style = $this->_style;
$cb = $this->_containing_block; $cb = $this->_containing_block;
$x = $this->_position["x"] + $style->length_in_pt($style->margin_left, $cb["w"]); $x = $this->_position["x"] + (float)$style->length_in_pt($style->margin_left, $cb["w"]);
$y = $this->_position["y"] + $style->length_in_pt($style->margin_top, $cb["h"]); $y = $this->_position["y"] + (float)$style->length_in_pt($style->margin_top, $cb["h"]);
$w = $style->length_in_pt(array($style->border_left_width, $w = $style->length_in_pt(array($style->border_left_width,
$style->padding_left, $style->padding_left,
@ -716,6 +756,56 @@ class Frame
$this->_containing_line = $line; $this->_containing_line = $line;
} }
/**
* Indicates if the margin height is auto sized
*
* @return bool
*/
public function is_auto_height()
{
$style = $this->_style;
return in_array(
"auto",
array(
$style->height,
$style->margin_top,
$style->margin_bottom,
$style->border_top_width,
$style->border_bottom_width,
$style->padding_top,
$style->padding_bottom,
$this->_containing_block["h"]
),
true
);
}
/**
* Indicates if the margin width is auto sized
*
* @return bool
*/
public function is_auto_width()
{
$style = $this->_style;
return in_array(
"auto",
array(
$style->width,
$style->margin_left,
$style->margin_right,
$style->border_left_width,
$style->border_right_width,
$style->padding_left,
$style->padding_right,
$this->_containing_block["w"]
),
true
);
}
/** /**
* Tells if the frame is a text node * Tells if the frame is a text node
* *
@ -770,6 +860,18 @@ class Frame
return $this->_is_cache["block"] = in_array($this->get_style()->display, Style::$BLOCK_TYPES); return $this->_is_cache["block"] = in_array($this->get_style()->display, Style::$BLOCK_TYPES);
} }
/**
* @return bool
*/
public function is_inline_block()
{
if (isset($this->_is_cache["inline_block"])) {
return $this->_is_cache["inline_block"];
}
return $this->_is_cache["inline_block"] = ($this->get_style()->display === 'inline-block');
}
/** /**
* @return bool * @return bool
*/ */
@ -860,6 +962,11 @@ class Frame
} }
$child->_parent = $this; $child->_parent = $this;
$decorator = $child->get_decorator();
// force an update to the cached parent
if ($decorator !== null) {
$decorator->get_parent(false);
}
$child->_next_sibling = null; $child->_next_sibling = null;
// Handle the first child // Handle the first child

View File

@ -15,6 +15,7 @@ use Dompdf\FrameDecorator\AbstractFrameDecorator;
use DOMXPath; use DOMXPath;
use Dompdf\FrameDecorator\Page as PageFrameDecorator; use Dompdf\FrameDecorator\Page as PageFrameDecorator;
use Dompdf\FrameReflower\Page as PageFrameReflower; use Dompdf\FrameReflower\Page as PageFrameReflower;
use Dompdf\Positioner\AbstractPositioner;
/** /**
* Contains frame decorating logic * Contains frame decorating logic
@ -30,6 +31,13 @@ use Dompdf\FrameReflower\Page as PageFrameReflower;
class Factory class Factory
{ {
/**
* Array of positioners for specific frame types
*
* @var AbstractPositioner[]
*/
protected static $_positioners;
/** /**
* Decorate the root Frame * Decorate the root Frame
* *
@ -76,12 +84,15 @@ class Factory
switch ($display) { switch ($display) {
case "flex": //FIXME: display type not yet supported
case "table-caption": //FIXME: display type not yet supported
case "block": case "block":
$positioner = "Block"; $positioner = "Block";
$decorator = "Block"; $decorator = "Block";
$reflower = "Block"; $reflower = "Block";
break; break;
case "inline-flex": //FIXME: display type not yet supported
case "inline-block": case "inline-block":
$positioner = "Inline"; $positioner = "Inline";
$decorator = "Block"; $decorator = "Block";
@ -205,14 +216,13 @@ class Factory
$reflower = "Image"; $reflower = "Image";
} }
$positioner = "Dompdf\\Positioner\\$positioner";
$decorator = "Dompdf\\FrameDecorator\\$decorator"; $decorator = "Dompdf\\FrameDecorator\\$decorator";
$reflower = "Dompdf\\FrameReflower\\$reflower"; $reflower = "Dompdf\\FrameReflower\\$reflower";
/** @var AbstractFrameDecorator $deco */ /** @var AbstractFrameDecorator $deco */
$deco = new $decorator($frame, $dompdf); $deco = new $decorator($frame, $dompdf);
$deco->set_positioner(new $positioner($deco)); $deco->set_positioner(self::getPositionerInstance($positioner));
$deco->set_reflower(new $reflower($deco, $dompdf->getFontMetrics())); $deco->set_reflower(new $reflower($deco, $dompdf->getFontMetrics()));
if ($root) { if ($root) {
@ -221,7 +231,7 @@ class Factory
if ($display === "list-item") { if ($display === "list-item") {
// Insert a list-bullet frame // Insert a list-bullet frame
$xml = $dompdf->get_dom(); $xml = $dompdf->getDom();
$bullet_node = $xml->createElement("bullet"); // arbitrary choice $bullet_node = $xml->createElement("bullet"); // arbitrary choice
$b_f = new Frame($bullet_node); $b_f = new Frame($bullet_node);
@ -241,7 +251,7 @@ class Factory
if (!$parent_node->hasAttribute("dompdf-counter")) { if (!$parent_node->hasAttribute("dompdf-counter")) {
$index = ($parent_node->hasAttribute("start") ? $parent_node->getAttribute("start") : 1); $index = ($parent_node->hasAttribute("start") ? $parent_node->getAttribute("start") : 1);
} else { } else {
$index = $parent_node->getAttribute("dompdf-counter") + 1; $index = (int)$parent_node->getAttribute("dompdf-counter") + 1;
} }
} }
@ -249,7 +259,7 @@ class Factory
$bullet_node->setAttribute("dompdf-counter", $index); $bullet_node->setAttribute("dompdf-counter", $index);
} }
$new_style = $dompdf->get_css()->create_style(); $new_style = $dompdf->getCss()->create_style();
$new_style->display = "-dompdf-list-bullet"; $new_style->display = "-dompdf-list-bullet";
$new_style->inherit($style); $new_style->inherit($style);
$b_f->set_style($new_style); $b_f->set_style($new_style);
@ -259,4 +269,19 @@ class Factory
return $deco; return $deco;
} }
/**
* Creates Positioners
*
* @param string $type type of positioner to use
* @return AbstractPositioner
*/
protected static function getPositionerInstance($type)
{
if (!isset(self::$_positioners[$type])) {
$class = '\\Dompdf\\Positioner\\'.$type;
self::$_positioners[$type] = new $class();
}
return self::$_positioners[$type];
}
} }

View File

@ -1,6 +1,7 @@
<?php <?php
namespace Dompdf\Frame; namespace Dompdf\Frame;
use Dompdf\Frame;
use IteratorAggregate; use IteratorAggregate;
/** /**
@ -12,12 +13,12 @@ use IteratorAggregate;
class FrameList implements IteratorAggregate class FrameList implements IteratorAggregate
{ {
/** /**
* @var * @var Frame
*/ */
protected $_frame; protected $_frame;
/** /**
* @param $frame * @param Frame $frame
*/ */
function __construct($frame) function __construct($frame)
{ {

View File

@ -4,11 +4,11 @@ namespace Dompdf\Frame;
use DOMDocument; use DOMDocument;
use DOMNode; use DOMNode;
use DOMElement;
use DOMXPath; use DOMXPath;
use Dompdf\Exception; use Dompdf\Exception;
use Dompdf\Frame; use Dompdf\Frame;
use Dompdf\FrameDecorator\Page;
/** /**
* @package dompdf * @package dompdf
@ -44,7 +44,6 @@ class FrameTree
"title", "title",
"colgroup", "colgroup",
"noembed", "noembed",
"noscript",
"param", "param",
"#comment" "#comment"
); );
@ -103,7 +102,7 @@ class FrameTree
/** /**
* Returns the root frame of the tree * Returns the root frame of the tree
* *
* @return \Dompdf\FrameDecorator\Page * @return Frame
*/ */
public function get_root() public function get_root()
{ {
@ -115,7 +114,7 @@ class FrameTree
* *
* @param string $id * @param string $id
* *
* @return Frame * @return Frame|null
*/ */
public function get_frame($id) public function get_frame($id)
{ {
@ -160,20 +159,37 @@ class FrameTree
// Move table caption before the table // Move table caption before the table
// FIXME find a better way to deal with it... // FIXME find a better way to deal with it...
$captions = $xp->query("//table/caption"); $captions = $xp->query('//table/caption');
foreach ($captions as $caption) { foreach ($captions as $caption) {
$table = $caption->parentNode; $table = $caption->parentNode;
$table->parentNode->insertBefore($caption, $table); $table->parentNode->insertBefore($caption, $table);
} }
$rows = $xp->query("//table/tr"); $firstRows = $xp->query('//table/tr[1]');
foreach ($rows as $row) { /** @var DOMElement $tableChild */
$tbody = $this->_dom->createElement("tbody"); foreach ($firstRows as $tableChild) {
$tbody = $row->parentNode->insertBefore($tbody, $row); $tbody = $this->_dom->createElement('tbody');
$tbody->appendChild($row); $tableNode = $tableChild->parentNode;
do {
if ($tableChild->nodeName === 'tr') {
$tmpNode = $tableChild;
$tableChild = $tableChild->nextSibling;
$tableNode->removeChild($tmpNode);
$tbody->appendChild($tmpNode);
} else {
if ($tbody->hasChildNodes() === true) {
$tableNode->insertBefore($tbody, $tableChild);
$tbody = $this->_dom->createElement('tbody');
}
$tableChild = $tableChild->nextSibling;
}
} while ($tableChild);
if ($tbody->hasChildNodes() === true) {
$tableNode->appendChild($tbody);
}
} }
} }
// FIXME: temporary hack, preferably we will improve rendering of sequential #text nodes // FIXME: temporary hack, preferably we will improve rendering of sequential #text nodes
/** /**
* Remove a child from a node * Remove a child from a node
@ -181,9 +197,9 @@ class FrameTree
* Remove a child from a node. If the removed node results in two * Remove a child from a node. If the removed node results in two
* adjacent #text nodes then combine them. * adjacent #text nodes then combine them.
* *
* @param DONNode $node the current DOMNode being considered * @param DOMNode $node the current DOMNode being considered
* @param array $children an array of nodes that are the children of $node * @param array $children an array of nodes that are the children of $node
* @param $index index from the $children array of the node to remove * @param int $index index from the $children array of the node to remove
*/ */
protected function _remove_node(DOMNode $node, array &$children, $index) protected function _remove_node(DOMNode $node, array &$children, $index)
{ {
@ -192,8 +208,7 @@ class FrameTree
$nextChild = $child->nextSibling; $nextChild = $child->nextSibling;
$node->removeChild($child); $node->removeChild($child);
if (isset($previousChild, $nextChild)) { if (isset($previousChild, $nextChild)) {
if ($previousChild->nodeName === "#text" && $nextChild->nodeName === "#text") if ($previousChild->nodeName === "#text" && $nextChild->nodeName === "#text") {
{
$previousChild->nodeValue .= $nextChild->nodeValue; $previousChild->nodeValue .= $nextChild->nodeValue;
$this->_remove_node($node, $children, $index+1); $this->_remove_node($node, $children, $index+1);
} }
@ -222,7 +237,7 @@ class FrameTree
if (!$node->hasChildNodes()) { if (!$node->hasChildNodes()) {
return $frame; return $frame;
} }
// Store the children in an array so that the tree can be modified // Store the children in an array so that the tree can be modified
$children = array(); $children = array();
$length = $node->childNodes->length; $length = $node->childNodes->length;
@ -234,7 +249,7 @@ class FrameTree
while ($index < count($children)) { while ($index < count($children)) {
$child = $children[$index]; $child = $children[$index];
$nodeName = strtolower($child->nodeName); $nodeName = strtolower($child->nodeName);
// Skip non-displaying nodes // Skip non-displaying nodes
if (in_array($nodeName, self::$HIDDEN_TAGS)) { if (in_array($nodeName, self::$HIDDEN_TAGS)) {
if ($nodeName !== "head" && $nodeName !== "style") { if ($nodeName !== "head" && $nodeName !== "style") {
@ -254,7 +269,7 @@ class FrameTree
$this->_remove_node($node, $children, $index); $this->_remove_node($node, $children, $index);
continue; continue;
} }
if (is_object($child)) { if (is_object($child)) {
$frame->append_child($this->_build_tree_r($child), false); $frame->append_child($this->_build_tree_r($child), false);
} }
@ -265,13 +280,13 @@ class FrameTree
} }
/** /**
* @param DOMNode $node * @param DOMElement $node
* @param DOMNode $new_node * @param DOMElement $new_node
* @param string $pos * @param string $pos
* *
* @return mixed * @return mixed
*/ */
public function insert_node(DOMNode $node, DOMNode $new_node, $pos) public function insert_node(DOMElement $node, DOMElement $new_node, $pos)
{ {
if ($pos === "after" || !$node->firstChild) { if ($pos === "after" || !$node->firstChild) {
$node->appendChild($new_node); $node->appendChild($new_node);

View File

@ -82,6 +82,13 @@ abstract class AbstractFrameDecorator extends Frame
*/ */
private $_positionned_parent; private $_positionned_parent;
/**
* Cache for the get_parent wehile loop results
*
* @var Frame
*/
private $_cached_parent;
/** /**
* Class constructor * Class constructor
* *
@ -146,12 +153,12 @@ abstract class AbstractFrameDecorator extends Frame
function deep_copy() function deep_copy()
{ {
$node = $this->_frame->get_node(); $node = $this->_frame->get_node();
if ($node instanceof DOMElement && $node->hasAttribute("id")) { if ($node instanceof DOMElement && $node->hasAttribute("id")) {
$node->setAttribute("data-dompdf-original-id", $node->getAttribute("id")); $node->setAttribute("data-dompdf-original-id", $node->getAttribute("id"));
$node->removeAttribute("id"); $node->removeAttribute("id");
} }
$frame = new Frame($node->cloneNode()); $frame = new Frame($node->cloneNode());
$frame->set_style(clone $this->_frame->get_original_style()); $frame->set_style(clone $this->_frame->get_original_style());
@ -173,6 +180,8 @@ abstract class AbstractFrameDecorator extends Frame
$this->_counters = array(); $this->_counters = array();
$this->_cached_parent = null; //clear get_parent() cache
// Reset all children // Reset all children
foreach ($this->get_children() as $child) { foreach ($this->get_children() as $child) {
$child->reset(); $child->reset();
@ -180,6 +189,10 @@ abstract class AbstractFrameDecorator extends Frame
} }
// Getters ----------- // Getters -----------
/**
* @return string
*/
function get_id() function get_id()
{ {
return $this->_frame->get_id(); return $this->_frame->get_id();
@ -261,6 +274,14 @@ abstract class AbstractFrameDecorator extends Frame
return $this->_frame->get_margin_width(); return $this->_frame->get_margin_width();
} }
/**
* @return array
*/
function get_content_box()
{
return $this->_frame->get_content_box();
}
/** /**
* @return array * @return array
*/ */
@ -313,6 +334,22 @@ abstract class AbstractFrameDecorator extends Frame
$this->_frame->set_position($x, $y); $this->_frame->set_position($x, $y);
} }
/**
* @return bool
*/
function is_auto_height()
{
return $this->_frame->is_auto_height();
}
/**
* @return bool
*/
function is_auto_width()
{
return $this->_frame->is_auto_width();
}
/** /**
* @return string * @return string
*/ */
@ -372,15 +409,17 @@ abstract class AbstractFrameDecorator extends Frame
*/ */
function insert_child_after(Frame $new_child, Frame $ref, $update_node = true) function insert_child_after(Frame $new_child, Frame $ref, $update_node = true)
{ {
while ($new_child instanceof AbstractFrameDecorator) { $insert_frame = $new_child;
$new_child = $new_child->_frame; while ($insert_frame instanceof AbstractFrameDecorator) {
$insert_frame = $insert_frame->_frame;
} }
while ($ref instanceof AbstractFrameDecorator) { $reference_frame = $ref;
$ref = $ref->_frame; while ($reference_frame instanceof AbstractFrameDecorator) {
$reference_frame = $reference_frame->_frame;
} }
$this->_frame->insert_child_after($new_child, $ref, $update_node); $this->_frame->insert_child_after($insert_frame, $reference_frame, $update_node);
} }
/** /**
@ -401,22 +440,21 @@ abstract class AbstractFrameDecorator extends Frame
/** /**
* @return AbstractFrameDecorator * @return AbstractFrameDecorator
*/ */
function get_parent() function get_parent($use_cache = true)
{ {
if ($use_cache && $this->_cached_parent) {
return $this->_cached_parent;
}
$p = $this->_frame->get_parent(); $p = $this->_frame->get_parent();
if ($p && $deco = $p->get_decorator()) { if ($p && $deco = $p->get_decorator()) {
while ($tmp = $deco->get_decorator()) { while ($tmp = $deco->get_decorator()) {
$deco = $tmp; $deco = $tmp;
} }
return $deco; return $this->_cached_parent = $deco;
} else { } else {
if ($p) { return $this->_cached_parent = $p;
return $p;
}
} }
return null;
} }
/** /**
@ -612,8 +650,10 @@ abstract class AbstractFrameDecorator extends Frame
{ {
// decrement any counters that were incremented on the current node, unless that node is the body // decrement any counters that were incremented on the current node, unless that node is the body
$style = $this->_frame->get_style(); $style = $this->_frame->get_style();
if ($this->_frame->get_node( if (
)->nodeName !== "body" && $style->counter_increment && ($decrement = $style->counter_increment) !== "none" $this->_frame->get_node()->nodeName !== "body" &&
$style->counter_increment &&
($decrement = $style->counter_increment) !== "none"
) { ) {
$this->decrement_counters($decrement); $this->decrement_counters($decrement);
} }
@ -624,8 +664,9 @@ abstract class AbstractFrameDecorator extends Frame
// it's been rendered, thus the position check) // it's been rendered, thus the position check)
if (!$this->is_text_node() && $this->get_node()->hasAttribute("dompdf_before_frame_id")) { if (!$this->is_text_node() && $this->get_node()->hasAttribute("dompdf_before_frame_id")) {
foreach ($this->_frame->get_children() as $child) { foreach ($this->_frame->get_children() as $child) {
if ($this->get_node()->getAttribute("dompdf_before_frame_id") == $child->get_id( if (
) && $child->get_position('x') !== null $this->get_node()->getAttribute("dompdf_before_frame_id") == $child->get_id() &&
$child->get_position('x') !== null
) { ) {
$style = $child->get_style(); $style = $child->get_style();
if ($style->counter_increment && ($decrement = $style->counter_increment) !== "none") { if ($style->counter_increment && ($decrement = $style->counter_increment) !== "none") {
@ -644,7 +685,7 @@ abstract class AbstractFrameDecorator extends Frame
} }
$node = $this->_frame->get_node(); $node = $this->_frame->get_node();
if ($node instanceof DOMElement && $node->hasAttribute("id")) { if ($node instanceof DOMElement && $node->hasAttribute("id")) {
$node->setAttribute("data-dompdf-original-id", $node->getAttribute("id")); $node->setAttribute("data-dompdf-original-id", $node->getAttribute("id"));
$node->removeAttribute("id"); $node->removeAttribute("id");
@ -654,6 +695,7 @@ abstract class AbstractFrameDecorator extends Frame
$split->reset(); $split->reset();
$split->get_original_style()->text_indent = 0; $split->get_original_style()->text_indent = 0;
$split->_splitted = true; $split->_splitted = true;
$split->_already_pushed = true;
// The body's properties must be kept // The body's properties must be kept
if ($node->nodeName !== "body") { if ($node->nodeName !== "body") {
@ -672,7 +714,13 @@ abstract class AbstractFrameDecorator extends Frame
$orig_style->page_break_before = "auto"; $orig_style->page_break_before = "auto";
} }
// recalculate the float offsets after paging
$this->get_parent()->insert_child_after($split, $this); $this->get_parent()->insert_child_after($split, $this);
if ($this instanceof Block) {
foreach ($this->get_line_boxes() as $index => $line_box) {
$line_box->get_float_offsets();
}
}
// Add $frame and all following siblings to the new split node // Add $frame and all following siblings to the new split node
$iter = $child; $iter = $child;
@ -680,7 +728,15 @@ abstract class AbstractFrameDecorator extends Frame
$frame = $iter; $frame = $iter;
$iter = $iter->get_next_sibling(); $iter = $iter->get_next_sibling();
$frame->reset(); $frame->reset();
$frame->_parent = $split;
$split->append_child($frame); $split->append_child($frame);
// recalculate the float offsets
if ($frame instanceof Block) {
foreach ($frame->get_line_boxes() as $index => $line_box) {
$line_box->get_float_offsets();
}
}
} }
$this->get_parent()->split($split, $force_pagebreak); $this->get_parent()->split($split, $force_pagebreak);
@ -692,11 +748,18 @@ abstract class AbstractFrameDecorator extends Frame
} }
} }
/**
* @param string $id
* @param int $value
*/
function reset_counter($id = self::DEFAULT_COUNTER, $value = 0) function reset_counter($id = self::DEFAULT_COUNTER, $value = 0)
{ {
$this->get_parent()->_counters[$id] = intval($value); $this->get_parent()->_counters[$id] = intval($value);
} }
/**
* @param $counters
*/
function decrement_counters($counters) function decrement_counters($counters)
{ {
foreach ($counters as $id => $increment) { foreach ($counters as $id => $increment) {
@ -704,6 +767,9 @@ abstract class AbstractFrameDecorator extends Frame
} }
} }
/**
* @param $counters
*/
function increment_counters($counters) function increment_counters($counters)
{ {
foreach ($counters as $id => $increment) { foreach ($counters as $id => $increment) {
@ -711,6 +777,10 @@ abstract class AbstractFrameDecorator extends Frame
} }
} }
/**
* @param string $id
* @param int $increment
*/
function increment_counter($id = self::DEFAULT_COUNTER, $increment = 1) function increment_counter($id = self::DEFAULT_COUNTER, $increment = 1)
{ {
$counter_frame = $this->lookup_counter_frame($id); $counter_frame = $this->lookup_counter_frame($id);
@ -724,6 +794,10 @@ abstract class AbstractFrameDecorator extends Frame
} }
} }
/**
* @param string $id
* @return AbstractFrameDecorator|null
*/
function lookup_counter_frame($id = self::DEFAULT_COUNTER) function lookup_counter_frame($id = self::DEFAULT_COUNTER)
{ {
$f = $this->get_parent(); $f = $this->get_parent();
@ -740,9 +814,17 @@ abstract class AbstractFrameDecorator extends Frame
$f = $fp; $f = $fp;
} }
return null;
} }
// TODO: What version is the best : this one or the one in ListBullet ? /**
* @param string $id
* @param string $type
* @return bool|string
*
* TODO: What version is the best : this one or the one in ListBullet ?
*/
function counter_value($id = self::DEFAULT_COUNTER, $type = "decimal") function counter_value($id = self::DEFAULT_COUNTER, $type = "decimal")
{ {
$type = mb_strtolower($type); $type = mb_strtolower($type);
@ -783,16 +865,27 @@ abstract class AbstractFrameDecorator extends Frame
} }
} }
/**
*
*/
final function position() final function position()
{ {
$this->_positioner->position(); $this->_positioner->position($this);
} }
/**
* @param $offset_x
* @param $offset_y
* @param bool $ignore_self
*/
final function move($offset_x, $offset_y, $ignore_self = false) final function move($offset_x, $offset_y, $ignore_self = false)
{ {
$this->_positioner->move($offset_x, $offset_y, $ignore_self); $this->_positioner->move($this, $offset_x, $offset_y, $ignore_self);
} }
/**
* @param Block|null $block
*/
final function reflow(Block $block = null) final function reflow(Block $block = null)
{ {
// Uncomment this to see the frames before they're laid out, instead of // Uncomment this to see the frames before they're laid out, instead of
@ -801,8 +894,21 @@ abstract class AbstractFrameDecorator extends Frame
$this->_reflower->reflow($block); $this->_reflower->reflow($block);
} }
/**
* @return array
*/
final function get_min_max_width() final function get_min_max_width()
{ {
return $this->_reflower->get_min_max_width(); return $this->_reflower->get_min_max_width();
} }
/**
* Determine current frame width based on contents
*
* @return float
*/
final function calculate_auto_width()
{
return $this->_reflower->calculate_auto_width();
}
} }

View File

@ -33,6 +33,11 @@ class Block extends AbstractFrameDecorator
*/ */
protected $_line_boxes; protected $_line_boxes;
/**
* Block constructor.
* @param Frame $frame
* @param Dompdf $dompdf
*/
function __construct(Frame $frame, Dompdf $dompdf) function __construct(Frame $frame, Dompdf $dompdf)
{ {
parent::__construct($frame, $dompdf); parent::__construct($frame, $dompdf);
@ -41,6 +46,9 @@ class Block extends AbstractFrameDecorator
$this->_cl = 0; $this->_cl = 0;
} }
/**
*
*/
function reset() function reset()
{ {
parent::reset(); parent::reset();
@ -73,6 +81,17 @@ class Block extends AbstractFrameDecorator
return $this->_line_boxes; return $this->_line_boxes;
} }
/**
* @param integer $line_number
* @return integer
*/
function set_current_line_number($line_number)
{
$line_boxes_count = count($this->_line_boxes);
$cl = max(min($line_number, $line_boxes_count), 0);
return ($this->_cl = $cl);
}
/** /**
* @param integer $i * @param integer $i
*/ */
@ -114,7 +133,6 @@ class Block extends AbstractFrameDecorator
// Handle inline frames (which are effectively wrappers) // Handle inline frames (which are effectively wrappers)
if ($frame instanceof Inline) { if ($frame instanceof Inline) {
// Handle line breaks // Handle line breaks
if ($frame->get_node()->nodeName === "br") { if ($frame->get_node()->nodeName === "br") {
$this->maximize_line_height($style->length_in_pt($style->line_height), $frame); $this->maximize_line_height($style->length_in_pt($style->line_height), $frame);
@ -130,14 +148,15 @@ class Block extends AbstractFrameDecorator
$frame->is_text_node() && $frame->is_text_node() &&
!$frame->is_pre() !$frame->is_pre()
) { ) {
$frame->set_text(ltrim($frame->get_text())); $frame->set_text(ltrim($frame->get_text()));
$frame->recalculate_width(); $frame->recalculate_width();
} }
$w = $frame->get_margin_width(); $w = $frame->get_margin_width();
if ($w == 0) { // FIXME: Why? Doesn't quite seem to be the correct thing to do,
// but does appear to be necessary. Hack to handle wrapped white space?
if ($w == 0 && $frame->get_node()->nodeName !== "hr") {
return; return;
} }
@ -177,6 +196,9 @@ class Block extends AbstractFrameDecorator
$this->maximize_line_height($frame->get_margin_height(), $frame); $this->maximize_line_height($frame->get_margin_height(), $frame);
} }
/**
* @param Frame $frame
*/
function remove_frames_from_line(Frame $frame) function remove_frames_from_line(Frame $frame)
{ {
// Search backwards through the lines for $frame // Search backwards through the lines for $frame
@ -221,11 +243,18 @@ class Block extends AbstractFrameDecorator
} }
} }
/**
* @param float $w
*/
function increase_line_width($w) function increase_line_width($w)
{ {
$this->_line_boxes[$this->_cl]->w += $w; $this->_line_boxes[$this->_cl]->w += $w;
} }
/**
* @param $val
* @param Frame $frame
*/
function maximize_line_height($val, Frame $frame) function maximize_line_height($val, Frame $frame)
{ {
if ($val > $this->_line_boxes[$this->_cl]->h) { if ($val > $this->_line_boxes[$this->_cl]->h) {
@ -234,6 +263,9 @@ class Block extends AbstractFrameDecorator
} }
} }
/**
* @param bool $br
*/
function add_line($br = false) function add_line($br = false)
{ {

View File

@ -10,7 +10,6 @@ namespace Dompdf\FrameDecorator;
use Dompdf\Dompdf; use Dompdf\Dompdf;
use Dompdf\Frame; use Dompdf\Frame;
use Dompdf\FontMetrics;
use Dompdf\Image\Cache; use Dompdf\Image\Cache;
/** /**
@ -47,14 +46,16 @@ class Image extends AbstractFrameDecorator
parent::__construct($frame, $dompdf); parent::__construct($frame, $dompdf);
$url = $frame->get_node()->getAttribute("src"); $url = $frame->get_node()->getAttribute("src");
$debug_png = $dompdf->get_option("debug_png"); $debug_png = $dompdf->getOptions()->getDebugPng();
if ($debug_png) print '[__construct ' . $url . ']'; if ($debug_png) {
print '[__construct ' . $url . ']';
}
list($this->_image_url, /*$type*/, $this->_image_msg) = Cache::resolve_url( list($this->_image_url, /*$type*/, $this->_image_msg) = Cache::resolve_url(
$url, $url,
$dompdf->get_protocol(), $dompdf->getProtocol(),
$dompdf->get_host(), $dompdf->getBaseHost(),
$dompdf->get_base_path(), $dompdf->getBasePath(),
$dompdf $dompdf
); );

View File

@ -8,6 +8,7 @@
*/ */
namespace Dompdf\FrameDecorator; namespace Dompdf\FrameDecorator;
use DOMElement;
use Dompdf\Dompdf; use Dompdf\Dompdf;
use Dompdf\Frame; use Dompdf\Frame;
use Dompdf\Exception; use Dompdf\Exception;
@ -21,11 +22,21 @@ use Dompdf\Exception;
class Inline extends AbstractFrameDecorator class Inline extends AbstractFrameDecorator
{ {
/**
* Inline constructor.
* @param Frame $frame
* @param Dompdf $dompdf
*/
function __construct(Frame $frame, Dompdf $dompdf) function __construct(Frame $frame, Dompdf $dompdf)
{ {
parent::__construct($frame, $dompdf); parent::__construct($frame, $dompdf);
} }
/**
* @param Frame|null $frame
* @param bool $force_pagebreak
* @throws Exception
*/
function split(Frame $frame = null, $force_pagebreak = false) function split(Frame $frame = null, $force_pagebreak = false)
{ {
if (is_null($frame)) { if (is_null($frame)) {
@ -38,13 +49,17 @@ class Inline extends AbstractFrameDecorator
} }
$node = $this->_frame->get_node(); $node = $this->_frame->get_node();
if ($node instanceof DOMElement && $node->hasAttribute("id")) { if ($node instanceof DOMElement && $node->hasAttribute("id")) {
$node->setAttribute("data-dompdf-original-id", $node->getAttribute("id")); $node->setAttribute("data-dompdf-original-id", $node->getAttribute("id"));
$node->removeAttribute("id"); $node->removeAttribute("id");
} }
$split = $this->copy($node->cloneNode()); $split = $this->copy($node->cloneNode());
// if this is a generated node don't propagate the content style
if ($split->get_node()->nodeName == "dompdf_generated") {
$split->get_style()->content = "normal";
}
$this->get_parent()->insert_child_after($split, $this); $this->get_parent()->insert_child_after($split, $this);
// Unset the current node's right style properties // Unset the current node's right style properties
@ -84,7 +99,6 @@ class Inline extends AbstractFrameDecorator
in_array($frame_style->page_break_before, $page_breaks) || in_array($frame_style->page_break_before, $page_breaks) ||
in_array($frame_style->page_break_after, $page_breaks) in_array($frame_style->page_break_after, $page_breaks)
) { ) {
$this->get_parent()->split($split, true); $this->get_parent()->split($split, true);
} }
} }

View File

@ -27,29 +27,35 @@ class ListBullet extends AbstractFrameDecorator
static $BULLET_TYPES = array("disc", "circle", "square"); static $BULLET_TYPES = array("disc", "circle", "square");
//........................................................................ /**
* ListBullet constructor.
* @param Frame $frame
* @param Dompdf $dompdf
*/
function __construct(Frame $frame, Dompdf $dompdf) function __construct(Frame $frame, Dompdf $dompdf)
{ {
parent::__construct($frame, $dompdf); parent::__construct($frame, $dompdf);
} }
/**
* @return float|int
*/
function get_margin_width() function get_margin_width()
{ {
$style = $this->_frame->get_style(); $style = $this->_frame->get_style();
// Small hack to prevent extra indenting of list text on list_style_position === "inside" if ($style->list_style_type === "none") {
// and on suppressed bullet
if ($style->list_style_position === "outside" ||
$style->list_style_type === "none"
) {
return 0; return 0;
} }
return $style->get_font_size() * self::BULLET_SIZE + 2 * self::BULLET_PADDING; return $style->get_font_size() * self::BULLET_SIZE + 2 * self::BULLET_PADDING;
} }
//hits only on "inset" lists items, to increase height of box /**
* hits only on "inset" lists items, to increase height of box
*
* @return float|int
*/
function get_margin_height() function get_margin_height()
{ {
$style = $this->_frame->get_style(); $style = $this->_frame->get_style();
@ -61,11 +67,17 @@ class ListBullet extends AbstractFrameDecorator
return $style->get_font_size() * self::BULLET_SIZE + 2 * self::BULLET_PADDING; return $style->get_font_size() * self::BULLET_SIZE + 2 * self::BULLET_PADDING;
} }
/**
* @return float|int
*/
function get_width() function get_width()
{ {
return $this->get_margin_height(); return $this->get_margin_width();
} }
/**
* @return float|int
*/
function get_height() function get_height()
{ {
return $this->get_margin_height(); return $this->get_margin_height();

View File

@ -59,7 +59,7 @@ class ListBulletImage extends AbstractFrameDecorator
// Resample the bullet image to be consistent with 'auto' sized images // Resample the bullet image to be consistent with 'auto' sized images
// See also Image::get_min_max_width // See also Image::get_min_max_width
// Tested php ver: value measured in px, suffix "px" not in value: rtrim unnecessary. // Tested php ver: value measured in px, suffix "px" not in value: rtrim unnecessary.
$dpi = $this->_dompdf->get_option("dpi"); $dpi = $this->_dompdf->getOptions()->getDpi();
$this->_width = ((float)rtrim($width, "px") * 72) / $dpi; $this->_width = ((float)rtrim($width, "px") * 72) / $dpi;
$this->_height = ((float)rtrim($height, "px") * 72) / $dpi; $this->_height = ((float)rtrim($height, "px") * 72) / $dpi;
@ -118,10 +118,9 @@ class ListBulletImage extends AbstractFrameDecorator
// Small hack to prevent indenting of list text // Small hack to prevent indenting of list text
// Image Might not exist, then position like on list_bullet_frame_decorator fallback to none. // Image Might not exist, then position like on list_bullet_frame_decorator fallback to none.
if ($this->_frame->get_style()->list_style_position === "outside" || if ($this->_frame->get_style()->list_style_position === "outside" || $this->_width == 0) {
$this->_width == 0
)
return 0; return 0;
}
//This aligns the "inside" image position with the text. //This aligns the "inside" image position with the text.
//The text starts to the right of the image. //The text starts to the right of the image.
//Between the image and the text there is an added margin of image width. //Between the image and the text there is an added margin of image width.

View File

@ -17,7 +17,11 @@ use Dompdf\Frame;
*/ */
class NullFrameDecorator extends AbstractFrameDecorator class NullFrameDecorator extends AbstractFrameDecorator
{ {
/**
* NullFrameDecorator constructor.
* @param Frame $frame
* @param Dompdf $dompdf
*/
function __construct(Frame $frame, Dompdf $dompdf) function __construct(Frame $frame, Dompdf $dompdf)
{ {
parent::__construct($frame, $dompdf); parent::__construct($frame, $dompdf);
@ -27,5 +31,4 @@ class NullFrameDecorator extends AbstractFrameDecorator
$style->margin = 0; $style->margin = 0;
$style->padding = 0; $style->padding = 0;
} }
} }

View File

@ -191,9 +191,7 @@ class Page extends AbstractFrameDecorator
$prev = $prev->get_prev_sibling(); $prev = $prev->get_prev_sibling();
} }
if (in_array($style->page_break_before, $page_breaks)) { if (in_array($style->page_break_before, $page_breaks)) {
// Prevent cascading splits // Prevent cascading splits
$frame->split(null, true); $frame->split(null, true);
// We have to grab the style again here because split() resets // We have to grab the style again here because split() resets
@ -224,7 +222,6 @@ class Page extends AbstractFrameDecorator
} }
} }
return false; return false;
} }
@ -279,7 +276,6 @@ class Page extends AbstractFrameDecorator
*/ */
protected function _page_break_allowed(Frame $frame) protected function _page_break_allowed(Frame $frame)
{ {
$block_types = array("block", "list-item", "table", "-dompdf-image"); $block_types = array("block", "list-item", "table", "-dompdf-image");
Helpers::dompdf_debug("page-break", "_page_break_allowed(" . $frame->get_node()->nodeName . ")"); Helpers::dompdf_debug("page-break", "_page_break_allowed(" . $frame->get_node()->nodeName . ")");
$display = $frame->get_style()->display; $display = $frame->get_style()->display;
@ -393,9 +389,7 @@ class Page extends AbstractFrameDecorator
} }
// Skip breaks on empty text nodes // Skip breaks on empty text nodes
if ($frame->is_text_node() && if ($frame->is_text_node() && $frame->get_node()->nodeValue == "") {
$frame->get_node()->nodeValue == ""
) {
return false; return false;
} }
@ -403,14 +397,14 @@ class Page extends AbstractFrameDecorator
return true; return true;
// Table-rows // Table-rows
} else { } else {
if ($display === "table-row") { if ($display === "table-row") {
// Simply check if the parent table's page_break_inside property is // Simply check if the parent table's page_break_inside property is
// not 'avoid' // not 'avoid'
$p = Table::find_parent_table($frame); $table = Table::find_parent_table($frame);
$p = $table;
while ($p) { while ($p) {
if ($p->get_style()->page_break_inside === "avoid") { if ($p->get_style()->page_break_inside === "avoid") {
Helpers::dompdf_debug("page-break", "parent->inside: avoid"); Helpers::dompdf_debug("page-break", "parent->inside: avoid");
@ -421,7 +415,7 @@ class Page extends AbstractFrameDecorator
} }
// Avoid breaking after the first row of a table // Avoid breaking after the first row of a table
if ($p && $p->get_first_child() === $frame) { if ($table && $table->get_first_child() === $frame || $table->get_first_child()->get_first_child() === $frame) {
Helpers::dompdf_debug("page-break", "table: first-row"); Helpers::dompdf_debug("page-break", "table: first-row");
return false; return false;
@ -437,7 +431,6 @@ class Page extends AbstractFrameDecorator
Helpers::dompdf_debug("page-break", "table-row/row-groups: break allowed"); Helpers::dompdf_debug("page-break", "table-row/row-groups: break allowed");
return true; return true;
} else { } else {
if (in_array($display, Table::$ROW_GROUPS)) { if (in_array($display, Table::$ROW_GROUPS)) {
@ -445,7 +438,6 @@ class Page extends AbstractFrameDecorator
return false; return false;
} else { } else {
Helpers::dompdf_debug("page-break", "? " . $frame->get_style()->display . ""); Helpers::dompdf_debug("page-break", "? " . $frame->get_style()->display . "");
return false; return false;
@ -463,56 +455,76 @@ class Page extends AbstractFrameDecorator
* *
* @param Frame $frame the frame to check * @param Frame $frame the frame to check
* *
* @return Frame the frame following the page break * @return bool
*/ */
function check_page_break(Frame $frame) function check_page_break(Frame $frame)
{ {
//FIXME: should not need to do this since we're tracking table status in `$this->_in_table`
$p = $frame;
$in_table = false;
while ($p) {
if ($p->is_table()) { $in_table = true; break; }
$p = $p->get_parent();
}
// Do not split if we have already or if the frame was already // Do not split if we have already or if the frame was already
// pushed to the next page (prevents infinite loops) // pushed to the next page (prevents infinite loops)
if ($this->_page_full || $frame->_already_pushed) { if ($in_table) {
if ($this->_page_full && $frame->_already_pushed) {
return false;
}
} elseif ($this->_page_full || $frame->_already_pushed) {
return false; return false;
} }
//FIXME: work-around for infinite loop due to tables
if ($in_table && $frame->_already_pushed) {
return false;
}
$p = $frame;
do {
$display = $p->get_style()->display;
if ($display == "table-row") {
if ($p->_already_pushed) { return false; }
}
} while ($p = $p->get_parent());
// If the frame is absolute of fixed it shouldn't break // If the frame is absolute of fixed it shouldn't break
$p = $frame; $p = $frame;
do { do {
if ($p->is_absolute()) if ($p->is_absolute()) {
return false; return false;
}
// FIXME If the row is taller than the page and
// if it the first of the page, we don't break
$display = $p->get_style()->display;
if ($display === "table-row"
&& !$p->get_prev_sibling()
&& $p->get_margin_height() > $this->get_margin_height()
) {
return false;
}
} while ($p = $p->get_parent()); } while ($p = $p->get_parent());
$margin_height = $frame->get_margin_height(); $margin_height = $frame->get_margin_height();
// FIXME If the row is taller than the page and
// if it the first of the page, we don't break
if ($frame->get_style()->display === "table-row" &&
!$frame->get_prev_sibling() &&
$margin_height > $this->get_margin_height()
)
return false;
// Determine the frame's maximum y value // Determine the frame's maximum y value
$max_y = $frame->get_position("y") + $margin_height; $max_y = (float)$frame->get_position("y") + $margin_height;
// If a split is to occur here, then the bottom margins & paddings of all // If a split is to occur here, then the bottom margins & paddings of all
// parents of $frame must fit on the page as well: // parents of $frame must fit on the page as well:
$p = $frame->get_parent(); $p = $frame->get_parent();
while ($p) { while ($p) {
$style = $p->get_style(); $max_y += $p->get_style()->computed_bottom_spacing();
$max_y += $style->length_in_pt(
array(
$style->margin_bottom,
$style->padding_bottom,
$style->border_bottom_width
)
);
$p = $p->get_parent(); $p = $p->get_parent();
} }
// Check if $frame flows off the page // Check if $frame flows off the page
if ($max_y <= $this->_bottom_page_margin) if ($max_y <= $this->_bottom_page_margin) {
// no: do nothing // no: do nothing
return false; return false;
}
Helpers::dompdf_debug("page-break", "check_page_break"); Helpers::dompdf_debug("page-break", "check_page_break");
Helpers::dompdf_debug("page-break", "in_table: " . $this->_in_table); Helpers::dompdf_debug("page-break", "in_table: " . $this->_in_table);
@ -545,8 +557,9 @@ class Page extends AbstractFrameDecorator
if (!$flg && $next = $iter->get_last_child()) { if (!$flg && $next = $iter->get_last_child()) {
Helpers::dompdf_debug("page-break", "following last child."); Helpers::dompdf_debug("page-break", "following last child.");
if ($next->is_table()) if ($next->is_table()) {
$this->_in_table++; $this->_in_table++;
}
$iter = $next; $iter = $next;
continue; continue;
@ -555,11 +568,11 @@ class Page extends AbstractFrameDecorator
if ($next = $iter->get_prev_sibling()) { if ($next = $iter->get_prev_sibling()) {
Helpers::dompdf_debug("page-break", "following prev sibling."); Helpers::dompdf_debug("page-break", "following prev sibling.");
if ($next->is_table() && !$iter->is_table()) if ($next->is_table() && !$iter->is_table()) {
$this->_in_table++; $this->_in_table++;
} else if (!$next->is_table() && $iter->is_table()) {
else if (!$next->is_table() && $iter->is_table())
$this->_in_table--; $this->_in_table--;
}
$iter = $next; $iter = $next;
$flg = false; $flg = false;
@ -569,8 +582,9 @@ class Page extends AbstractFrameDecorator
if ($next = $iter->get_parent()) { if ($next = $iter->get_parent()) {
Helpers::dompdf_debug("page-break", "following parent."); Helpers::dompdf_debug("page-break", "following parent.");
if ($iter->is_table()) if ($iter->is_table()) {
$this->_in_table--; $this->_in_table--;
}
$iter = $next; $iter = $next;
$flg = true; $flg = true;
@ -588,10 +602,15 @@ class Page extends AbstractFrameDecorator
// If we are in a table, backtrack to the nearest top-level table row // If we are in a table, backtrack to the nearest top-level table row
if ($this->_in_table) { if ($this->_in_table) {
$iter = $frame; $iter = $frame;
while ($iter && $iter->get_style()->display !== "table-row" && $iter->get_style()->display !== 'table-row-group') while ($iter && $iter->get_style()->display !== "table-row" && $iter->get_style()->display !== 'table-row-group' && $iter->_already_pushed === false) {
$iter = $iter->get_parent(); $iter = $iter->get_parent();
}
$iter->split(null, true); if ($iter) {
$iter->split(null, true);
} else {
return false;
}
} else { } else {
$frame->split(null, true); $frame->split(null, true);
} }
@ -604,6 +623,10 @@ class Page extends AbstractFrameDecorator
//........................................................................ //........................................................................
/**
* @param Frame|null $frame
* @param bool $force_pagebreak
*/
function split(Frame $frame = null, $force_pagebreak = false) function split(Frame $frame = null, $force_pagebreak = false)
{ {
// Do nothing // Do nothing
@ -629,11 +652,18 @@ class Page extends AbstractFrameDecorator
return $this->_floating_frames; return $this->_floating_frames;
} }
/**
* @param $key
*/
public function remove_floating_frame($key) public function remove_floating_frame($key)
{ {
unset($this->_floating_frames[$key]); unset($this->_floating_frames[$key]);
} }
/**
* @param Frame $child
* @return int|mixed
*/
public function get_lowest_float_offset(Frame $child) public function get_lowest_float_offset(Frame $child)
{ {
$style = $child->get_style(); $style = $child->get_style();
@ -642,17 +672,19 @@ class Page extends AbstractFrameDecorator
$y = 0; $y = 0;
foreach ($this->_floating_frames as $key => $frame) { if ($float === "none") {
if ($side === "both" || $frame->get_style()->float === $side) { foreach ($this->_floating_frames as $key => $frame) {
$y = max($y, $frame->get_position("y") + $frame->get_margin_height()); if ($side === "both" || $frame->get_style()->float === $side) {
$y = max($y, $frame->get_position("y") + $frame->get_margin_height());
if ($float !== "none") {
$this->remove_floating_frame($key);
} }
$this->remove_floating_frame($key);
} }
} }
if ($y > 0) {
$y++; // add 1px buffer from float
}
return $y; return $y;
} }
} }

View File

@ -96,7 +96,6 @@ class Table extends AbstractFrameDecorator
$this->_footers = array(); $this->_footers = array();
} }
public function reset() public function reset()
{ {
parent::reset(); parent::reset();
@ -133,7 +132,6 @@ class Table extends AbstractFrameDecorator
if (count($this->_headers) && !in_array($child, $this->_headers, true) && if (count($this->_headers) && !in_array($child, $this->_headers, true) &&
!in_array($child->get_prev_sibling(), $this->_headers, true) !in_array($child->get_prev_sibling(), $this->_headers, true)
) { ) {
$first_header = null; $first_header = null;
// Insert copies of the table headers before $child // Insert copies of the table headers before $child
@ -195,10 +193,11 @@ class Table extends AbstractFrameDecorator
*/ */
public static function find_parent_table(Frame $frame) public static function find_parent_table(Frame $frame)
{ {
while ($frame = $frame->get_parent()) {
while ($frame = $frame->get_parent()) if ($frame->is_table()) {
if ($frame->is_table())
break; break;
}
}
return $frame; return $frame;
} }
@ -257,6 +256,9 @@ class Table extends AbstractFrameDecorator
* Restructure tree so that the table has the correct structure. * Restructure tree so that the table has the correct structure.
* Invalid children (i.e. all non-table-rows) are moved below the * Invalid children (i.e. all non-table-rows) are moved below the
* table. * table.
*
* @fixme #1363 Method has some bugs. $table_row has not been initialized and lookup most likely could return an
* array of Style instead a Style Object
*/ */
public function normalise() public function normalise()
{ {
@ -278,6 +280,7 @@ class Table extends AbstractFrameDecorator
$table_row->normalise(); $table_row->normalise();
$child->normalise(); $child->normalise();
$this->_cellmap->add_row();
$anon_row = false; $anon_row = false;
continue; continue;
} }
@ -294,12 +297,34 @@ class Table extends AbstractFrameDecorator
} }
if ($display === "table-cell") { if ($display === "table-cell") {
$css = $this->get_style()->get_stylesheet();
// Create an anonymous table row group
$tbody = $this->get_node()->ownerDocument->createElement("tbody");
$frame = new Frame($tbody);
$style = $css->create_style();
$style->inherit($this->get_style());
// Lookup styles for tbody tags. If the user wants styles to work
// better, they should make the tbody explicit... I'm not going to
// try to guess what they intended.
if ($tbody_style = $css->lookup("tbody")) {
$style->merge($tbody_style);
}
$style->display = 'table-row-group';
// Okay, I have absolutely no idea why I need this clone here, but
// if it's omitted, php (as of 2004-07-28) segfaults.
$frame->set_style($style);
$table_row_group = Factory::decorate_frame($frame, $this->_dompdf, $this->_root);
// Create an anonymous table row // Create an anonymous table row
$tr = $this->get_node()->ownerDocument->createElement("tr"); $tr = $this->get_node()->ownerDocument->createElement("tr");
$frame = new Frame($tr); $frame = new Frame($tr);
$css = $this->get_style()->get_stylesheet();
$style = $css->create_style(); $style = $css->create_style();
$style->inherit($this->get_style()); $style->inherit($this->get_style());
@ -309,6 +334,7 @@ class Table extends AbstractFrameDecorator
if ($tr_style = $css->lookup("tr")) { if ($tr_style = $css->lookup("tr")) {
$style->merge($tr_style); $style->merge($tr_style);
} }
$style->display = 'table-row';
// Okay, I have absolutely no idea why I need this clone here, but // Okay, I have absolutely no idea why I need this clone here, but
// if it's omitted, php (as of 2004-07-28) segfaults. // if it's omitted, php (as of 2004-07-28) segfaults.
@ -316,7 +342,10 @@ class Table extends AbstractFrameDecorator
$table_row = Factory::decorate_frame($frame, $this->_dompdf, $this->_root); $table_row = Factory::decorate_frame($frame, $this->_dompdf, $this->_root);
// Add the cell to the row // Add the cell to the row
$table_row->append_child($child); $table_row->append_child($child, true);
// Add the tr to the tbody
$table_row_group->append_child($table_row, true);
$anon_row = true; $anon_row = true;
continue; continue;
@ -343,11 +372,10 @@ class Table extends AbstractFrameDecorator
} }
} }
if ($anon_row && $table_row instanceof DOMNode) { if ($anon_row && $table_row_group instanceof AbstractFrameDecorator) {
// Add the row to the table // Add the row to the table
$this->_frame->append_child($table_row); $this->_frame->append_child($table_row_group->_frame);
$table_row->normalise(); $table_row->normalise();
$this->_cellmap->add_row();
} }
foreach ($erroneous_frames as $frame) { foreach ($erroneous_frames as $frame) {

View File

@ -24,6 +24,11 @@ class TableCell extends BlockFrameDecorator
//........................................................................ //........................................................................
/**
* TableCell constructor.
* @param Frame $frame
* @param Dompdf $dompdf
*/
function __construct(Frame $frame, Dompdf $dompdf) function __construct(Frame $frame, Dompdf $dompdf)
{ {
parent::__construct($frame, $dompdf); parent::__construct($frame, $dompdf);
@ -41,26 +46,39 @@ class TableCell extends BlockFrameDecorator
$this->_frame->reset(); $this->_frame->reset();
} }
/**
* @return int
*/
function get_content_height() function get_content_height()
{ {
return $this->_content_height; return $this->_content_height;
} }
/**
* @param $height
*/
function set_content_height($height) function set_content_height($height)
{ {
$this->_content_height = $height; $this->_content_height = $height;
} }
/**
* @param $height
*/
function set_cell_height($height) function set_cell_height($height)
{ {
$style = $this->get_style(); $style = $this->get_style();
$v_space = $style->length_in_pt(array($style->margin_top, $v_space = (float)$style->length_in_pt(
array(
$style->margin_top,
$style->padding_top, $style->padding_top,
$style->border_top_width, $style->border_top_width,
$style->border_bottom_width, $style->border_bottom_width,
$style->padding_bottom, $style->padding_bottom,
$style->margin_bottom), $style->margin_bottom
$style->width); ),
(float)$style->length_in_pt($style->height)
);
$new_height = $height - $v_space; $new_height = $height - $v_space;
$style->height = $new_height; $style->height = $new_height;
@ -90,26 +108,35 @@ class TableCell extends BlockFrameDecorator
if ($y_offset) { if ($y_offset) {
// Move our children // Move our children
foreach ($this->get_line_boxes() as $line) { foreach ($this->get_line_boxes() as $line) {
foreach ($line->get_frames() as $frame) foreach ($line->get_frames() as $frame) {
$frame->move(0, $y_offset); $frame->move(0, $y_offset);
}
} }
} }
} }
} }
/**
* @param $side
* @param $border_spec
*/
function set_resolved_border($side, $border_spec) function set_resolved_border($side, $border_spec)
{ {
$this->_resolved_borders[$side] = $border_spec; $this->_resolved_borders[$side] = $border_spec;
} }
//........................................................................ /**
* @param $side
* @return mixed
*/
function get_resolved_border($side) function get_resolved_border($side)
{ {
return $this->_resolved_borders[$side]; return $this->_resolved_borders[$side];
} }
/**
* @return array
*/
function get_resolved_borders() function get_resolved_borders()
{ {
return $this->_resolved_borders; return $this->_resolved_borders;

View File

@ -18,6 +18,11 @@ use Dompdf\FrameDecorator\Table as TableFrameDecorator;
*/ */
class TableRow extends AbstractFrameDecorator class TableRow extends AbstractFrameDecorator
{ {
/**
* TableRow constructor.
* @param Frame $frame
* @param Dompdf $dompdf
*/
function __construct(Frame $frame, Dompdf $dompdf) function __construct(Frame $frame, Dompdf $dompdf)
{ {
parent::__construct($frame, $dompdf); parent::__construct($frame, $dompdf);
@ -31,7 +36,6 @@ class TableRow extends AbstractFrameDecorator
*/ */
function normalise() function normalise()
{ {
// Find our table parent // Find our table parent
$p = TableFrameDecorator::find_parent_table($this); $p = TableFrameDecorator::find_parent_table($this);
@ -39,14 +43,26 @@ class TableRow extends AbstractFrameDecorator
foreach ($this->get_children() as $child) { foreach ($this->get_children() as $child) {
$display = $child->get_style()->display; $display = $child->get_style()->display;
if ($display !== "table-cell") if ($display !== "table-cell") {
$erroneous_frames[] = $child; $erroneous_frames[] = $child;
}
} }
// dump the extra nodes after the table. // dump the extra nodes after the table.
foreach ($erroneous_frames as $frame) foreach ($erroneous_frames as $frame) {
$p->move_after($frame); $p->move_after($frame);
}
} }
function split(Frame $child = null, $force_pagebreak = false)
{
$this->_already_pushed = true;
if (is_null($child)) {
parent::split();
return;
}
parent::split($child, $force_pagebreak);
}
} }

View File

@ -41,7 +41,6 @@ class TableRowGroup extends AbstractFrameDecorator
*/ */
function split(Frame $child = null, $force_pagebreak = false) function split(Frame $child = null, $force_pagebreak = false)
{ {
if (is_null($child)) { if (is_null($child)) {
parent::split(); parent::split();
return; return;
@ -66,7 +65,6 @@ class TableRowGroup extends AbstractFrameDecorator
$cellmap->update_row_group($this, $child->get_prev_sibling()); $cellmap->update_row_group($this, $child->get_prev_sibling());
parent::split($child); parent::split($child);
} }
} }

View File

@ -12,8 +12,6 @@ namespace Dompdf\FrameDecorator;
use Dompdf\Dompdf; use Dompdf\Dompdf;
use Dompdf\Frame; use Dompdf\Frame;
use Dompdf\Exception; use Dompdf\Exception;
use DOMText;
use Dompdf\FontMetrics;
/** /**
* Decorates Frame objects for text layout * Decorates Frame objects for text layout
@ -27,6 +25,12 @@ class Text extends AbstractFrameDecorator
// protected members // protected members
protected $_text_spacing; protected $_text_spacing;
/**
* Text constructor.
* @param Frame $frame
* @param Dompdf $dompdf
* @throws Exception
*/
function __construct(Frame $frame, Dompdf $dompdf) function __construct(Frame $frame, Dompdf $dompdf)
{ {
if (!$frame->is_text_node()) { if (!$frame->is_text_node()) {
@ -37,22 +41,25 @@ class Text extends AbstractFrameDecorator
$this->_text_spacing = null; $this->_text_spacing = null;
} }
//........................................................................
function reset() function reset()
{ {
parent::reset(); parent::reset();
$this->_text_spacing = null; $this->_text_spacing = null;
} }
//........................................................................
// Accessor methods // Accessor methods
/**
* @return null
*/
function get_text_spacing() function get_text_spacing()
{ {
return $this->_text_spacing; return $this->_text_spacing;
} }
/**
* @return string
*/
function get_text() function get_text()
{ {
// FIXME: this should be in a child class (and is incorrect) // FIXME: this should be in a child class (and is incorrect)
@ -74,14 +81,18 @@ class Text extends AbstractFrameDecorator
//........................................................................ //........................................................................
// Vertical margins & padding do not apply to text frames /**
* Vertical margins & padding do not apply to text frames
// http://www.w3.org/TR/CSS21/visudet.html#inline-non-replaced: *
// * http://www.w3.org/TR/CSS21/visudet.html#inline-non-replaced:
// The vertical padding, border and margin of an inline, non-replaced box *
// start at the top and bottom of the content area, not the * The vertical padding, border and margin of an inline, non-replaced box
// 'line-height'. But only the 'line-height' is used to calculate the * start at the top and bottom of the content area, not the
// height of the line box. * 'line-height'. But only the 'line-height' is used to calculate the
* height of the line box.
*
* @return float|int
*/
function get_margin_height() function get_margin_height()
{ {
// This function is called in add_frame_to_line() and is used to // This function is called in add_frame_to_line() and is used to
@ -100,9 +111,11 @@ class Text extends AbstractFrameDecorator
*/ */
return ($style->line_height / ($size > 0 ? $size : 1)) * $this->_dompdf->getFontMetrics()->getFontHeight($font, $size); return ($style->line_height / ($size > 0 ? $size : 1)) * $this->_dompdf->getFontMetrics()->getFontHeight($font, $size);
} }
/**
* @return array
*/
function get_padding_box() function get_padding_box()
{ {
$pb = $this->_frame->get_padding_box(); $pb = $this->_frame->get_padding_box();
@ -110,41 +123,47 @@ class Text extends AbstractFrameDecorator
return $pb; return $pb;
} }
//........................................................................
// Set method /**
* @param $spacing
*/
function set_text_spacing($spacing) function set_text_spacing($spacing)
{ {
$style = $this->_frame->get_style(); $style = $this->_frame->get_style();
$this->_text_spacing = $spacing; $this->_text_spacing = $spacing;
$char_spacing = $style->length_in_pt($style->letter_spacing); $char_spacing = (float)$style->length_in_pt($style->letter_spacing);
// Re-adjust our width to account for the change in spacing // Re-adjust our width to account for the change in spacing
$style->width = $this->_dompdf->getFontMetrics()->getTextWidth($this->get_text(), $style->font_family, $style->font_size, $spacing, $char_spacing); $style->width = $this->_dompdf->getFontMetrics()->getTextWidth($this->get_text(), $style->font_family, $style->font_size, $spacing, $char_spacing);
} }
//........................................................................ /**
* Recalculate the text width
// Recalculate the text width *
* @return float
*/
function recalculate_width() function recalculate_width()
{ {
$style = $this->get_style(); $style = $this->get_style();
$text = $this->get_text(); $text = $this->get_text();
$size = $style->font_size; $size = $style->font_size;
$font = $style->font_family; $font = $style->font_family;
$word_spacing = $style->length_in_pt($style->word_spacing); $word_spacing = (float)$style->length_in_pt($style->word_spacing);
$char_spacing = $style->length_in_pt($style->letter_spacing); $char_spacing = (float)$style->length_in_pt($style->letter_spacing);
return $style->width = $this->_dompdf->getFontMetrics()->getTextWidth($text, $font, $size, $word_spacing, $char_spacing); return $style->width = $this->_dompdf->getFontMetrics()->getTextWidth($text, $font, $size, $word_spacing, $char_spacing);
} }
//........................................................................
// Text manipulation methods // Text manipulation methods
// split the text in this frame at the offset specified. The remaining /**
// text is added a sibling frame following this one and is returned. * split the text in this frame at the offset specified. The remaining
* text is added a sibling frame following this one and is returned.
*
* @param $offset
* @return Frame|null
*/
function split_text($offset) function split_text($offset)
{ {
if ($offset == 0) { if ($offset == 0) {
@ -165,18 +184,20 @@ class Text extends AbstractFrameDecorator
return $deco; return $deco;
} }
//........................................................................ /**
* @param $offset
* @param $count
*/
function delete_text($offset, $count) function delete_text($offset, $count)
{ {
$this->_frame->get_node()->deleteData($offset, $count); $this->_frame->get_node()->deleteData($offset, $count);
} }
//........................................................................ /**
* @param $text
*/
function set_text($text) function set_text($text)
{ {
$this->_frame->get_node()->data = $text; $this->_frame->get_node()->data = $text;
} }
} }

View File

@ -40,6 +40,10 @@ abstract class AbstractFrameReflower
*/ */
protected $_min_max_cache; protected $_min_max_cache;
/**
* AbstractFrameReflower constructor.
* @param Frame $frame
*/
function __construct(Frame $frame) function __construct(Frame $frame)
{ {
$this->_frame = $frame; $this->_frame = $frame;
@ -68,7 +72,8 @@ abstract class AbstractFrameReflower
$cb = $frame->get_containing_block(); $cb = $frame->get_containing_block();
$style = $frame->get_style(); $style = $frame->get_style();
if (!$frame->is_in_flow()) { // Margins of float/absolutely positioned/inline-block elements do not collapse.
if (!$frame->is_in_flow() || $frame->is_inline_block()) {
return; return;
} }
@ -88,9 +93,9 @@ abstract class AbstractFrameReflower
// Collapse vertical margins: // Collapse vertical margins:
$n = $frame->get_next_sibling(); $n = $frame->get_next_sibling();
if ($n && !$n->is_block()) { if ( $n && !$n->is_block() & !$n->is_table() ) {
while ($n = $n->get_next_sibling()) { while ($n = $n->get_next_sibling()) {
if ($n->is_block()) { if ($n->is_block() || $n->is_table()) {
break; break;
} }
@ -103,45 +108,104 @@ abstract class AbstractFrameReflower
if ($n) { if ($n) {
$n_style = $n->get_style(); $n_style = $n->get_style();
$b = max($b, $n_style->length_in_pt($n_style->margin_top, $cb["h"])); $n_t = (float)$n_style->length_in_pt($n_style->margin_top, $cb["h"]);
$n_style->margin_top = "0pt";
$b = $this->_get_collapsed_margin_length($b, $n_t);
$style->margin_bottom = $b . "pt"; $style->margin_bottom = $b . "pt";
$n_style->margin_top = "0pt";
} }
// Collapse our first child's margin // Collapse our first child's margin, if there is no border or padding
/*$f = $this->_frame->get_first_child(); if ($style->get_border_top_width() == 0 && $style->length_in_pt($style->padding_top) == 0) {
if ( $f && !$f->is_block() ) { $f = $this->_frame->get_first_child();
while ( $f = $f->get_next_sibling() ) { if ( $f && !$f->is_block() && !$f->is_table() ) {
if ( $f->is_block() ) { while ( $f = $f->get_next_sibling() ) {
break; if ( $f->is_block() || $f->is_table() ) {
break;
}
if ( !$f->get_first_child() ) {
$f = null;
break;
}
}
} }
if ( !$f->get_first_child() ) { // Margin are collapsed only between block-level boxes
$f = null; if ($f) {
break; $f_style = $f->get_style();
$f_t = (float)$f_style->length_in_pt($f_style->margin_top, $cb["h"]);
$t = $this->_get_collapsed_margin_length($t, $f_t);
$style->margin_top = $t."pt";
$f_style->margin_top = "0pt";
} }
}
} }
// Margin are collapsed only between block elements // Collapse our last child's margin, if there is no border or padding
if ( $f ) { if ($style->get_border_bottom_width() == 0 && $style->length_in_pt($style->padding_bottom) == 0) {
$f_style = $f->get_style(); $l = $this->_frame->get_last_child();
$t = max($t, $f_style->length_in_pt($f_style->margin_top, $cb["h"])); if ( $l && !$l->is_block() && !$l->is_table() ) {
$style->margin_top = $t."pt"; while ( $l = $l->get_prev_sibling() ) {
$f_style->margin_bottom = "0pt"; if ( $l->is_block() || $l->is_table() ) {
}*/ break;
}
if ( !$l->get_last_child() ) {
$l = null;
break;
}
}
}
// Margin are collapsed only between block-level boxes
if ($l) {
$l_style = $l->get_style();
$l_b = (float)$l_style->length_in_pt($l_style->margin_bottom, $cb["h"]);
$b = $this->_get_collapsed_margin_length($b, $l_b);
$style->margin_bottom = $b."pt";
$l_style->margin_bottom = "0pt";
}
}
} }
//........................................................................ /**
* Get the combined (collapsed) length of two adjoining margins.
*
* See http://www.w3.org/TR/CSS2/box.html#collapsing-margins.
*
* @param number $length1
* @param number $length2
* @return number
*/
private function _get_collapsed_margin_length($length1, $length2)
{
if ($length1 < 0 && $length2 < 0) {
return min($length1, $length2); // min(x, y) = - max(abs(x), abs(y)), if x < 0 && y < 0
}
if ($length1 < 0 || $length2 < 0) {
return $length1 + $length2; // x + y = x - abs(y), if y < 0
}
return max($length1, $length2);
}
/**
* @param Block|null $block
* @return mixed
*/
abstract function reflow(Block $block = null); abstract function reflow(Block $block = null);
//........................................................................ /**
* Required for table layout: Returns an array(0 => min, 1 => max, "min"
// Required for table layout: Returns an array(0 => min, 1 => max, "min" * => min, "max" => max) of the minimum and maximum widths of this frame.
// => min, "max" => max) of the minimum and maximum widths of this frame. * This provides a basic implementation. Child classes should override
// This provides a basic implementation. Child classes should override * this if necessary.
// this if necessary. *
* @return array|null
*/
function get_min_max_width() function get_min_max_width()
{ {
if (!is_null($this->_min_max_cache)) { if (!is_null($this->_min_max_cache)) {
@ -159,7 +223,7 @@ abstract class AbstractFrameReflower
$style->margin_right); $style->margin_right);
$cb_w = $this->_frame->get_containing_block("w"); $cb_w = $this->_frame->get_containing_block("w");
$delta = $style->length_in_pt($dims, $cb_w); $delta = (float)$style->length_in_pt($dims, $cb_w);
// Handle degenerate case // Handle degenerate case
if (!$this->_frame->get_first_child()) { if (!$this->_frame->get_first_child()) {
@ -173,16 +237,12 @@ abstract class AbstractFrameReflower
$low = array(); $low = array();
$high = array(); $high = array();
for ($iter = $this->_frame->get_children()->getIterator(); for ($iter = $this->_frame->get_children()->getIterator(); $iter->valid(); $iter->next()) {
$iter->valid();
$iter->next()) {
$inline_min = 0; $inline_min = 0;
$inline_max = 0; $inline_max = 0;
// Add all adjacent inline widths together to calculate max width // Add all adjacent inline widths together to calculate max width
while ($iter->valid() && in_array($iter->current()->get_style()->display, Style::$INLINE_TYPES)) { while ($iter->valid() && in_array($iter->current()->get_style()->display, Style::$INLINE_TYPES)) {
$child = $iter->current(); $child = $iter->current();
$minmax = $child->get_min_max_width(); $minmax = $child->get_min_max_width();
@ -195,17 +255,19 @@ abstract class AbstractFrameReflower
$inline_max += $minmax["max"]; $inline_max += $minmax["max"];
$iter->next(); $iter->next();
} }
if ($inline_max > 0) $high[] = $inline_max; if ($inline_max > 0) {
if ($inline_min > 0) $low[] = $inline_min; $high[] = $inline_max;
}
if ($inline_min > 0) {
$low[] = $inline_min;
}
if ($iter->valid()) { if ($iter->valid()) {
list($low[], $high[]) = $iter->current()->get_min_max_width(); list($low[], $high[]) = $iter->current()->get_min_max_width();
continue; continue;
} }
} }
$min = count($low) ? max($low) : 0; $min = count($low) ? max($low) : 0;
$max = count($high) ? max($high) : 0; $max = count($high) ? max($high) : 0;
@ -214,9 +276,13 @@ abstract class AbstractFrameReflower
// content. If the width is a percentage ignore it for now. // content. If the width is a percentage ignore it for now.
$width = $style->width; $width = $style->width;
if ($width !== "auto" && !Helpers::is_percent($width)) { if ($width !== "auto" && !Helpers::is_percent($width)) {
$width = $style->length_in_pt($width, $cb_w); $width = (float)$style->length_in_pt($width, $cb_w);
if ($min < $width) $min = $width; if ($min < $width) {
if ($max < $width) $max = $width; $min = $width;
}
if ($max < $width) {
$max = $width;
}
} }
$min += $delta; $min += $delta;
@ -257,7 +323,6 @@ abstract class AbstractFrameReflower
*/ */
protected function _parse_quotes() protected function _parse_quotes()
{ {
// Matches quote types // Matches quote types
$re = '/(\'[^\']*\')|(\"[^\"]*\")/'; $re = '/(\'[^\']*\')|(\"[^\"]*\")/';
@ -287,7 +352,6 @@ abstract class AbstractFrameReflower
*/ */
protected function _parse_content() protected function _parse_content()
{ {
// Matches generated content // Matches generated content
$re = "/\n" . $re = "/\n" .
"\s(counters?\\([^)]*\\))|\n" . "\s(counters?\\([^)]*\\))|\n" .
@ -310,7 +374,6 @@ abstract class AbstractFrameReflower
$text = ""; $text = "";
foreach ($matches as $match) { foreach ($matches as $match) {
if (isset($match[2]) && $match[2] !== "") { if (isset($match[2]) && $match[2] !== "") {
$match[1] = $match[2]; $match[1] = $match[2];
} }
@ -324,7 +387,6 @@ abstract class AbstractFrameReflower
} }
if (isset($match[1]) && $match[1] !== "") { if (isset($match[1]) && $match[1] !== "") {
// counters?(...) // counters?(...)
$match[1] = mb_strtolower(trim($match[1])); $match[1] = mb_strtolower(trim($match[1]));
@ -371,10 +433,8 @@ abstract class AbstractFrameReflower
array_unshift($tmp, $p->counter_value($counter_id, $type)); array_unshift($tmp, $p->counter_value($counter_id, $type));
} }
$p = $p->lookup_counter_frame($counter_id); $p = $p->lookup_counter_frame($counter_id);
} }
$text .= implode($string, $tmp); $text .= implode($string, $tmp);
} else { } else {
// countertops? // countertops?
continue; continue;
@ -397,7 +457,6 @@ abstract class AbstractFrameReflower
} else if ($match[7] === "no-close-quote") { } else if ($match[7] === "no-close-quote") {
// FIXME: // FIXME:
} else if (mb_strpos($match[7], "attr(") === 0) { } else if (mb_strpos($match[7], "attr(") === 0) {
$i = mb_strpos($match[7], ")"); $i = mb_strpos($match[7], ")");
if ($i === false) { if ($i === false) {
continue; continue;
@ -436,12 +495,12 @@ abstract class AbstractFrameReflower
$frame->increment_counters($increment); $frame->increment_counters($increment);
} }
if ($style->content && !$frame->get_first_child() && $frame->get_node()->nodeName === "dompdf_generated") { if ($style->content && $frame->get_node()->nodeName === "dompdf_generated") {
$content = $this->_parse_content(); $content = $this->_parse_content();
// add generated content to the font subset // add generated content to the font subset
// FIXME: This is currently too late because the font subset has already been generated. // FIXME: This is currently too late because the font subset has already been generated.
// See notes in issue #750. // See notes in issue #750.
if ($frame->get_dompdf()->get_option("enable_font_subsetting") && $frame->get_dompdf()->get_canvas() instanceof CPDF) { if ($frame->get_dompdf()->getOptions()->getIsFontSubsettingEnabled() && $frame->get_dompdf()->get_canvas() instanceof CPDF) {
$frame->get_dompdf()->get_canvas()->register_string_subset($style->font_family, $content); $frame->get_dompdf()->get_canvas()->register_string_subset($style->font_family, $content);
} }
@ -457,4 +516,14 @@ abstract class AbstractFrameReflower
$frame->append_child($new_frame); $frame->append_child($new_frame);
} }
} }
/**
* Determine current frame width based on contents
*
* @return float
*/
public function calculate_auto_width()
{
return $this->_frame->get_margin_width();
}
} }

View File

@ -8,11 +8,12 @@
*/ */
namespace Dompdf\FrameReflower; namespace Dompdf\FrameReflower;
use Dompdf\FontMetrics;
use Dompdf\Frame; use Dompdf\Frame;
use Dompdf\FrameDecorator\Block as BlockFrameDecorator; use Dompdf\FrameDecorator\Block as BlockFrameDecorator;
use Dompdf\FrameDecorator\TableCell as TableCellFrameDecorator;
use Dompdf\FrameDecorator\Text as TextFrameDecorator; use Dompdf\FrameDecorator\Text as TextFrameDecorator;
use Dompdf\Exception; use Dompdf\Exception;
use Dompdf\Css\Style;
/** /**
* Reflows block frames * Reflows block frames
@ -76,22 +77,23 @@ class Block extends AbstractFrameReflower
$absolute = false; $absolute = false;
} }
$sum = $style->length_in_pt($dims, $w); $sum = (float)$style->length_in_pt($dims, $w);
// Compare to the containing block // Compare to the containing block
$diff = $w - $sum; $diff = $w - $sum;
if ($diff > 0) { if ($diff > 0) {
if ($absolute) { if ($absolute) {
// resolve auto properties: see // resolve auto properties: see
// http://www.w3.org/TR/CSS21/visudet.html#abs-non-replaced-width // http://www.w3.org/TR/CSS21/visudet.html#abs-non-replaced-width
if ($width === "auto" && $left === "auto" && $right === "auto") { if ($width === "auto" && $left === "auto" && $right === "auto") {
if ($lm === "auto") {
if ($lm === "auto") $lm = 0; $lm = 0;
if ($rm === "auto") $rm = 0; }
if ($rm === "auto") {
$rm = 0;
}
// Technically, the width should be "shrink-to-fit" i.e. based on the // Technically, the width should be "shrink-to-fit" i.e. based on the
// preferred width of the content... a little too costly here as a // preferred width of the content... a little too costly here as a
@ -100,30 +102,44 @@ class Block extends AbstractFrameReflower
$right = 0; $right = 0;
$width = $diff; $width = $diff;
} else if ($width === "auto") { } else if ($width === "auto") {
if ($lm === "auto") {
if ($lm === "auto") $lm = 0; $lm = 0;
if ($rm === "auto") $rm = 0; }
if ($left === "auto") $left = 0; if ($rm === "auto") {
if ($right === "auto") $right = 0; $rm = 0;
}
if ($left === "auto") {
$left = 0;
}
if ($right === "auto") {
$right = 0;
}
$width = $diff; $width = $diff;
} else if ($left === "auto") { } else if ($left === "auto") {
if ($lm === "auto") {
if ($lm === "auto") $lm = 0; $lm = 0;
if ($rm === "auto") $rm = 0; }
if ($right === "auto") $right = 0; if ($rm === "auto") {
$rm = 0;
}
if ($right === "auto") {
$right = 0;
}
$left = $diff; $left = $diff;
} else if ($right === "auto") { } else if ($right === "auto") {
if ($lm === "auto") {
if ($lm === "auto") $lm = 0; $lm = 0;
if ($rm === "auto") $rm = 0; }
if ($rm === "auto") {
$rm = 0;
}
$right = $diff; $right = $diff;
} }
} else { } else {
// Find auto properties and get them to take up the slack // Find auto properties and get them to take up the slack
if ($width === "auto") { if ($width === "auto") {
$width = $diff; $width = $diff;
@ -135,12 +151,9 @@ class Block extends AbstractFrameReflower
$rm = $diff; $rm = $diff;
} }
} }
} else if ($diff < 0) { } else if ($diff < 0) {
// We are over constrained--set margin-right to the difference // We are over constrained--set margin-right to the difference
$rm = $diff; $rm = $diff;
} }
return array( return array(
@ -182,7 +195,12 @@ class Block extends AbstractFrameReflower
$width = $style->length_in_pt($style->width, $cb["w"]); $width = $style->length_in_pt($style->width, $cb["w"]);
} }
extract($this->_calculate_width($width)); $calculate_width = $this->_calculate_width($width);
$margin_left = $calculate_width['margin_left'];
$margin_right = $calculate_width['margin_right'];
$width = $calculate_width['width'];
$left = $calculate_width['left'];
$right = $calculate_width['right'];
// Handle min/max width // Handle min/max width
$min_width = $style->length_in_pt($style->min_width, $cb["w"]); $min_width = $style->length_in_pt($style->min_width, $cb["w"]);
@ -197,7 +215,12 @@ class Block extends AbstractFrameReflower
} }
if ($width < $min_width) { if ($width < $min_width) {
extract($this->_calculate_width($min_width)); $calculate_width = $this->_calculate_width($min_width);
$margin_left = $calculate_width['margin_left'];
$margin_right = $calculate_width['margin_right'];
$width = $calculate_width['width'];
$left = $calculate_width['left'];
$right = $calculate_width['right'];
} }
return array($width, $margin_left, $margin_right, $left, $right); return array($width, $margin_left, $margin_right, $left, $right);
@ -212,19 +235,13 @@ class Block extends AbstractFrameReflower
*/ */
protected function _calculate_content_height() protected function _calculate_content_height()
{ {
$lines = $this->_frame->get_line_boxes();
$height = 0; $height = 0;
$lines = $this->_frame->get_line_boxes();
foreach ($lines as $line) { if (count($lines) > 0) {
$height += $line->h; $last_line = end($lines);
$content_box = $this->_frame->get_content_box();
$height = $last_line->y + $last_line->h - $content_box["y"];
} }
/*
$first_line = reset($lines);
$last_line = end($lines);
$height2 = $last_line->y + $last_line->h - $first_line->y;
*/
return $height; return $height;
} }
@ -262,74 +279,101 @@ class Block extends AbstractFrameReflower
$style->margin_bottom !== "auto" ? $style->margin_bottom : 0, $style->margin_bottom !== "auto" ? $style->margin_bottom : 0,
$bottom !== "auto" ? $bottom : 0); $bottom !== "auto" ? $bottom : 0);
$sum = $style->length_in_pt($dims, $cb["h"]); $sum = (float)$style->length_in_pt($dims, $cb["h"]);
$diff = $cb["h"] - $sum; $diff = $cb["h"] - $sum;
if ($diff > 0) { if ($diff > 0) {
if ($height === "auto" && $top === "auto" && $bottom === "auto") { if ($height === "auto" && $top === "auto" && $bottom === "auto") {
if ($margin_top === "auto") {
if ($margin_top === "auto") $margin_top = 0; $margin_top = 0;
if ($margin_bottom === "auto") $margin_bottom = 0; }
if ($margin_bottom === "auto") {
$margin_bottom = 0;
}
$height = $diff; $height = $diff;
} else if ($height === "auto" && $top === "auto") { } else if ($height === "auto" && $top === "auto") {
if ($margin_top === "auto") {
if ($margin_top === "auto") $margin_top = 0; $margin_top = 0;
if ($margin_bottom === "auto") $margin_bottom = 0; }
if ($margin_bottom === "auto") {
$margin_bottom = 0;
}
$height = $content_height; $height = $content_height;
$top = $diff - $content_height; $top = $diff - $content_height;
} else if ($height === "auto" && $bottom === "auto") { } else if ($height === "auto" && $bottom === "auto") {
if ($margin_top === "auto") {
if ($margin_top === "auto") $margin_top = 0; $margin_top = 0;
if ($margin_bottom === "auto") $margin_bottom = 0; }
if ($margin_bottom === "auto") {
$margin_bottom = 0;
}
$height = $content_height; $height = $content_height;
$bottom = $diff - $content_height; $bottom = $diff - $content_height;
} else if ($top === "auto" && $bottom === "auto") { } else if ($top === "auto" && $bottom === "auto") {
if ($margin_top === "auto") {
if ($margin_top === "auto") $margin_top = 0; $margin_top = 0;
if ($margin_bottom === "auto") $margin_bottom = 0; }
if ($margin_bottom === "auto") {
$margin_bottom = 0;
}
$bottom = $diff; $bottom = $diff;
} else if ($top === "auto") { } else if ($top === "auto") {
if ($margin_top === "auto") {
if ($margin_top === "auto") $margin_top = 0; $margin_top = 0;
if ($margin_bottom === "auto") $margin_bottom = 0; }
if ($margin_bottom === "auto") {
$margin_bottom = 0;
}
$top = $diff; $top = $diff;
} else if ($height === "auto") { } else if ($height === "auto") {
if ($margin_top === "auto") {
if ($margin_top === "auto") $margin_top = 0; $margin_top = 0;
if ($margin_bottom === "auto") $margin_bottom = 0; }
if ($margin_bottom === "auto") {
$margin_bottom = 0;
}
$height = $diff; $height = $diff;
} else if ($bottom === "auto") { } else if ($bottom === "auto") {
if ($margin_top === "auto") {
if ($margin_top === "auto") $margin_top = 0; $margin_top = 0;
if ($margin_bottom === "auto") $margin_bottom = 0; }
if ($margin_bottom === "auto") {
$margin_bottom = 0;
}
$bottom = $diff; $bottom = $diff;
} else { } else {
if ($style->overflow === "visible") { if ($style->overflow === "visible") {
// set all autos to zero // set all autos to zero
if ($margin_top === "auto") $margin_top = 0; if ($margin_top === "auto") {
if ($margin_bottom === "auto") $margin_bottom = 0; $margin_top = 0;
if ($top === "auto") $top = 0; }
if ($bottom === "auto") $bottom = 0; if ($margin_bottom === "auto") {
if ($height === "auto") $height = $content_height; $margin_bottom = 0;
}
if ($top === "auto") {
$top = 0;
}
if ($bottom === "auto") {
$bottom = 0;
}
if ($height === "auto") {
$height = $content_height;
}
} }
// FIXME: overflow hidden // FIXME: overflow hidden
} }
} }
} else { } else {
// Expand the height if overflow is visible // Expand the height if overflow is visible
if ($height === "auto" && $content_height > $height /* && $style->overflow === "visible" */) { if ($height === "auto" && $content_height > $height /* && $style->overflow === "visible" */) {
$height = $content_height; $height = $content_height;
@ -339,19 +383,14 @@ class Block extends AbstractFrameReflower
// _calculate_restricted_width // _calculate_restricted_width
// Only handle min/max height if the height is independent of the frame's content // Only handle min/max height if the height is independent of the frame's content
if (!($style->overflow === "visible" || if (!($style->overflow === "visible" || ($style->overflow === "hidden" && $height === "auto"))) {
($style->overflow === "hidden" && $height === "auto"))
) {
$min_height = $style->min_height; $min_height = $style->min_height;
$max_height = $style->max_height; $max_height = $style->max_height;
if (isset($cb["h"])) { if (isset($cb["h"])) {
$min_height = $style->length_in_pt($min_height, $cb["h"]); $min_height = $style->length_in_pt($min_height, $cb["h"]);
$max_height = $style->length_in_pt($max_height, $cb["h"]); $max_height = $style->length_in_pt($max_height, $cb["h"]);
} else if (isset($cb["w"])) { } else if (isset($cb["w"])) {
if (mb_strpos($min_height, "%") !== false) { if (mb_strpos($min_height, "%") !== false) {
$min_height = 0; $min_height = 0;
} else { } else {
@ -378,11 +417,9 @@ class Block extends AbstractFrameReflower
$height = $min_height; $height = $min_height;
} }
} }
} }
return array($height, $margin_top, $margin_bottom, $top, $bottom); return array($height, $margin_top, $margin_bottom, $top, $bottom);
} }
/** /**
@ -393,7 +430,7 @@ class Block extends AbstractFrameReflower
{ {
$style = $this->_frame->get_style(); $style = $this->_frame->get_style();
$w = $this->_frame->get_containing_block("w"); $w = $this->_frame->get_containing_block("w");
$width = $style->length_in_pt($style->width, $w); $width = (float)$style->length_in_pt($style->width, $w);
switch ($style->text_align) { switch ($style->text_align) {
default: default:
@ -428,11 +465,10 @@ class Block extends AbstractFrameReflower
} }
break; break;
case "justify": case "justify":
// We justify all lines except the last one // We justify all lines except the last one
$lines = $this->_frame->get_line_boxes(); // needs to be a variable (strict standards) $lines = $this->_frame->get_line_boxes(); // needs to be a variable (strict standards)
array_pop($lines); $last_line = array_pop($lines);
foreach ($lines as $i => $line) { foreach ($lines as $i => $line) {
if ($line->br) { if ($line->br) {
@ -454,10 +490,6 @@ class Block extends AbstractFrameReflower
} }
} }
// Only set the spacing if the line is long enough. This is really
// just an aesthetic choice ;)
//if ( $line["left"] + $line["w"] + $line["right"] > self::MIN_JUSTIFY_WIDTH * $width ) {
// Set the spacing for each child // Set the spacing for each child
if ($line->wc > 1) { if ($line->wc > 1) {
$spacing = ($width - ($line->left + $line->w + $line->right) + $space_width) / ($line->wc - 1); $spacing = ($width - ($line->left + $line->w + $line->right) + $space_width) / ($line->wc - 1);
@ -474,7 +506,7 @@ class Block extends AbstractFrameReflower
$text = $frame->get_text(); $text = $frame->get_text();
$spaces = mb_substr_count($text, " "); $spaces = mb_substr_count($text, " ");
$char_spacing = $style->length_in_pt($style->letter_spacing); $char_spacing = (float)$style->length_in_pt($style->letter_spacing);
$_spacing = $spacing + $char_spacing; $_spacing = $spacing + $char_spacing;
$frame->set_position($frame->get_position("x") + $dx); $frame->set_position($frame->get_position("x") + $dx);
@ -485,8 +517,16 @@ class Block extends AbstractFrameReflower
// The line (should) now occupy the entire width // The line (should) now occupy the entire width
$line->w = $width; $line->w = $width;
}
//} // Adjust the last line if necessary
if ($last_line->left) {
foreach ($last_line->get_frames() as $frame) {
if ($frame instanceof BlockFrameDecorator) {
continue;
}
$frame->set_position($frame->get_position("x") + $last_line->left);
}
} }
break; break;
@ -515,7 +555,6 @@ class Block extends AbstractFrameReflower
*/ */
function vertical_align() function vertical_align()
{ {
$canvas = null; $canvas = null;
foreach ($this->_frame->get_line_boxes() as $line) { foreach ($this->_frame->get_line_boxes() as $line) {
@ -524,13 +563,15 @@ class Block extends AbstractFrameReflower
foreach ($line->get_frames() as $frame) { foreach ($line->get_frames() as $frame) {
$style = $frame->get_style(); $style = $frame->get_style();
$isInlineBlock = (
if ($style->display !== "inline") { '-dompdf-image' === $style->display
|| 'inline-block' === $style->display
|| 'inline-table' === $style->display
);
if (!$isInlineBlock && $style->display !== "inline") {
continue; continue;
} }
$align = $frame->get_parent()->get_style()->vertical_align;
if (!isset($canvas)) { if (!isset($canvas)) {
$canvas = $frame->get_root()->get_dompdf()->get_canvas(); $canvas = $frame->get_root()->get_dompdf()->get_canvas();
} }
@ -538,34 +579,91 @@ class Block extends AbstractFrameReflower
$baseline = $canvas->get_font_baseline($style->font_family, $style->font_size); $baseline = $canvas->get_font_baseline($style->font_family, $style->font_size);
$y_offset = 0; $y_offset = 0;
switch ($align) { //FIXME: The 0.8 ratio applied to the height is arbitrary (used to accommodate descenders?)
case "baseline": if($isInlineBlock) {
$y_offset = $height * 0.8 - $baseline; // The 0.8 ratio is arbitrary until we find it's meaning $lineFrames = $line->get_frames();
break; if (count($lineFrames) == 1) {
continue;
}
$frameBox = $frame->get_frame()->get_border_box();
$imageHeightDiff = $height * 0.8 - (float)$frameBox['h'];
case "middle": $align = $frame->get_style()->vertical_align;
$y_offset = ($height * 0.8 - $baseline) / 2; if (in_array($align, Style::$vertical_align_keywords) === true) {
break; switch ($align) {
case "middle":
$y_offset = $imageHeightDiff / 2;
break;
case "sub": case "sub":
$y_offset = 0.3 * $height; $y_offset = 0.3 * $height + $imageHeightDiff;
break; break;
case "super": case "super":
$y_offset = -0.2 * $height; $y_offset = -0.2 * $height + $imageHeightDiff;
break; break;
case "text-top": case "text-top": // FIXME: this should be the height of the frame minus the height of the text
case "top": // Not strictly accurate, but good enough for now $y_offset = $height - (float)$style->length_in_pt($style->get_line_height(), $style->font_size);
break; break;
case "text-bottom": case "top":
case "bottom": break;
$y_offset = $height * 0.8 - $baseline;
break; case "text-bottom": // FIXME: align bottom of image with the descender?
case "bottom":
$y_offset = 0.3 * $height + $imageHeightDiff;
break;
case "baseline":
default:
$y_offset = $imageHeightDiff;
break;
}
} else {
$y_offset = $baseline - (float)$style->length_in_pt($align, $style->font_size) - (float)$frameBox['h'];
}
} else {
$parent = $frame->get_parent();
if ($parent instanceof TableCellFrameDecorator) {
$align = "baseline";
} else {
$align = $parent->get_style()->vertical_align;
}
if (in_array($align, Style::$vertical_align_keywords) === true) {
switch ($align) {
case "middle":
$y_offset = ($height * 0.8 - $baseline) / 2;
break;
case "sub":
$y_offset = $height * 0.8 - $baseline * 0.5;
break;
case "super":
$y_offset = $height * 0.8 - $baseline * 1.4;
break;
case "text-top":
case "top": // Not strictly accurate, but good enough for now
break;
case "text-bottom":
case "bottom":
$y_offset = $height * 0.8 - $baseline;
break;
case "baseline":
default:
$y_offset = $height * 0.8 - $baseline;
break;
}
} else {
$y_offset = $height * 0.8 - $baseline - (float)$style->length_in_pt($align, $style->font_size);
}
} }
if ($y_offset) { if ($y_offset !== 0) {
$frame->move(0, $y_offset); $frame->move(0, $y_offset);
} }
} }
@ -582,6 +680,14 @@ class Block extends AbstractFrameReflower
// Handle "clear" // Handle "clear"
if ($child_style->clear !== "none") { if ($child_style->clear !== "none") {
//TODO: this is a WIP for handling clear/float frames that are in between inline frames
if ($child->get_prev_sibling() !== null) {
$this->_frame->add_line();
}
if ($child_style->float !== "none" && $child->get_next_sibling()) {
$this->_frame->set_current_line_number($this->_frame->get_current_line_number() - 1);
}
$lowest_y = $root->get_lowest_float_offset($child); $lowest_y = $root->get_lowest_float_offset($child);
// If a float is still applying, we handle it // If a float is still applying, we handle it
@ -657,6 +763,7 @@ class Block extends AbstractFrameReflower
/** /**
* @param BlockFrameDecorator $block * @param BlockFrameDecorator $block
* @return mixed|void
*/ */
function reflow(BlockFrameDecorator $block = null) function reflow(BlockFrameDecorator $block = null)
{ {
@ -688,30 +795,30 @@ class Block extends AbstractFrameReflower
list($w, $left_margin, $right_margin, $left, $right) = $this->_calculate_restricted_width(); list($w, $left_margin, $right_margin, $left, $right) = $this->_calculate_restricted_width();
// Store the calculated properties // Store the calculated properties
$style->width = $w . "pt"; $style->width = $w;
$style->margin_left = $left_margin . "pt"; $style->margin_left = $left_margin;
$style->margin_right = $right_margin . "pt"; $style->margin_right = $right_margin;
$style->left = $left . "pt"; $style->left = $left;
$style->right = $right . "pt"; $style->right = $right;
// Update the position // Update the position
$this->_frame->position(); $this->_frame->position();
list($x, $y) = $this->_frame->get_position(); list($x, $y) = $this->_frame->get_position();
// Adjust the first line based on the text-indent property // Adjust the first line based on the text-indent property
$indent = $style->length_in_pt($style->text_indent, $cb["w"]); $indent = (float)$style->length_in_pt($style->text_indent, $cb["w"]);
$this->_frame->increase_line_width($indent); $this->_frame->increase_line_width($indent);
// Determine the content edge // Determine the content edge
$top = $style->length_in_pt(array($style->margin_top, $top = (float)$style->length_in_pt(array($style->margin_top,
$style->padding_top, $style->padding_top,
$style->border_top_width), $cb["h"]); $style->border_top_width), $cb["h"]);
$bottom = $style->length_in_pt(array($style->border_bottom_width, $bottom = (float)$style->length_in_pt(array($style->border_bottom_width,
$style->margin_bottom, $style->margin_bottom,
$style->padding_bottom), $cb["h"]); $style->padding_bottom), $cb["h"]);
$cb_x = $x + $left_margin + $style->length_in_pt(array($style->border_left_width, $cb_x = $x + (float)$left_margin + (float)$style->length_in_pt(array($style->border_left_width,
$style->padding_left), $cb["w"]); $style->padding_left), $cb["w"]);
$cb_y = $y + $top; $cb_y = $y + $top;
@ -753,11 +860,12 @@ class Block extends AbstractFrameReflower
$style->top = $top; $style->top = $top;
$style->bottom = $bottom; $style->bottom = $bottom;
$orig_style = $this->_frame->get_original_style();
$needs_reposition = ($style->position === "absolute" && ($style->right !== "auto" || $style->bottom !== "auto")); $needs_reposition = ($style->position === "absolute" && ($style->right !== "auto" || $style->bottom !== "auto"));
// Absolute positioning measurement // Absolute positioning measurement
if ($needs_reposition) { if ($needs_reposition) {
$orig_style = $this->_frame->get_original_style();
if ($orig_style->width === "auto" && ($orig_style->left === "auto" || $orig_style->right === "auto")) { if ($orig_style->width === "auto" && ($orig_style->left === "auto" || $orig_style->right === "auto")) {
$width = 0; $width = 0;
foreach ($this->_frame->get_line_boxes() as $line) { foreach ($this->_frame->get_line_boxes() as $line) {
@ -770,6 +878,25 @@ class Block extends AbstractFrameReflower
$style->right = $orig_style->right; $style->right = $orig_style->right;
} }
// Calculate inline-block / float auto-widths
if (($style->display === "inline-block" || $style->float !== 'none') && $orig_style->width === 'auto') {
$width = 0;
foreach ($this->_frame->get_line_boxes() as $line) {
$line->recalculate_width();
$width = max($line->w, $width);
}
if ($width === 0) {
foreach ($this->_frame->get_children() as $child) {
$width += $child->calculate_auto_width();
}
}
$style->width = $width;
}
$this->_text_align(); $this->_text_align();
$this->vertical_align(); $this->vertical_align();
@ -790,4 +917,32 @@ class Block extends AbstractFrameReflower
} }
} }
} }
/**
* Determine current frame width based on contents
*
* @return float
*/
public function calculate_auto_width()
{
$width = 0;
foreach ($this->_frame->get_line_boxes() as $line) {
$line_width = 0;
foreach ($line->get_frames() as $frame) {
if ($frame->get_original_style()->width == 'auto') {
$line_width += $frame->calculate_auto_width();
} else {
$line_width += $frame->get_margin_width();
}
}
$width = max($line_width, $width);
}
$this->_frame->get_style()->width = $width;
return $this->_frame->get_margin_width();
}
} }

View File

@ -20,11 +20,18 @@ use Dompdf\FrameDecorator\Image as ImageFrameDecorator;
class Image extends AbstractFrameReflower class Image extends AbstractFrameReflower
{ {
/**
* Image constructor.
* @param ImageFrameDecorator $frame
*/
function __construct(ImageFrameDecorator $frame) function __construct(ImageFrameDecorator $frame)
{ {
parent::__construct($frame); parent::__construct($frame);
} }
/**
* @param BlockFrameDecorator|null $block
*/
function reflow(BlockFrameDecorator $block = null) function reflow(BlockFrameDecorator $block = null)
{ {
$this->_frame->position(); $this->_frame->position();
@ -36,7 +43,7 @@ class Image extends AbstractFrameReflower
//if ($frame->get_style()->float !== "none" ) { //if ($frame->get_style()->float !== "none" ) {
// $page->add_floating_frame($this); // $page->add_floating_frame($this);
//} //}
// Set the frame's width // Set the frame's width
$this->get_min_max_width(); $this->get_min_max_width();
@ -45,9 +52,12 @@ class Image extends AbstractFrameReflower
} }
} }
/**
* @return array
*/
function get_min_max_width() function get_min_max_width()
{ {
if ($this->get_dompdf()->get_option("debugPng")) { if ($this->get_dompdf()->getOptions()->getDebugPng()) {
// Determine the image's size. Time consuming. Only when really needed? // Determine the image's size. Time consuming. Only when really needed?
list($img_width, $img_height) = Helpers::dompdf_getimagesize($this->_frame->get_image_url(), $this->get_dompdf()->getHttpContext()); list($img_width, $img_height) = Helpers::dompdf_getimagesize($this->_frame->get_image_url(), $this->get_dompdf()->getHttpContext());
print "get_min_max_width() " . print "get_min_max_width() " .
@ -83,7 +93,7 @@ class Image extends AbstractFrameReflower
} }
} }
$width = ((float)rtrim($width, "%") * $t) / 100; //maybe 0 $width = ((float)rtrim($width, "%") * $t) / 100; //maybe 0
} elseif (!mb_strpos($width, 'pt')) { } else {
// Don't set image original size if "%" branch was 0 or size not given. // Don't set image original size if "%" branch was 0 or size not given.
// Otherwise aspect changed on %/auto combination for width/height // Otherwise aspect changed on %/auto combination for width/height
// Resample according to px per inch // Resample according to px per inch
@ -96,13 +106,13 @@ class Image extends AbstractFrameReflower
$t = 0.0; $t = 0.0;
for ($f = $this->_frame->get_parent(); $f; $f = $f->get_parent()) { for ($f = $this->_frame->get_parent(); $f; $f = $f->get_parent()) {
$f_style = $f->get_style(); $f_style = $f->get_style();
$t = $f_style->length_in_pt($f_style->height); $t = (float)$f_style->length_in_pt($f_style->height);
if ($t != 0) { if ($t != 0) {
break; break;
} }
} }
$height = ((float)rtrim($height, "%") * $t) / 100; //maybe 0 $height = ((float)rtrim($height, "%") * $t) / 100; //maybe 0
} elseif (!mb_strpos($height, 'pt')) { } else {
// Don't set image original size if "%" branch was 0 or size not given. // Don't set image original size if "%" branch was 0 or size not given.
// Otherwise aspect changed on %/auto combination for width/height // Otherwise aspect changed on %/auto combination for width/height
// Resample according to px per inch // Resample according to px per inch
@ -118,7 +128,7 @@ class Image extends AbstractFrameReflower
// Resample according to px per inch // Resample according to px per inch
// See also ListBulletImage::__construct // See also ListBulletImage::__construct
if ($width == 0 && $height == 0) { if ($width == 0 && $height == 0) {
$dpi = $this->_frame->get_dompdf()->get_option("dpi"); $dpi = $this->_frame->get_dompdf()->getOptions()->getDpi();
$width = (float)($img_width * 72) / $dpi; $width = (float)($img_width * 72) / $dpi;
$height = (float)($img_height * 72) / $dpi; $height = (float)($img_height * 72) / $dpi;
$width_forced = false; $width_forced = false;
@ -179,7 +189,9 @@ class Image extends AbstractFrameReflower
} }
} }
if ($this->get_dompdf()->get_option("debugPng")) print $width . ' ' . $height . ';'; if ($this->get_dompdf()->getOptions()->getDebugPng()) {
print $width . ' ' . $height . ';';
}
$style->width = $width . "pt"; $style->width = $width . "pt";
$style->height = $height . "pt"; $style->height = $height . "pt";
@ -190,6 +202,5 @@ class Image extends AbstractFrameReflower
$style->max_height = "none"; $style->max_height = "none";
return array($width, $width, "min" => $width, "max" => $width); return array($width, $width, "min" => $width, "max" => $width);
} }
} }

View File

@ -19,13 +19,18 @@ use Dompdf\FrameDecorator\Text as TextFrameDecorator;
class Inline extends AbstractFrameReflower class Inline extends AbstractFrameReflower
{ {
/**
* Inline constructor.
* @param Frame $frame
*/
function __construct(Frame $frame) function __construct(Frame $frame)
{ {
parent::__construct($frame); parent::__construct($frame);
} }
//........................................................................ /**
* @param BlockFrameDecorator|null $block
*/
function reflow(BlockFrameDecorator $block = null) function reflow(BlockFrameDecorator $block = null)
{ {
$frame = $this->_frame; $frame = $this->_frame;
@ -73,4 +78,26 @@ class Inline extends AbstractFrameReflower
$child->reflow($block); $child->reflow($block);
} }
} }
/**
* Determine current frame width based on contents
*
* @return float
*/
public function calculate_auto_width()
{
$width = 0;
foreach ($this->_frame->get_children() as $child) {
if ($child->get_original_style()->width == 'auto') {
$width += $child->calculate_auto_width();
} else {
$width += $child->get_margin_width();
}
}
$this->_frame->get_style()->width = $width;
return $this->_frame->get_margin_width();
}
} }

View File

@ -18,13 +18,18 @@ use Dompdf\FrameDecorator\AbstractFrameDecorator;
class ListBullet extends AbstractFrameReflower class ListBullet extends AbstractFrameReflower
{ {
/**
* ListBullet constructor.
* @param AbstractFrameDecorator $frame
*/
function __construct(AbstractFrameDecorator $frame) function __construct(AbstractFrameDecorator $frame)
{ {
parent::__construct($frame); parent::__construct($frame);
} }
//........................................................................ /**
* @param BlockFrameDecorator|null $block
*/
function reflow(BlockFrameDecorator $block = null) function reflow(BlockFrameDecorator $block = null)
{ {
$style = $this->_frame->get_style(); $style = $this->_frame->get_style();
@ -36,6 +41,5 @@ class ListBullet extends AbstractFrameReflower
$p = $this->_frame->find_block_parent(); $p = $this->_frame->find_block_parent();
$p->add_frame_to_line($this->_frame); $p->add_frame_to_line($this->_frame);
} }
} }
} }

View File

@ -19,11 +19,18 @@ use Dompdf\FrameDecorator\Block as BlockFrameDecorator;
class NullFrameReflower extends AbstractFrameReflower class NullFrameReflower extends AbstractFrameReflower
{ {
/**
* NullFrameReflower constructor.
* @param Frame $frame
*/
function __construct(Frame $frame) function __construct(Frame $frame)
{ {
parent::__construct($frame); parent::__construct($frame);
} }
/**
* @param BlockFrameDecorator|null $block
*/
function reflow(BlockFrameDecorator $block = null) function reflow(BlockFrameDecorator $block = null)
{ {
return; return;

View File

@ -34,11 +34,19 @@ class Page extends AbstractFrameReflower
*/ */
private $_canvas; private $_canvas;
/**
* Page constructor.
* @param PageFrameDecorator $frame
*/
function __construct(PageFrameDecorator $frame) function __construct(PageFrameDecorator $frame)
{ {
parent::__construct($frame); parent::__construct($frame);
} }
/**
* @param Frame $frame
* @param $page_number
*/
function apply_page_style(Frame $frame, $page_number) function apply_page_style(Frame $frame, $page_number)
{ {
$style = $frame->get_style(); $style = $frame->get_style();
@ -77,11 +85,11 @@ class Page extends AbstractFrameReflower
} }
} }
//........................................................................
/** /**
* Paged layout: * Paged layout:
* http://www.w3.org/TR/CSS21/page.html * http://www.w3.org/TR/CSS21/page.html
*
* @param BlockFrameDecorator|null $block
*/ */
function reflow(BlockFrameDecorator $block = null) function reflow(BlockFrameDecorator $block = null)
{ {
@ -97,10 +105,10 @@ class Page extends AbstractFrameReflower
// Pages are only concerned with margins // Pages are only concerned with margins
$cb = $this->_frame->get_containing_block(); $cb = $this->_frame->get_containing_block();
$left = $style->length_in_pt($style->margin_left, $cb["w"]); $left = (float)$style->length_in_pt($style->margin_left, $cb["w"]);
$right = $style->length_in_pt($style->margin_right, $cb["w"]); $right = (float)$style->length_in_pt($style->margin_right, $cb["w"]);
$top = $style->length_in_pt($style->margin_top, $cb["h"]); $top = (float)$style->length_in_pt($style->margin_top, $cb["h"]);
$bottom = $style->length_in_pt($style->margin_bottom, $cb["h"]); $bottom = (float)$style->length_in_pt($style->margin_bottom, $cb["h"]);
$content_x = $cb["x"] + $left; $content_x = $cb["x"] + $left;
$content_y = $cb["y"] + $top; $content_y = $cb["y"] + $top;
@ -162,8 +170,6 @@ class Page extends AbstractFrameReflower
} }
} }
//........................................................................
/** /**
* Check for callbacks that need to be performed when a given event * Check for callbacks that need to be performed when a given event
* gets triggered on a page * gets triggered on a page

View File

@ -32,6 +32,10 @@ class Table extends AbstractFrameReflower
*/ */
protected $_state; protected $_state;
/**
* Table constructor.
* @param TableFrameDecorator $frame
*/
function __construct(TableFrameDecorator $frame) function __construct(TableFrameDecorator $frame)
{ {
$this->_state = null; $this->_state = null;
@ -47,8 +51,6 @@ class Table extends AbstractFrameReflower
$this->_min_max_cache = null; $this->_min_max_cache = null;
} }
//........................................................................
protected function _assign_widths() protected function _assign_widths()
{ {
$style = $this->_frame->get_style(); $style = $this->_frame->get_style();
@ -77,13 +79,13 @@ class Table extends AbstractFrameReflower
$centered = ($left === "auto" && $right === "auto"); $centered = ($left === "auto" && $right === "auto");
$left = $left === "auto" ? 0 : $style->length_in_pt($left, $cb["w"]); $left = (float)($left === "auto" ? 0 : $style->length_in_pt($left, $cb["w"]));
$right = $right === "auto" ? 0 : $style->length_in_pt($right, $cb["w"]); $right = (float)($right === "auto" ? 0 : $style->length_in_pt($right, $cb["w"]));
$delta = $left + $right; $delta = $left + $right;
if (!$centered) { if (!$centered) {
$delta += $style->length_in_pt(array( $delta += (float)$style->length_in_pt(array(
$style->padding_left, $style->padding_left,
$style->border_left_width, $style->border_left_width,
$style->border_right_width, $style->border_right_width,
@ -91,35 +93,37 @@ class Table extends AbstractFrameReflower
$cb["w"]); $cb["w"]);
} }
$min_table_width = $style->length_in_pt($style->min_width, $cb["w"] - $delta); $min_table_width = (float)$style->length_in_pt($style->min_width, $cb["w"] - $delta);
// min & max widths already include borders & padding // min & max widths already include borders & padding
$min_width -= $delta; $min_width -= $delta;
$max_width -= $delta; $max_width -= $delta;
if ($width !== "auto") { if ($width !== "auto") {
$preferred_width = (float)$style->length_in_pt($width, $cb["w"]) - $delta;
$preferred_width = $style->length_in_pt($width, $cb["w"]) - $delta; if ($preferred_width < $min_table_width) {
if ($preferred_width < $min_table_width)
$preferred_width = $min_table_width; $preferred_width = $min_table_width;
}
if ($preferred_width > $min_width) if ($preferred_width > $min_width) {
$width = $preferred_width; $width = $preferred_width;
else } else {
$width = $min_width; $width = $min_width;
}
} else { } else {
if ($max_width + $delta < $cb["w"]) {
if ($max_width + $delta < $cb["w"])
$width = $max_width; $width = $max_width;
else if ($cb["w"] - $delta > $min_width) } else if ($cb["w"] - $delta > $min_width) {
$width = $cb["w"] - $delta; $width = $cb["w"] - $delta;
else } else {
$width = $min_width; $width = $min_width;
}
if ($width < $min_table_width) if ($width < $min_table_width) {
$width = $min_table_width; $width = $min_table_width;
}
} }
@ -134,16 +138,15 @@ class Table extends AbstractFrameReflower
// If the whole table fits on the page, then assign each column it's max width // If the whole table fits on the page, then assign each column it's max width
if ($width == $max_width) { if ($width == $max_width) {
foreach (array_keys($columns) as $i) {
foreach (array_keys($columns) as $i)
$cellmap->set_column_width($i, $columns[$i]["max-width"]); $cellmap->set_column_width($i, $columns[$i]["max-width"]);
}
return; return;
} }
// Determine leftover and assign it evenly to all columns // Determine leftover and assign it evenly to all columns
if ($width > $min_width) { if ($width > $min_width) {
// We have four cases to deal with: // We have four cases to deal with:
// //
// 1. All columns are auto--no widths have been specified. In this // 1. All columns are auto--no widths have been specified. In this
@ -172,21 +175,19 @@ class Table extends AbstractFrameReflower
return; return;
} }
// Case 2 // Case 2
if ($absolute_used > 0 && $percent_used == 0) { if ($absolute_used > 0 && $percent_used == 0) {
if (count($auto) > 0) {
if (count($auto) > 0)
$increment = ($width - $auto_min - $absolute_used) / count($auto); $increment = ($width - $auto_min - $absolute_used) / count($auto);
}
// Use the absolutely specified width or the increment // Use the absolutely specified width or the increment
foreach (array_keys($columns) as $i) { foreach (array_keys($columns) as $i) {
if ($columns[$i]["absolute"] > 0 && count($auto)) {
if ($columns[$i]["absolute"] > 0 && count($auto))
$cellmap->set_column_width($i, $columns[$i]["min-width"]); $cellmap->set_column_width($i, $columns[$i]["min-width"]);
else if (count($auto)) } else if (count($auto)) {
$cellmap->set_column_width($i, $columns[$i]["min-width"] + $increment); $cellmap->set_column_width($i, $columns[$i]["min-width"] + $increment);
else { } else {
// All absolute columns // All absolute columns
$increment = ($width - $absolute_used) * $columns[$i]["absolute"] / $absolute_used; $increment = ($width - $absolute_used) * $columns[$i]["absolute"] / $absolute_used;
@ -197,19 +198,18 @@ class Table extends AbstractFrameReflower
return; return;
} }
// Case 3: // Case 3:
if ($absolute_used == 0 && $percent_used > 0) { if ($absolute_used == 0 && $percent_used > 0) {
$scale = null; $scale = null;
$remaining = null; $remaining = null;
// Scale percent values if the total percentage is > 100, or if all // Scale percent values if the total percentage is > 100, or if all
// values are specified as percentages. // values are specified as percentages.
if ($percent_used > 100 || count($auto) == 0) if ($percent_used > 100 || count($auto) == 0) {
$scale = 100 / $percent_used; $scale = 100 / $percent_used;
else } else {
$scale = 1; $scale = 1;
}
// Account for the minimum space used by the unassigned auto columns // Account for the minimum space used by the unassigned auto columns
$used_width = $auto_min; $used_width = $auto_min;
@ -221,8 +221,9 @@ class Table extends AbstractFrameReflower
$w = min($columns[$i]["percent"] * $width / 100, $slack); $w = min($columns[$i]["percent"] * $width / 100, $slack);
if ($w < $columns[$i]["min-width"]) if ($w < $columns[$i]["min-width"]) {
$w = $columns[$i]["min-width"]; $w = $columns[$i]["min-width"];
}
$cellmap->set_column_width($i, $w); $cellmap->set_column_width($i, $w);
$used_width += $w; $used_width += $w;
@ -234,8 +235,9 @@ class Table extends AbstractFrameReflower
if (count($auto) > 0) { if (count($auto) > 0) {
$increment = ($width - $used_width) / count($auto); $increment = ($width - $used_width) / count($auto);
foreach ($auto as $i) foreach ($auto as $i) {
$cellmap->set_column_width($i, $columns[$i]["min-width"] + $increment); $cellmap->set_column_width($i, $columns[$i]["min-width"] + $increment);
}
} }
return; return;
@ -245,7 +247,6 @@ class Table extends AbstractFrameReflower
// First-come, first served // First-come, first served
if ($absolute_used > 0 && $percent_used > 0) { if ($absolute_used > 0 && $percent_used > 0) {
$used_width = $auto_min; $used_width = $auto_min;
foreach ($absolute as $i) { foreach ($absolute as $i) {
@ -255,10 +256,11 @@ class Table extends AbstractFrameReflower
// Scale percent values if the total percentage is > 100 or there // Scale percent values if the total percentage is > 100 or there
// are no auto values to take up slack // are no auto values to take up slack
if ($percent_used > 100 || count($auto) == 0) if ($percent_used > 100 || count($auto) == 0) {
$scale = 100 / $percent_used; $scale = 100 / $percent_used;
else } else {
$scale = 1; $scale = 1;
}
$remaining_width = $width - $used_width; $remaining_width = $width - $used_width;
@ -268,8 +270,9 @@ class Table extends AbstractFrameReflower
$columns[$i]["percent"] *= $scale; $columns[$i]["percent"] *= $scale;
$w = min($columns[$i]["percent"] * $remaining_width / 100, $slack); $w = min($columns[$i]["percent"] * $remaining_width / 100, $slack);
if ($w < $columns[$i]["min-width"]) if ($w < $columns[$i]["min-width"]) {
$w = $columns[$i]["min-width"]; $w = $columns[$i]["min-width"];
}
$columns[$i]["used-width"] = $w; $columns[$i]["used-width"] = $w;
$used_width += $w; $used_width += $w;
@ -278,30 +281,28 @@ class Table extends AbstractFrameReflower
if (count($auto) > 0) { if (count($auto) > 0) {
$increment = ($width - $used_width) / count($auto); $increment = ($width - $used_width) / count($auto);
foreach ($auto as $i) foreach ($auto as $i) {
$cellmap->set_column_width($i, $columns[$i]["min-width"] + $increment); $cellmap->set_column_width($i, $columns[$i]["min-width"] + $increment);
}
} }
return; return;
} }
} else { // we are over constrained } else { // we are over constrained
// Each column gets its minimum width // Each column gets its minimum width
foreach (array_keys($columns) as $i) foreach (array_keys($columns) as $i) {
$cellmap->set_column_width($i, $columns[$i]["min-width"]); $cellmap->set_column_width($i, $columns[$i]["min-width"]);
}
} }
} }
//........................................................................ /**
* Determine the frame's height based on min/max height
// Determine the frame's height based on min/max height *
* @return float|int|mixed|string
*/
protected function _calculate_height() protected function _calculate_height()
{ {
$style = $this->_frame->get_style(); $style = $this->_frame->get_style();
$height = $style->height; $height = $style->height;
@ -311,15 +312,15 @@ class Table extends AbstractFrameReflower
// Determine our content height // Determine our content height
$content_height = 0; $content_height = 0;
foreach ($rows as $r) foreach ($rows as $r) {
$content_height += $r["height"]; $content_height += $r["height"];
}
$cb = $this->_frame->get_containing_block(); $cb = $this->_frame->get_containing_block();
if (!($style->overflow === "visible" || if (!($style->overflow === "visible" ||
($style->overflow === "hidden" && $height === "auto")) ($style->overflow === "hidden" && $height === "auto"))
) { ) {
// Only handle min/max height if the height is independent of the frame's content // Only handle min/max height if the height is independent of the frame's content
$min_height = $style->min_height; $min_height = $style->min_height;
@ -330,48 +331,47 @@ class Table extends AbstractFrameReflower
$max_height = $style->length_in_pt($max_height, $cb["h"]); $max_height = $style->length_in_pt($max_height, $cb["h"]);
} else if (isset($cb["w"])) { } else if (isset($cb["w"])) {
if (mb_strpos($min_height, "%") !== false) {
if (mb_strpos($min_height, "%") !== false)
$min_height = 0; $min_height = 0;
else } else {
$min_height = $style->length_in_pt($min_height, $cb["w"]); $min_height = $style->length_in_pt($min_height, $cb["w"]);
}
if (mb_strpos($max_height, "%") !== false) if (mb_strpos($max_height, "%") !== false) {
$max_height = "none"; $max_height = "none";
else } else {
$max_height = $style->length_in_pt($max_height, $cb["w"]); $max_height = $style->length_in_pt($max_height, $cb["w"]);
}
} }
if ($max_height !== "none" && $min_height > $max_height) if ($max_height !== "none" && $min_height > $max_height) {
// Swap 'em // Swap 'em
list($max_height, $min_height) = array($min_height, $max_height); list($max_height, $min_height) = array($min_height, $max_height);
}
if ($max_height !== "none" && $height > $max_height) if ($max_height !== "none" && $height > $max_height) {
$height = $max_height; $height = $max_height;
}
if ($height < $min_height) if ($height < $min_height) {
$height = $min_height; $height = $min_height;
}
} else { } else {
// Use the content height or the height value, whichever is greater // Use the content height or the height value, whichever is greater
if ($height !== "auto") { if ($height !== "auto") {
$height = $style->length_in_pt($height, $cb["h"]); $height = $style->length_in_pt($height, $cb["h"]);
if ($height <= $content_height) if ($height <= $content_height) {
$height = $content_height; $height = $content_height;
else } else {
$cellmap->set_frame_heights($height, $content_height); $cellmap->set_frame_heights($height, $content_height);
}
} else } else {
$height = $content_height; $height = $content_height;
}
} }
return $height; return $height;
} }
//........................................................................
/** /**
* @param BlockFrameDecorator $block * @param BlockFrameDecorator $block
@ -386,8 +386,9 @@ class Table extends AbstractFrameReflower
$page->check_forced_page_break($frame); $page->check_forced_page_break($frame);
// Bail if the page is full // Bail if the page is full
if ($page->is_full()) if ($page->is_full()) {
return; return;
}
// Let the page know that we're reflowing a table so that splits // Let the page know that we're reflowing a table so that splits
// are suppressed (simply setting page-break-inside: avoid won't // are suppressed (simply setting page-break-inside: avoid won't
@ -403,8 +404,9 @@ class Table extends AbstractFrameReflower
// Table layout algorithm: // Table layout algorithm:
// http://www.w3.org/TR/CSS21/tables.html#auto-table-layout // http://www.w3.org/TR/CSS21/tables.html#auto-table-layout
if (is_null($this->_state)) if (is_null($this->_state)) {
$this->get_min_max_width(); $this->get_min_max_width();
}
$cb = $frame->get_containing_block(); $cb = $frame->get_containing_block();
$style = $frame->get_style(); $style = $frame->get_style();
@ -415,14 +417,13 @@ class Table extends AbstractFrameReflower
if ($style->border_collapse === "separate") { if ($style->border_collapse === "separate") {
list($h, $v) = $style->border_spacing; list($h, $v) = $style->border_spacing;
$v = $style->length_in_pt($v) / 2; $v = (float)$style->length_in_pt($v) / 2;
$h = $style->length_in_pt($h) / 2; $h = (float)$style->length_in_pt($h) / 2;
$style->padding_left = $style->length_in_pt($style->padding_left, $cb["w"]) + $h;
$style->padding_right = $style->length_in_pt($style->padding_right, $cb["w"]) + $h;
$style->padding_top = $style->length_in_pt($style->padding_top, $cb["h"]) + $v;
$style->padding_bottom = $style->length_in_pt($style->padding_bottom, $cb["h"]) + $v;
$style->padding_left = (float)$style->length_in_pt($style->padding_left, $cb["w"]) + $h;
$style->padding_right = (float)$style->length_in_pt($style->padding_right, $cb["w"]) + $h;
$style->padding_top = (float)$style->length_in_pt($style->padding_top, $cb["h"]) + $v;
$style->padding_bottom = (float)$style->length_in_pt($style->padding_bottom, $cb["h"]) + $v;
} }
$this->_assign_widths(); $this->_assign_widths();
@ -442,31 +443,31 @@ class Table extends AbstractFrameReflower
$left = $right = $diff / 2; $left = $right = $diff / 2;
} }
$style->margin_left = "$left pt"; $style->margin_left = sprintf("%Fpt", $left);
$style->margin_right = "$right pt"; $style->margin_right = sprintf("%Fpt", $right);;
} else { } else {
if ($left === "auto") { if ($left === "auto") {
$left = $style->length_in_pt($cb["w"] - $right - $width, $cb["w"]); $left = (float)$style->length_in_pt($cb["w"] - $right - $width, $cb["w"]);
} }
if ($right === "auto") { if ($right === "auto") {
$left = $style->length_in_pt($left, $cb["w"]); $left = (float)$style->length_in_pt($left, $cb["w"]);
} }
} }
list($x, $y) = $frame->get_position(); list($x, $y) = $frame->get_position();
// Determine the content edge // Determine the content edge
$content_x = $x + $left + $style->length_in_pt(array($style->padding_left, $content_x = $x + (float)$left + (float)$style->length_in_pt(array($style->padding_left,
$style->border_left_width), $cb["w"]); $style->border_left_width), $cb["w"]);
$content_y = $y + $style->length_in_pt(array($style->margin_top, $content_y = $y + (float)$style->length_in_pt(array($style->margin_top,
$style->border_top_width, $style->border_top_width,
$style->padding_top), $cb["h"]); $style->padding_top), $cb["h"]);
if (isset($cb["h"])) if (isset($cb["h"])) {
$h = $cb["h"]; $h = $cb["h"];
else } else {
$h = null; $h = null;
}
$cellmap = $frame->get_cellmap(); $cellmap = $frame->get_cellmap();
$col =& $cellmap->get_column(0); $col =& $cellmap->get_column(0);
@ -479,17 +480,18 @@ class Table extends AbstractFrameReflower
// Set the containing block of each child & reflow // Set the containing block of each child & reflow
foreach ($frame->get_children() as $child) { foreach ($frame->get_children() as $child) {
// Bail if the page is full // Bail if the page is full
if (!$page->in_nested_table() && $page->is_full()) if (!$page->in_nested_table() && $page->is_full()) {
break; break;
}
$child->set_containing_block($content_x, $content_y, $width, $h); $child->set_containing_block($content_x, $content_y, $width, $h);
$child->reflow(); $child->reflow();
if (!$page->in_nested_table()) if (!$page->in_nested_table()) {
// Check if a split has occured // Check if a split has occured
$page->check_page_break($child); $page->check_page_break($child);
}
} }
@ -512,13 +514,14 @@ class Table extends AbstractFrameReflower
} }
} }
//........................................................................ /**
* @return array|null
*/
function get_min_max_width() function get_min_max_width()
{ {
if (!is_null($this->_min_max_cache)) {
if (!is_null($this->_min_max_cache))
return $this->_min_max_cache; return $this->_min_max_cache;
}
$style = $this->_frame->get_style(); $style = $this->_frame->get_style();
@ -550,11 +553,9 @@ class Table extends AbstractFrameReflower
if ($columns[$i]["absolute"] > 0) { if ($columns[$i]["absolute"] > 0) {
$this->_state["absolute"][] = $i; $this->_state["absolute"][] = $i;
$this->_state["absolute_used"] += $columns[$i]["absolute"]; $this->_state["absolute_used"] += $columns[$i]["absolute"];
} else if ($columns[$i]["percent"] > 0) { } else if ($columns[$i]["percent"] > 0) {
$this->_state["percent"][] = $i; $this->_state["percent"][] = $i;
$this->_state["percent_used"] += $columns[$i]["percent"]; $this->_state["percent_used"] += $columns[$i]["percent"];
} else { } else {
$this->_state["auto"][] = $i; $this->_state["auto"][] = $i;
$this->_state["auto_min"] += $columns[$i]["min-width"]; $this->_state["auto_min"] += $columns[$i]["min-width"];
@ -569,10 +570,11 @@ class Table extends AbstractFrameReflower
$style->margin_left, $style->margin_left,
$style->margin_right); $style->margin_right);
if ($style->border_collapse !== "collapse") if ($style->border_collapse !== "collapse") {
list($dims[]) = $style->border_spacing; list($dims[]) = $style->border_spacing;
}
$delta = $style->length_in_pt($dims, $this->_frame->get_containing_block("w")); $delta = (float)$style->length_in_pt($dims, $this->_frame->get_containing_block("w"));
$this->_state["min_width"] += $delta; $this->_state["min_width"] += $delta;
$this->_state["max_width"] += $delta; $this->_state["max_width"] += $delta;

View File

@ -17,14 +17,20 @@ use Dompdf\FrameDecorator\Table as TableFrameDecorator;
*/ */
class TableCell extends Block class TableCell extends Block
{ {
/**
* TableCell constructor.
* @param BlockFrameDecorator $frame
*/
function __construct(BlockFrameDecorator $frame) function __construct(BlockFrameDecorator $frame)
{ {
parent::__construct($frame); parent::__construct($frame);
} }
/**
* @param BlockFrameDecorator|null $block
*/
function reflow(BlockFrameDecorator $block = null) function reflow(BlockFrameDecorator $block = null)
{ {
$style = $this->_frame->get_style(); $style = $this->_frame->get_style();
$table = TableFrameDecorator::find_parent_table($this->_frame); $table = TableFrameDecorator::find_parent_table($this->_frame);
@ -44,21 +50,21 @@ class TableCell extends Block
//FIXME? //FIXME?
$h = $this->_frame->get_containing_block("h"); $h = $this->_frame->get_containing_block("h");
$left_space = $style->length_in_pt(array($style->margin_left, $left_space = (float)$style->length_in_pt(array($style->margin_left,
$style->padding_left, $style->padding_left,
$style->border_left_width), $style->border_left_width),
$w); $w);
$right_space = $style->length_in_pt(array($style->padding_right, $right_space = (float)$style->length_in_pt(array($style->padding_right,
$style->margin_right, $style->margin_right,
$style->border_right_width), $style->border_right_width),
$w); $w);
$top_space = $style->length_in_pt(array($style->margin_top, $top_space = (float)$style->length_in_pt(array($style->margin_top,
$style->padding_top, $style->padding_top,
$style->border_top_width), $style->border_top_width),
$h); $h);
$bottom_space = $style->length_in_pt(array($style->margin_bottom, $bottom_space = (float)$style->length_in_pt(array($style->margin_bottom,
$style->padding_bottom, $style->padding_bottom,
$style->border_bottom_width), $style->border_bottom_width),
$h); $h);
@ -69,7 +75,7 @@ class TableCell extends Block
$content_y = $line_y = $y + $top_space; $content_y = $line_y = $y + $top_space;
// Adjust the first line based on the text-indent property // Adjust the first line based on the text-indent property
$indent = $style->length_in_pt($style->text_indent, $w); $indent = (float)$style->length_in_pt($style->text_indent, $w);
$this->_frame->increase_line_width($indent); $this->_frame->increase_line_width($indent);
$page = $this->_frame->get_root(); $page = $this->_frame->get_root();
@ -80,41 +86,36 @@ class TableCell extends Block
// Set the containing blocks and reflow each child // Set the containing blocks and reflow each child
foreach ($this->_frame->get_children() as $child) { foreach ($this->_frame->get_children() as $child) {
if ($page->is_full()) {
if ($page->is_full())
break; break;
}
$child->set_containing_block($content_x, $content_y, $cb_w, $h); $child->set_containing_block($content_x, $content_y, $cb_w, $h);
$this->process_clear($child); $this->process_clear($child);
$child->reflow($this->_frame); $child->reflow($this->_frame);
$this->process_float($child, $x + $left_space, $w - $right_space - $left_space); $this->process_float($child, $x + $left_space, $w - $right_space - $left_space);
} }
// Determine our height // Determine our height
$style_height = $style->length_in_pt($style->height, $h); $style_height = (float)$style->length_in_pt($style->height, $h);
$this->_frame->set_content_height($this->_calculate_content_height()); $this->_frame->set_content_height($this->_calculate_content_height());
$height = max($style_height, $this->_frame->get_content_height()); $height = max($style_height, (float)$this->_frame->get_content_height());
// Let the cellmap know our height // Let the cellmap know our height
$cell_height = $height / count($cells["rows"]); $cell_height = $height / count($cells["rows"]);
if ($style_height <= $height) if ($style_height <= $height) {
$cell_height += $top_space + $bottom_space; $cell_height += $top_space + $bottom_space;
}
foreach ($cells["rows"] as $i) foreach ($cells["rows"] as $i) {
$cellmap->set_row_height($i, $cell_height); $cellmap->set_row_height($i, $cell_height);
}
$style->height = $height; $style->height = $height;
$this->_text_align(); $this->_text_align();
$this->vertical_align(); $this->vertical_align();
} }
} }

View File

@ -19,36 +19,42 @@ use Dompdf\Exception;
*/ */
class TableRow extends AbstractFrameReflower class TableRow extends AbstractFrameReflower
{ {
/**
* TableRow constructor.
* @param TableRowFrameDecorator $frame
*/
function __construct(TableRowFrameDecorator $frame) function __construct(TableRowFrameDecorator $frame)
{ {
parent::__construct($frame); parent::__construct($frame);
} }
//........................................................................ /**
* @param BlockFrameDecorator|null $block
*/
function reflow(BlockFrameDecorator $block = null) function reflow(BlockFrameDecorator $block = null)
{ {
$page = $this->_frame->get_root(); $page = $this->_frame->get_root();
if ($page->is_full()) if ($page->is_full()) {
return; return;
}
$this->_frame->position(); $this->_frame->position();
$style = $this->_frame->get_style(); $style = $this->_frame->get_style();
$cb = $this->_frame->get_containing_block(); $cb = $this->_frame->get_containing_block();
foreach ($this->_frame->get_children() as $child) { foreach ($this->_frame->get_children() as $child) {
if ($page->is_full()) {
if ($page->is_full())
return; return;
}
$child->set_containing_block($cb); $child->set_containing_block($cb);
$child->reflow(); $child->reflow();
} }
if ($page->is_full()) if ($page->is_full()) {
return; return;
}
$table = TableFrameDecorator::find_parent_table($this->_frame); $table = TableFrameDecorator::find_parent_table($this->_frame);
$cellmap = $table->get_cellmap(); $cellmap = $table->get_cellmap();
@ -56,11 +62,11 @@ class TableRow extends AbstractFrameReflower
$style->height = $cellmap->get_frame_height($this->_frame); $style->height = $cellmap->get_frame_height($this->_frame);
$this->_frame->set_position($cellmap->get_frame_position($this->_frame)); $this->_frame->set_position($cellmap->get_frame_position($this->_frame));
} }
//........................................................................ /**
* @throws Exception
*/
function get_min_max_width() function get_min_max_width()
{ {
throw new Exception("Min/max width is undefined for table rows"); throw new Exception("Min/max width is undefined for table rows");

View File

@ -18,11 +18,18 @@ use Dompdf\FrameDecorator\Table as TableFrameDecorator;
class TableRowGroup extends AbstractFrameReflower class TableRowGroup extends AbstractFrameReflower
{ {
/**
* TableRowGroup constructor.
* @param \Dompdf\Frame $frame
*/
function __construct($frame) function __construct($frame)
{ {
parent::__construct($frame); parent::__construct($frame);
} }
/**
* @param BlockFrameDecorator|null $block
*/
function reflow(BlockFrameDecorator $block = null) function reflow(BlockFrameDecorator $block = null)
{ {
$page = $this->_frame->get_root(); $page = $this->_frame->get_root();
@ -36,19 +43,20 @@ class TableRowGroup extends AbstractFrameReflower
foreach ($this->_frame->get_children() as $child) { foreach ($this->_frame->get_children() as $child) {
// Bail if the page is full // Bail if the page is full
if ($page->is_full()) if ($page->is_full()) {
return; return;
}
$child->set_containing_block($cb["x"], $cb["y"], $cb["w"], $cb["h"]); $child->set_containing_block($cb["x"], $cb["y"], $cb["w"], $cb["h"]);
$child->reflow(); $child->reflow();
// Check if a split has occured // Check if a split has occured
$page->check_page_break($child); $page->check_page_break($child);
} }
if ($page->is_full()) if ($page->is_full()) {
return; return;
}
$cellmap = $table->get_cellmap(); $cellmap = $table->get_cellmap();
$style->width = $cellmap->get_frame_width($this->_frame); $style->width = $cellmap->get_frame_width($this->_frame);
@ -56,10 +64,9 @@ class TableRowGroup extends AbstractFrameReflower
$this->_frame->set_position($cellmap->get_frame_position($this->_frame)); $this->_frame->set_position($cellmap->get_frame_position($this->_frame));
if ($table->get_style()->border_collapse === "collapse") if ($table->get_style()->border_collapse === "collapse") {
// Unset our borders because our cells are now using them // Unset our borders because our cells are now using them
$style->border_style = "none"; $style->border_style = "none";
}
} }
} }

View File

@ -11,6 +11,7 @@ namespace Dompdf\FrameReflower;
use Dompdf\FrameDecorator\Block as BlockFrameDecorator; use Dompdf\FrameDecorator\Block as BlockFrameDecorator;
use Dompdf\FrameDecorator\Text as TextFrameDecorator; use Dompdf\FrameDecorator\Text as TextFrameDecorator;
use Dompdf\FontMetrics; use Dompdf\FontMetrics;
use Dompdf\Helpers;
/** /**
* Reflows text frames. * Reflows text frames.
@ -47,8 +48,10 @@ class Text extends AbstractFrameReflower
$this->setFontMetrics($fontMetrics); $this->setFontMetrics($fontMetrics);
} }
//........................................................................ /**
* @param $text
* @return mixed
*/
protected function _collapse_white_space($text) protected function _collapse_white_space($text)
{ {
//$text = $this->_frame->get_text(); //$text = $this->_frame->get_text();
@ -57,8 +60,10 @@ class Text extends AbstractFrameReflower
return preg_replace(self::$_whitespace_pattern, " ", $text); return preg_replace(self::$_whitespace_pattern, " ", $text);
} }
//........................................................................ /**
* @param $text
* @return bool|int
*/
protected function _line_break($text) protected function _line_break($text)
{ {
$style = $this->_frame->get_style(); $style = $this->_frame->get_style();
@ -73,13 +78,13 @@ class Text extends AbstractFrameReflower
$available_width = $line_width - $current_line_width; $available_width = $line_width - $current_line_width;
// Account for word-spacing // Account for word-spacing
$word_spacing = $style->length_in_pt($style->word_spacing); $word_spacing = (float)$style->length_in_pt($style->word_spacing);
$char_spacing = $style->length_in_pt($style->letter_spacing); $char_spacing = (float)$style->length_in_pt($style->letter_spacing);
// Determine the frame width including margin, padding & border // Determine the frame width including margin, padding & border
$text_width = $this->getFontMetrics()->getTextWidth($text, $font, $size, $word_spacing, $char_spacing); $text_width = $this->getFontMetrics()->getTextWidth($text, $font, $size, $word_spacing, $char_spacing);
$mbp_width = $mbp_width =
$style->length_in_pt(array($style->margin_left, (float)$style->length_in_pt(array($style->margin_left,
$style->border_left_width, $style->border_left_width,
$style->padding_left, $style->padding_left,
$style->padding_right, $style->padding_right,
@ -99,8 +104,9 @@ class Text extends AbstractFrameReflower
// Helpers::pre_r($words); // Helpers::pre_r($words);
if ($frame_width <= $available_width) if ($frame_width <= $available_width) {
return false; return false;
}
// split the text into words // split the text into words
$words = preg_split('/([\s-]+)/u', $text, -1, PREG_SPLIT_DELIM_CAPTURE); $words = preg_split('/([\s-]+)/u', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
@ -115,8 +121,9 @@ class Text extends AbstractFrameReflower
for ($i = 0; $i < $wc; $i += 2) { for ($i = 0; $i < $wc; $i += 2) {
$word = $words[$i] . (isset($words[$i + 1]) ? $words[$i + 1] : ""); $word = $words[$i] . (isset($words[$i + 1]) ? $words[$i + 1] : "");
$word_width = $this->getFontMetrics()->getTextWidth($word, $font, $size, $word_spacing, $char_spacing); $word_width = $this->getFontMetrics()->getTextWidth($word, $font, $size, $word_spacing, $char_spacing);
if ($width + $word_width + $mbp_width > $available_width) if ($width + $word_width + $mbp_width > $available_width) {
break; break;
}
$width += $word_width; $width += $word_width;
$str .= $word; $str .= $word;
@ -126,7 +133,6 @@ class Text extends AbstractFrameReflower
// The first word has overflowed. Force it onto the line // The first word has overflowed. Force it onto the line
if ($current_line_width == 0 && $width == 0) { if ($current_line_width == 0 && $width == 0) {
$s = ""; $s = "";
$last_width = 0; $last_width = 0;
@ -143,39 +149,42 @@ class Text extends AbstractFrameReflower
} }
if ($break_word && $last_width > 0) { if ($break_word && $last_width > 0) {
$width += $last_width; //$width += $last_width;
$str .= substr($s, 0, -1); $str .= substr($s, 0, -1);
} else { } else {
$width += $word_width; //$width += $word_width;
$str .= $word; $str .= $word;
} }
} }
$offset = mb_strlen($str); $offset = mb_strlen($str);
// More debugging: // More debugging:
// var_dump($str); // var_dump($str);
// print_r("Width: ". $width); // print_r("Width: ". $width);
// print_r("Offset: " . $offset); // print_r("Offset: " . $offset);
return $offset; return $offset;
} }
//........................................................................ //........................................................................
/**
* @param $text
* @return bool|int
*/
protected function _newline_break($text) protected function _newline_break($text)
{ {
if (($i = mb_strpos($text, "\n")) === false) {
if (($i = mb_strpos($text, "\n")) === false)
return false; return false;
}
return $i + 1; return $i + 1;
} }
//........................................................................ /**
*
*/
protected function _layout_line() protected function _layout_line()
{ {
$frame = $this->_frame; $frame = $this->_frame;
@ -196,7 +205,7 @@ class Text extends AbstractFrameReflower
default: default:
break; break;
case "capitalize": case "capitalize":
$text = mb_convert_case($text, MB_CASE_TITLE); $text = Helpers::mb_ucwords($text);
break; break;
case "uppercase": case "uppercase":
$text = mb_convert_case($text, MB_CASE_UPPER); $text = mb_convert_case($text, MB_CASE_UPPER);
@ -209,12 +218,12 @@ class Text extends AbstractFrameReflower
// Handle white-space property: // Handle white-space property:
// http://www.w3.org/TR/CSS21/text.html#propdef-white-space // http://www.w3.org/TR/CSS21/text.html#propdef-white-space
switch ($style->white_space) { switch ($style->white_space) {
default: default:
case "normal": case "normal":
$frame->set_text($text = $this->_collapse_white_space($text)); $frame->set_text($text = $this->_collapse_white_space($text));
if ($text == "") if ($text == "") {
break; break;
}
$split = $this->_line_break($text); $split = $this->_line_break($text);
break; break;
@ -243,27 +252,29 @@ class Text extends AbstractFrameReflower
// Collapse white-space except for \n // Collapse white-space except for \n
$frame->set_text($text = preg_replace("/[ \t]+/u", " ", $text)); $frame->set_text($text = preg_replace("/[ \t]+/u", " ", $text));
if ($text == "") if ($text == "") {
break; break;
}
$split = $this->_newline_break($text); $split = $this->_newline_break($text);
if (($tmp = $this->_line_break($text)) !== false) { if (($tmp = $this->_line_break($text)) !== false) {
$add_line = $split < $tmp; $add_line = $split < $tmp;
$split = min($tmp, $split); $split = min($tmp, $split);
} else } else {
$add_line = true; $add_line = true;
}
break; break;
} }
// Handle degenerate case // Handle degenerate case
if ($text === "") if ($text === "") {
return; return;
}
if ($split !== false) { if ($split !== false) {
// Handle edge cases // Handle edge cases
if ($split == 0 && $text === " ") { if ($split == 0 && $text === " ") {
$frame->set_text(""); $frame->set_text("");
@ -271,16 +282,15 @@ class Text extends AbstractFrameReflower
} }
if ($split == 0) { if ($split == 0) {
// Trim newlines from the beginning of the line // Trim newlines from the beginning of the line
//$this->_frame->set_text(ltrim($text, "\n\r")); //$this->_frame->set_text(ltrim($text, "\n\r"));
$this->_block_parent->maximize_line_height($style->height, $frame);
$this->_block_parent->add_line(); $this->_block_parent->add_line();
$frame->position(); $frame->position();
// Layout the new line // Layout the new line
$this->_layout_line(); $this->_layout_line();
} else if ($split < mb_strlen($frame->get_text())) { } else if ($split < mb_strlen($frame->get_text())) {
// split the line if required // split the line if required
$frame->split_text($split); $frame->split_text($split);
@ -288,8 +298,9 @@ class Text extends AbstractFrameReflower
$t = $frame->get_text(); $t = $frame->get_text();
// Remove any trailing newlines // Remove any trailing newlines
if ($split > 1 && $t[$split - 1] === "\n" && !$frame->is_pre()) if ($split > 1 && $t[$split - 1] === "\n" && !$frame->is_pre()) {
$frame->set_text(mb_substr($t, 0, -1)); $frame->set_text(mb_substr($t, 0, -1));
}
// Do we need to trim spaces on wrapped lines? This might be desired, however, we // Do we need to trim spaces on wrapped lines? This might be desired, however, we
// can't trim the lines here or the layout will be affected if trimming the line // can't trim the lines here or the layout will be affected if trimming the line
@ -305,49 +316,48 @@ class Text extends AbstractFrameReflower
$this->_block_parent->add_line(); $this->_block_parent->add_line();
$frame->position(); $frame->position();
} }
} else { } else {
// Remove empty space from start and end of line, but only where there isn't an inline sibling // Remove empty space from start and end of line, but only where there isn't an inline sibling
// and the parent node isn't an inline element with siblings // and the parent node isn't an inline element with siblings
// FIXME: Include non-breaking spaces? // FIXME: Include non-breaking spaces?
$t = $frame->get_text(); $t = $frame->get_text();
$parent = $frame->get_parent(); $parent = $frame->get_parent();
$is_inline_frame = get_class($parent) === 'Inline_Frame_Decorator'; $is_inline_frame = ($parent instanceof \Dompdf\FrameDecorator\Inline);
if ((!$is_inline_frame && !$frame->get_next_sibling()) /* || if ((!$is_inline_frame && !$frame->get_next_sibling()) /* ||
( $is_inline_frame && !$parent->get_next_sibling())*/ ( $is_inline_frame && !$parent->get_next_sibling())*/
) { // fails <b>BOLD <u>UNDERLINED</u></b> becomes <b>BOLD<u>UNDERLINED</u></b> ) { // fails <b>BOLD <u>UNDERLINED</u></b> becomes <b>BOLD<u>UNDERLINED</u></b>
$t = rtrim($t); $t = rtrim($t);
} }
if ((!$is_inline_frame && !$frame->get_prev_sibling()) /* || if ((!$is_inline_frame && !$frame->get_prev_sibling()) /* ||
( $is_inline_frame && !$parent->get_prev_sibling())*/ ( $is_inline_frame && !$parent->get_prev_sibling())*/
) { // <span><span>A<span>B</span> C</span></span> fails (the whitespace is removed) ) { // <span><span>A<span>B</span> C</span></span> fails (the whitespace is removed)
$t = ltrim($t); $t = ltrim($t);
} }
$frame->set_text($t); $frame->set_text($t);
} }
// Set our new width // Set our new width
$width = $frame->recalculate_width(); $width = $frame->recalculate_width();
} }
//........................................................................ /**
* @param BlockFrameDecorator|null $block
*/
function reflow(BlockFrameDecorator $block = null) function reflow(BlockFrameDecorator $block = null)
{ {
$frame = $this->_frame; $frame = $this->_frame;
$page = $frame->get_root(); $page = $frame->get_root();
$page->check_forced_page_break($this->_frame); $page->check_forced_page_break($this->_frame);
if ($page->is_full()) if ($page->is_full()) {
return; return;
}
$this->_block_parent = /*isset($block) ? $block : */ $this->_block_parent = /*isset($block) ? $block : */
$frame->find_block_parent(); $frame->find_block_parent();
// Left trim the text if this is the first text on the line and we're // Left trim the text if this is the first text on the line and we're
// collapsing white space // collapsing white space
@ -383,11 +393,10 @@ class Text extends AbstractFrameReflower
$size = $style->font_size; $size = $style->font_size;
$font = $style->font_family; $font = $style->font_family;
$word_spacing = $style->length_in_pt($style->word_spacing); $word_spacing = (float)$style->length_in_pt($style->word_spacing);
$char_spacing = $style->length_in_pt($style->letter_spacing); $char_spacing = (float)$style->length_in_pt($style->letter_spacing);
switch ($style->white_space) { switch ($style->white_space) {
default: default:
case "normal": case "normal":
$str = preg_replace(self::$_whitespace_pattern, " ", $str); $str = preg_replace(self::$_whitespace_pattern, " ", $str);
@ -423,11 +432,9 @@ class Text extends AbstractFrameReflower
case "nowrap": case "nowrap":
$min = $this->getFontMetrics()->getTextWidth($this->_collapse_white_space($str), $font, $size, $word_spacing, $char_spacing); $min = $this->getFontMetrics()->getTextWidth($this->_collapse_white_space($str), $font, $size, $word_spacing, $char_spacing);
break; break;
} }
switch ($style->white_space) { switch ($style->white_space) {
default: default:
case "normal": case "normal":
case "nowrap": case "nowrap":
@ -449,12 +456,11 @@ class Text extends AbstractFrameReflower
reset($lines); reset($lines);
$str = key($lines); $str = key($lines);
break; break;
} }
$max = $this->getFontMetrics()->getTextWidth($str, $font, $size, $word_spacing, $char_spacing); $max = $this->getFontMetrics()->getTextWidth($str, $font, $size, $word_spacing, $char_spacing);
$delta = $style->length_in_pt(array($style->margin_left, $delta = (float)$style->length_in_pt(array($style->margin_left,
$style->border_left_width, $style->border_left_width,
$style->padding_left, $style->padding_left,
$style->padding_right, $style->padding_right,
@ -464,7 +470,6 @@ class Text extends AbstractFrameReflower
$max += $delta; $max += $delta;
return $this->_min_max_cache = array($min, $max, "min" => $min, "max" => $max); return $this->_min_max_cache = array($min, $max, "min" => $min, "max" => $max);
} }
/** /**
@ -484,4 +489,14 @@ class Text extends AbstractFrameReflower
{ {
return $this->fontMetrics; return $this->fontMetrics;
} }
/**
* Determine current frame width based on contents
*
* @return float
*/
public function calculate_auto_width()
{
return $this->_frame->recalculate_width();
}
} }

View File

@ -12,7 +12,7 @@ class Helpers
* @param mixed $mixed variable or expression to display * @param mixed $mixed variable or expression to display
* @param bool $return * @param bool $return
* *
* @return string * @return string|null
*/ */
public static function pre_r($mixed, $return = false) public static function pre_r($mixed, $return = false)
{ {
@ -33,6 +33,8 @@ class Helpers
} }
flush(); flush();
return null;
} }
/** /**
@ -72,7 +74,7 @@ class Helpers
//drive: followed by a relative path would be a drive specific default folder. //drive: followed by a relative path would be a drive specific default folder.
//not known in php app code, treat as abs path //not known in php app code, treat as abs path
//($url[1] !== ':' || ($url[2]!=='\\' && $url[2]!=='/')) //($url[1] !== ':' || ($url[2]!=='\\' && $url[2]!=='/'))
if ($url[0] !== '/' && (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN' || ($url[0] !== '\\' && $url[1] !== ':'))) { if ($url[0] !== '/' && (strtoupper(substr(PHP_OS, 0, 3)) !== 'WIN' || (mb_strlen($url) > 1 && $url[0] !== '\\' && $url[1] !== ':'))) {
// For rel path and local acess we ignore the host, and run the path through realpath() // For rel path and local acess we ignore the host, and run the path through realpath()
$ret .= realpath($base_path) . '/'; $ret .= realpath($base_path) . '/';
} }
@ -97,6 +99,33 @@ class Helpers
return $ret; return $ret;
} }
/**
* Builds a HTTP Content-Disposition header string using `$dispositionType`
* and `$filename`.
*
* If the filename contains any characters not in the ISO-8859-1 character
* set, a fallback filename will be included for clients not supporting the
* `filename*` parameter.
*
* @param string $dispositionType
* @param string $filename
* @return string
*/
public static function buildContentDispositionHeader($dispositionType, $filename)
{
$encoding = mb_detect_encoding($filename);
$fallbackfilename = mb_convert_encoding($filename, "ISO-8859-1", $encoding);
$fallbackfilename = str_replace("\"", "", $fallbackfilename);
$encodedfilename = rawurlencode($filename);
$contentDisposition = "Content-Disposition: $dispositionType; filename=\"$fallbackfilename\"";
if ($fallbackfilename !== $filename) {
$contentDisposition .= "; filename*=UTF-8''$encodedfilename";
}
return $contentDisposition;
}
/** /**
* Converts decimal numbers to roman numerals * Converts decimal numbers to roman numerals
* *
@ -125,12 +154,16 @@ class Helpers
$ret = ""; $ret = "";
switch (mb_strlen($num)) { switch (mb_strlen($num)) {
/** @noinspection PhpMissingBreakStatementInspection */
case 4: case 4:
$ret .= $thou[$num[3]]; $ret .= $thou[$num[3]];
/** @noinspection PhpMissingBreakStatementInspection */
case 3: case 3:
$ret .= $hund[$num[2]]; $ret .= $hund[$num[2]];
/** @noinspection PhpMissingBreakStatementInspection */
case 2: case 2:
$ret .= $tens[$num[1]]; $ret .= $tens[$num[1]];
/** @noinspection PhpMissingBreakStatementInspection */
case 1: case 1:
$ret .= $ones[$num[0]]; $ret .= $ones[$num[0]];
default: default:
@ -158,7 +191,7 @@ class Helpers
* *
* @param string $data_uri The data URI to parse * @param string $data_uri The data URI to parse
* *
* @return array The result with charset, mime type and decoded data * @return array|bool The result with charset, mime type and decoded data
*/ */
public static function parse_data_uri($data_uri) public static function parse_data_uri($data_uri)
{ {
@ -176,6 +209,37 @@ class Helpers
return $result; return $result;
} }
/**
* Encodes a Uniform Resource Identifier (URI) by replacing non-alphanumeric
* characters with a percent (%) sign followed by two hex digits, excepting
* characters in the URI reserved character set.
*
* Assumes that the URI is a complete URI, so does not encode reserved
* characters that have special meaning in the URI.
*
* Simulates the encodeURI function available in JavaScript
* https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/encodeURI
*
* Source: http://stackoverflow.com/q/4929584/264628
*
* @param string $uri The URI to encode
* @return string The original URL with special characters encoded
*/
public static function encodeURI($uri) {
$unescaped = array(
'%2D'=>'-','%5F'=>'_','%2E'=>'.','%21'=>'!', '%7E'=>'~',
'%2A'=>'*', '%27'=>"'", '%28'=>'(', '%29'=>')'
);
$reserved = array(
'%3B'=>';','%2C'=>',','%2F'=>'/','%3F'=>'?','%3A'=>':',
'%40'=>'@','%26'=>'&','%3D'=>'=','%2B'=>'+','%24'=>'$'
);
$score = array(
'%23'=>'#'
);
return strtr(rawurlencode(rawurldecode($uri)), array_merge($reserved,$unescaped,$score));
}
/** /**
* Decoder for RLE8 compression in windows bitmaps * Decoder for RLE8 compression in windows bitmaps
* http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/bitmaps_6x0u.asp * http://msdn.microsoft.com/library/default.asp?url=/library/en-us/gdi/bitmaps_6x0u.asp
@ -199,20 +263,27 @@ class Helpers
switch (ord($str[$i])) { switch (ord($str[$i])) {
case 0: # NEW LINE case 0: # NEW LINE
$padCnt = $lineWidth - strlen($out) % $lineWidth; $padCnt = $lineWidth - strlen($out) % $lineWidth;
if ($padCnt < $lineWidth) $out .= str_repeat(chr(0), $padCnt); # pad line if ($padCnt < $lineWidth) {
$out .= str_repeat(chr(0), $padCnt); # pad line
}
break; break;
case 1: # END OF FILE case 1: # END OF FILE
$padCnt = $lineWidth - strlen($out) % $lineWidth; $padCnt = $lineWidth - strlen($out) % $lineWidth;
if ($padCnt < $lineWidth) $out .= str_repeat(chr(0), $padCnt); # pad line if ($padCnt < $lineWidth) {
$out .= str_repeat(chr(0), $padCnt); # pad line
}
break 3; break 3;
case 2: # DELTA case 2: # DELTA
$i += 2; $i += 2;
break; break;
default: # ABSOLUTE MODE default: # ABSOLUTE MODE
$num = ord($str[$i]); $num = ord($str[$i]);
for ($j = 0; $j < $num; $j++) for ($j = 0; $j < $num; $j++) {
$out .= $str[++$i]; $out .= $str[++$i];
if ($num % 2) $i++; }
if ($num % 2) {
$i++;
}
} }
break; break;
default: default:
@ -492,9 +563,15 @@ class Helpers
$g = (1 - round(2.55 * ($m + $k))); $g = (1 - round(2.55 * ($m + $k)));
$b = (1 - round(2.55 * ($y + $k))); $b = (1 - round(2.55 * ($y + $k)));
if ($r < 0) $r = 0; if ($r < 0) {
if ($g < 0) $g = 0; $r = 0;
if ($b < 0) $b = 0; }
if ($g < 0) {
$g = 0;
}
if ($b < 0) {
$b = 0;
}
return array( return array(
$r, $g, $b, $r, $g, $b,
@ -529,7 +606,7 @@ class Helpers
$type = isset($types[$type]) ? $types[$type] : null; $type = isset($types[$type]) ? $types[$type] : null;
if ($width == null || $height == null) { if ($width == null || $height == null) {
$data = file_get_contents($filename, null, $context, 0, 26); list($data, $headers) = Helpers::getFileContent($filename, $context);
if (substr($data, 0, 2) === "BM") { if (substr($data, 0, 2) === "BM") {
$meta = unpack('vtype/Vfilesize/Vreserved/Voffset/Vheadersize/Vwidth/Vheight', $data); $meta = unpack('vtype/Vfilesize/Vreserved/Voffset/Vheadersize/Vwidth/Vheight', $data);
@ -538,7 +615,7 @@ class Helpers
$type = "bmp"; $type = "bmp";
} }
else { else {
if (strpos(file_get_contents($filename), "<svg") !== false) { if (strpos($data, "<svg") !== false) {
$doc = new \Svg\Document(); $doc = new \Svg\Document();
$doc->loadFile($filename); $doc->loadFile($filename);
@ -730,4 +807,88 @@ class Helpers
return $im; return $im;
} }
/**
* Gets the content of the file at the specified path using one of
* the following methods, in preferential order:
* - file_get_contents: if allow_url_fopen is true or the file is local
* - curl: if allow_url_fopen is false and curl is available
*
* @param string $uri
* @param resource $context (ignored if curl is used)
* @param int $offset
* @param int $maxlen (ignored if curl is used)
* @return bool|array
*/
public static function getFileContent($uri, $context = null, $offset = 0, $maxlen = null)
{
$result = false;
$headers = null;
list($proto, $host, $path, $file) = Helpers::explode_url($uri);
$is_local_path = ($proto == "" || $proto === "file://");
set_error_handler(array("\\Dompdf\\Helpers", "record_warnings"));
if ($is_local_path || ini_get("allow_url_fopen")) {
if ($is_local_path === false) {
$uri = Helpers::encodeURI($uri);
}
if (isset($maxlen)) {
$result = file_get_contents($uri, null, $context, $offset, $maxlen);
} else {
$result = file_get_contents($uri, null, $context, $offset);
}
if (isset($http_response_header)) {
$headers = $http_response_header;
}
} elseif (function_exists("curl_exec")) {
$curl = curl_init($uri);
//TODO: use $context to define additional curl options
curl_setopt($curl, CURLOPT_TIMEOUT, 10);
curl_setopt($curl, CURLOPT_CONNECTTIMEOUT, 10);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HEADER, true);
if ($offset > 0) {
curl_setopt($curl, CURLOPT_RESUME_FROM, $offset);
}
$data = curl_exec($curl);
$raw_headers = substr($data, 0, curl_getinfo($curl, CURLINFO_HEADER_SIZE));
$headers = preg_split("/[\n\r]+/", trim($raw_headers));
$result = substr($data, curl_getinfo($curl, CURLINFO_HEADER_SIZE));
curl_close($curl);
}
restore_error_handler();
return array($result, $headers);
}
public static function mb_ucwords($str) {
$max_len = mb_strlen($str);
if ($max_len === 1) {
return mb_strtoupper($str);
}
$str = mb_strtoupper(mb_substr($str, 0, 1)) . mb_substr($str, 1);
foreach (array(' ', '.', ',', '!', '?', '-', '+') as $s) {
$pos = 0;
while (($pos = mb_strpos($str, $s, $pos)) !== false) {
$pos++;
// Nothing to do if the separator is the last char of the string
if ($pos !== false && $pos < $max_len) {
// If the char we want to upper is the last char there is nothing to append behind
if ($pos + 1 < $max_len) {
$str = mb_substr($str, 0, $pos) . mb_strtoupper(mb_substr($str, $pos, 1)) . mb_substr($str, $pos + 1);
} else {
$str = mb_substr($str, 0, $pos) . mb_strtoupper(mb_substr($str, $pos, 1));
}
}
}
}
return $str;
}
} }

View File

@ -35,6 +35,8 @@ class Cache
* @var string * @var string
*/ */
public static $broken_image = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABABAMAAABYR2ztAAAAA3NCSVQICAjb4U/gAAAAHlBMVEWZmZn////g4OCkpKS1tbXv7++9vb2tra3m5ub5+fkFnN6oAAAACXBIWXMAAAsSAAALEgHS3X78AAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M0BrLToAAAABZ0RVh0Q3JlYXRpb24gVGltZQAwNC8xMi8xMRPnI58AAAGZSURBVEiJhZbPasMwDMbTw2DHKhDQcbDQPsEge4BAjg0Mxh5gkBcY7Niwkpx32PvOjv9JspX60It/+fxJsqxW1b11gN11rA7N3v6vAd5nfR9fDYCTDiyzAeA6qgKd9QDNoAtsAKyKCxzAAfhdBuyHGwC3oovNvQOaxxJwnSNg3ZQFAlBy4ax7AG6ZBLrgA5Cn038SAPgREiaJHJASwXYEhEQQIACyikTTCWCBJJoANBfpPAKQdBLHFMBYkctcBKIE9lAGggt6gRjgA2GV44CL7m1WgS08fAAdsPHxyyMAIyHujgRwEldHArCKy5cBz90+gNOyf8TTyKOUQN2LPEmgnWWPcKD+sr+rnuqTK1avAcHfRSv3afTgVAbqmCPiggLtGM8aSkBNOidVjADrmIDYebT1PoGsWJEE8Oc0b96aZoe4iMBZPiADB6RAzEUA2vwRmyiAL3Lfv6MBSEmUEg7ALt/3LhxwLgj4QNw4UCbKEsaBNpPsyRbgVRASFig78BIGyJNIJQyQTwIi0RvgT98H+Mi6W67j3X8H/427u5bfpQGVAAAAAElFTkSuQmCC"; public static $broken_image = "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEAAAABABAMAAABYR2ztAAAAA3NCSVQICAjb4U/gAAAAHlBMVEWZmZn////g4OCkpKS1tbXv7++9vb2tra3m5ub5+fkFnN6oAAAACXBIWXMAAAsSAAALEgHS3X78AAAAHHRFWHRTb2Z0d2FyZQBBZG9iZSBGaXJld29ya3MgQ1M0BrLToAAAABZ0RVh0Q3JlYXRpb24gVGltZQAwNC8xMi8xMRPnI58AAAGZSURBVEiJhZbPasMwDMbTw2DHKhDQcbDQPsEge4BAjg0Mxh5gkBcY7Niwkpx32PvOjv9JspX60It/+fxJsqxW1b11gN11rA7N3v6vAd5nfR9fDYCTDiyzAeA6qgKd9QDNoAtsAKyKCxzAAfhdBuyHGwC3oovNvQOaxxJwnSNg3ZQFAlBy4ax7AG6ZBLrgA5Cn038SAPgREiaJHJASwXYEhEQQIACyikTTCWCBJJoANBfpPAKQdBLHFMBYkctcBKIE9lAGggt6gRjgA2GV44CL7m1WgS08fAAdsPHxyyMAIyHujgRwEldHArCKy5cBz90+gNOyf8TTyKOUQN2LPEmgnWWPcKD+sr+rnuqTK1avAcHfRSv3afTgVAbqmCPiggLtGM8aSkBNOidVjADrmIDYebT1PoGsWJEE8Oc0b96aZoe4iMBZPiADB6RAzEUA2vwRmyiAL3Lfv6MBSEmUEg7ALt/3LhxwLgj4QNw4UCbKEsaBNpPsyRbgVRASFig78BIGyJNIJQyQTwIi0RvgT98H+Mi6W67j3X8H/427u5bfpQGVAAAAAElFTkSuQmCC";
public static $error_message = "Image not found or type unknown";
/** /**
* Current dompdf instance * Current dompdf instance
@ -67,7 +69,7 @@ class Cache
$data_uri = strpos($parsed_url['protocol'], "data:") === 0; $data_uri = strpos($parsed_url['protocol'], "data:") === 0;
$full_url = null; $full_url = null;
$enable_remote = $dompdf->get_option("enable_remote"); $enable_remote = $dompdf->getOptions()->getIsRemoteEnabled();
try { try {
@ -85,7 +87,7 @@ class Cache
$resolved_url = self::$_cache[$full_url]; $resolved_url = self::$_cache[$full_url];
} // From remote } // From remote
else { else {
$tmp_dir = $dompdf->get_option("temp_dir"); $tmp_dir = $dompdf->getOptions()->getTempDir();
$resolved_url = tempnam($tmp_dir, "ca_dompdf_img_"); $resolved_url = tempnam($tmp_dir, "ca_dompdf_img_");
$image = ""; $image = "";
@ -94,9 +96,7 @@ class Cache
$image = $parsed_data_uri['data']; $image = $parsed_data_uri['data'];
} }
} else { } else {
set_error_handler(array("\\Dompdf\\Helpers", "record_warnings")); list($image, $http_response_header) = Helpers::getFileContent($full_url, $dompdf->getHttpContext());
$image = file_get_contents($full_url, null, $dompdf->getHttpContext());
restore_error_handler();
} }
// Image not found or invalid // Image not found or invalid
@ -141,7 +141,7 @@ class Cache
} catch (ImageException $e) { } catch (ImageException $e) {
$resolved_url = self::$broken_image; $resolved_url = self::$broken_image;
$type = "png"; $type = "png";
$message = "Image not found or type unknown"; $message = self::$error_message;
Helpers::record_warnings($e->getCode(), $e->getMessage() . " \n $url", $e->getFile(), $e->getLine()); Helpers::record_warnings($e->getCode(), $e->getMessage() . " \n $url", $e->getFile(), $e->getLine());
} }
@ -154,12 +154,12 @@ class Cache
*/ */
static function clear() static function clear()
{ {
if (empty(self::$_cache) || self::$_dompdf->get_option("debugKeepTemp")) { if (empty(self::$_cache) || self::$_dompdf->getOptions()->getDebugKeepTemp()) {
return; return;
} }
foreach (self::$_cache as $file) { foreach (self::$_cache as $file) {
if (self::$_dompdf->get_option("debugPng")) { if (self::$_dompdf->getOptions()->getDebugPng()) {
print "[clear unlink $file]"; print "[clear unlink $file]";
} }
unlink($file); unlink($file);

View File

@ -7,8 +7,6 @@
*/ */
namespace Dompdf; namespace Dompdf;
use Dompdf\Frame;
/** /**
* Embeds Javascript into the PDF document * Embeds Javascript into the PDF document
* *
@ -22,19 +20,30 @@ class JavascriptEmbedder
*/ */
protected $_dompdf; protected $_dompdf;
function __construct(Dompdf $dompdf) /**
* JavascriptEmbedder constructor.
*
* @param Dompdf $dompdf
*/
public function __construct(Dompdf $dompdf)
{ {
$this->_dompdf = $dompdf; $this->_dompdf = $dompdf;
} }
function insert($script) /**
* @param $script
*/
public function insert($script)
{ {
$this->_dompdf->get_canvas()->javascript($script); $this->_dompdf->getCanvas()->javascript($script);
} }
function render(Frame $frame) /**
* @param Frame $frame
*/
public function render(Frame $frame)
{ {
if (!$this->_dompdf->get_option("enable_javascript")) { if (!$this->_dompdf->getOptions()->getIsJavascriptEnabled()) {
return; return;
} }

View File

@ -7,7 +7,6 @@
*/ */
namespace Dompdf; namespace Dompdf;
use Dompdf\Frame;
use Dompdf\FrameDecorator\Block; use Dompdf\FrameDecorator\Block;
use Dompdf\FrameDecorator\Page; use Dompdf\FrameDecorator\Page;
@ -81,8 +80,9 @@ class LineBox
* Class constructor * Class constructor
* *
* @param Block $frame the Block containing this line * @param Block $frame the Block containing this line
* @param int $y
*/ */
function __construct(Block $frame, $y = 0) public function __construct(Block $frame, $y = 0)
{ {
$this->_block_frame = $frame; $this->_block_frame = $frame;
$this->_frames = array(); $this->_frames = array();
@ -98,7 +98,7 @@ class LineBox
* *
* @return Frame[] * @return Frame[]
*/ */
function get_floats_inside(Page $root) public function get_floats_inside(Page $root)
{ {
$floating_frames = $root->get_floating_frames(); $floating_frames = $root->get_floating_frames();
@ -129,7 +129,7 @@ class LineBox
foreach ($floating_frames as $_floating) { foreach ($floating_frames as $_floating) {
$p = $_floating->get_parent(); $p = $_floating->get_parent();
while (($p = $p->get_parent()) && $p !== $parent) ; while (($p = $p->get_parent()) && $p !== $parent);
if ($p) { if ($p) {
$childs[] = $p; $childs[] = $p;
@ -139,9 +139,12 @@ class LineBox
return $childs; return $childs;
} }
function get_float_offsets() /**
*
*/
public function get_float_offsets()
{ {
static $anti_infinite_loop = 500; // FIXME smelly hack static $anti_infinite_loop = 10000; // FIXME smelly hack
$reflower = $this->_block_frame->get_reflower(); $reflower = $this->_block_frame->get_reflower();
@ -158,18 +161,22 @@ class LineBox
return; return;
} }
$style = $this->_block_frame->get_style();
$floating_frames = $this->get_floats_inside($root); $floating_frames = $this->get_floats_inside($root);
$inside_left_floating_width = 0;
$inside_right_floating_width = 0;
$outside_left_floating_width = 0;
$outside_right_floating_width = 0;
foreach ($floating_frames as $child_key => $floating_frame) { foreach ($floating_frames as $child_key => $floating_frame) {
$floating_frame_parent = $floating_frame->get_parent();
$id = $floating_frame->get_id(); $id = $floating_frame->get_id();
if (isset($this->floating_blocks[$id])) { if (isset($this->floating_blocks[$id])) {
continue; continue;
} }
$floating_style = $floating_frame->get_style(); $float = $floating_frame->get_style()->float;
$float = $floating_style->float;
$floating_width = $floating_frame->get_margin_width(); $floating_width = $floating_frame->get_margin_width();
if (!$cb_w) { if (!$cb_w) {
@ -185,13 +192,21 @@ class LineBox
// If the child is still shifted by the floating element // If the child is still shifted by the floating element
if ($anti_infinite_loop-- > 0 && if ($anti_infinite_loop-- > 0 &&
$floating_frame->get_position("y") + $floating_frame->get_margin_height() > $this->y && $floating_frame->get_position("y") + $floating_frame->get_margin_height() >= $this->y &&
$block->get_position("x") + $block->get_margin_width() > $floating_frame->get_position("x") $block->get_position("x") + $block->get_margin_width() >= $floating_frame->get_position("x")
) { ) {
if ($float === "left") { if ($float === "left") {
$this->left += $floating_width; if ($floating_frame_parent === $this->_block_frame) {
} else { $inside_left_floating_width += $floating_width;
$this->right += $floating_width; } else {
$outside_left_floating_width += $floating_width;
}
} elseif ($float === "right") {
if ($floating_frame_parent === $this->_block_frame) {
$inside_right_floating_width += $floating_width;
} else {
$outside_right_floating_width += $floating_width;
}
} }
$this->floating_blocks[$id] = true; $this->floating_blocks[$id] = true;
@ -200,12 +215,21 @@ class LineBox
$root->remove_floating_frame($child_key); $root->remove_floating_frame($child_key);
} }
} }
$this->left += $inside_left_floating_width;
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))) {
$this->left += $outside_left_floating_width - (float)$style->length_in_pt($style->margin_left) - (float)$style->length_in_pt($style->padding_left);
}
$this->right += $inside_right_floating_width;
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))) {
$this->right += $outside_right_floating_width - (float)$style->length_in_pt($style->margin_right) - (float)$style->length_in_pt($style->padding_right);
}
} }
/** /**
* @return float * @return float
*/ */
function get_width() public function get_width()
{ {
return $this->left + $this->w + $this->right; return $this->left + $this->w + $this->right;
} }
@ -213,7 +237,7 @@ class LineBox
/** /**
* @return Block * @return Block
*/ */
function get_block_frame() public function get_block_frame()
{ {
return $this->_block_frame; return $this->_block_frame;
} }
@ -229,12 +253,31 @@ class LineBox
/** /**
* @param Frame $frame * @param Frame $frame
*/ */
function add_frame(Frame $frame) public function add_frame(Frame $frame)
{ {
$this->_frames[] = $frame; $this->_frames[] = $frame;
} }
function __toString() /**
* Recalculate LineBox width based on the contained frames total width.
*
* @return float
*/
public function recalculate_width()
{
$width = 0;
foreach ($this->get_frames() as $frame) {
$width += $frame->calculate_auto_width();
}
return $this->w = $width;
}
/**
* @return string
*/
public function __toString()
{ {
$props = array("wc", "y", "w", "h", "left", "right", "br"); $props = array("wc", "y", "w", "h", "left", "right", "br");
$s = ""; $s = "";
@ -255,6 +298,6 @@ class LineBox
class LineBoxList implements Iterator { class LineBoxList implements Iterator {
private $_p = 0; private $_p = 0;
private $_lines = array(); private $_lines = array();
} }
*/ */

View File

@ -36,7 +36,7 @@ class Options
* *
* This directory contains the cached font metrics for the fonts used by DOMPDF. * This directory contains the cached font metrics for the fonts used by DOMPDF.
* This directory can be the same as $fontDir * This directory can be the same as $fontDir
* *
* Note: This directory must exist and be writable by the webserver process. * Note: This directory must exist and be writable by the webserver process.
* *
* @var string * @var string
@ -52,7 +52,7 @@ class Options
* read any files on the server. This should be an absolute path. * read any files on the server. This should be an absolute path.
* *
* ==== IMPORTANT ==== * ==== IMPORTANT ====
* This setting may increase the risk of system exploit. Do not change * This setting may increase the risk of system exploit. Do not change
* this settings without understanding the consequences. Additional * this settings without understanding the consequences. Additional
* documentation is available on the dompdf wiki at: * documentation is available on the dompdf wiki at:
* https://github.com/dompdf/dompdf/wiki * https://github.com/dompdf/dompdf/wiki
@ -84,12 +84,21 @@ class Options
* The default paper size. * The default paper size.
* *
* North America standard is "letter"; other countries generally "a4" * North America standard is "letter"; other countries generally "a4"
* @see Dompdf\Adapter\CPDF::PAPER_SIZES for valid sizes * @see \Dompdf\Adapter\CPDF::PAPER_SIZES for valid sizes
* *
* @var string * @var string
*/ */
private $defaultPaperSize = "letter"; private $defaultPaperSize = "letter";
/**
* The default paper orientation.
*
* The orientation of the page (portrait or landscape).
*
* @var string
*/
private $defaultPaperOrientation = "portrait";
/** /**
* The default font family * The default font family
* *
@ -141,7 +150,7 @@ class Options
* system access available to dompdf. Set this option to false (recommended) * system access available to dompdf. Set this option to false (recommended)
* if you wish to process untrusted documents. * if you wish to process untrusted documents.
* *
* This setting may increase the risk of system exploit. Do not change * This setting may increase the risk of system exploit. Do not change
* this settings without understanding the consequences. Additional * this settings without understanding the consequences. Additional
* documentation is available on the dompdf wiki at: * documentation is available on the dompdf wiki at:
* https://github.com/dompdf/dompdf/wiki * https://github.com/dompdf/dompdf/wiki
@ -163,7 +172,7 @@ class Options
* tracing back appears to being downloaded by your server, or allows malicious php code * tracing back appears to being downloaded by your server, or allows malicious php code
* in remote html pages to be executed by your server with your account privileges. * in remote html pages to be executed by your server with your account privileges.
* *
* This setting may increase the risk of system exploit. Do not change * This setting may increase the risk of system exploit. Do not change
* this settings without understanding the consequences. Additional * this settings without understanding the consequences. Additional
* documentation is available on the dompdf wiki at: * documentation is available on the dompdf wiki at:
* https://github.com/dompdf/dompdf/wiki * https://github.com/dompdf/dompdf/wiki
@ -192,7 +201,7 @@ class Options
/** /**
* Whether to enable font subsetting or not. * Whether to enable font subsetting or not.
* *
* @var is_bool * @var bool
*/ */
private $isFontSubsettingEnabled = false; private $isFontSubsettingEnabled = false;
@ -239,16 +248,16 @@ class Options
/** /**
* The PDF rendering backend to use * The PDF rendering backend to use
* *
* Valid settings are 'PDFLib', 'CPDF', 'GD', and 'auto'. 'auto' will * Valid settings are 'PDFLib', 'CPDF', 'GD', and 'auto'. 'auto' will
* look for PDFLib and use it if found, or if not it will fall back on * look for PDFLib and use it if found, or if not it will fall back on
* CPDF. 'GD' renders PDFs to graphic files. {@link Dompdf\CanvasFactory} * CPDF. 'GD' renders PDFs to graphic files. {@link Dompdf\CanvasFactory}
* ultimately determines which rendering class to instantiate * ultimately determines which rendering class to instantiate
* based on this setting. * based on this setting.
* *
* @var string * @var string
*/ */
private $pdfBackend = "CPDF"; private $pdfBackend = "CPDF";
/** /**
* PDFlib license key * PDFlib license key
* *
@ -264,7 +273,7 @@ class Options
* @var string * @var string
*/ */
private $pdflibLicense = ""; private $pdflibLicense = "";
/** /**
* @var string * @var string
* @deprecated * @deprecated
@ -319,6 +328,8 @@ class Options
$this->setDefaultMediaType($value); $this->setDefaultMediaType($value);
} elseif ($key === 'defaultPaperSize' || $key === 'default_paper_size') { } elseif ($key === 'defaultPaperSize' || $key === 'default_paper_size') {
$this->setDefaultPaperSize($value); $this->setDefaultPaperSize($value);
} elseif ($key === 'defaultPaperOrientation' || $key === 'default_paper_orientation') {
$this->setDefaultPaperOrientation($value);
} elseif ($key === 'defaultFont' || $key === 'default_font') { } elseif ($key === 'defaultFont' || $key === 'default_font') {
$this->setDefaultFont($value); $this->setDefaultFont($value);
} elseif ($key === 'dpi') { } elseif ($key === 'dpi') {
@ -384,6 +395,8 @@ class Options
return $this->getDefaultMediaType(); return $this->getDefaultMediaType();
} elseif ($key === 'defaultPaperSize' || $key === 'default_paper_size') { } elseif ($key === 'defaultPaperSize' || $key === 'default_paper_size') {
return $this->getDefaultPaperSize(); return $this->getDefaultPaperSize();
} elseif ($key === 'defaultPaperOrientation' || $key === 'default_paper_orientation') {
return $this->getDefaultPaperOrientation();
} elseif ($key === 'defaultFont' || $key === 'default_font') { } elseif ($key === 'defaultFont' || $key === 'default_font') {
return $this->getDefaultFont(); return $this->getDefaultFont();
} elseif ($key === 'dpi') { } elseif ($key === 'dpi') {
@ -481,7 +494,7 @@ class Options
{ {
return $this->pdfBackend; return $this->pdfBackend;
} }
/** /**
* @param string $pdflibLicense * @param string $pdflibLicense
* @return $this * @return $this
@ -499,7 +512,7 @@ class Options
{ {
return $this->pdflibLicense; return $this->pdflibLicense;
} }
/** /**
* @param string $chroot * @param string $chroot
* @return $this * @return $this
@ -708,6 +721,16 @@ class Options
return $this; return $this;
} }
/**
* @param string $defaultPaperOrientation
* @return $this
*/
public function setDefaultPaperOrientation($defaultPaperOrientation)
{
$this->defaultPaperOrientation = $defaultPaperOrientation;
return $this;
}
/** /**
* @return string * @return string
*/ */
@ -716,6 +739,14 @@ class Options
return $this->defaultPaperSize; return $this->defaultPaperSize;
} }
/**
* @return string
*/
public function getDefaultPaperOrientation()
{
return $this->defaultPaperOrientation;
}
/** /**
* @param int $dpi * @param int $dpi
* @return $this * @return $this

View File

@ -7,8 +7,6 @@
*/ */
namespace Dompdf; namespace Dompdf;
use Dompdf\Frame;
/** /**
* Executes inline PHP code during the rendering process * Executes inline PHP code during the rendering process
* *
@ -22,14 +20,22 @@ class PhpEvaluator
*/ */
protected $_canvas; protected $_canvas;
function __construct(Canvas $canvas) /**
* PhpEvaluator constructor.
* @param Canvas $canvas
*/
public function __construct(Canvas $canvas)
{ {
$this->_canvas = $canvas; $this->_canvas = $canvas;
} }
function evaluate($code, $vars = array()) /**
* @param $code
* @param array $vars
*/
public function evaluate($code, $vars = array())
{ {
if (!$this->_canvas->get_dompdf()->get_option("enable_php")) { if (!$this->_canvas->get_dompdf()->getOptions()->getIsPhpEnabled()) {
return; return;
} }
@ -44,11 +50,13 @@ class PhpEvaluator
$$k = $v; $$k = $v;
} }
//$code = html_entity_decode($code); // @todo uncomment this when tested
eval($code); eval($code);
} }
function render(Frame $frame) /**
* @param Frame $frame
*/
public function render(Frame $frame)
{ {
$this->evaluate($frame->get_node()->nodeValue); $this->evaluate($frame->get_node()->nodeValue);
} }

View File

@ -16,15 +16,11 @@ use Dompdf\FrameDecorator\AbstractFrameDecorator;
class Absolute extends AbstractPositioner class Absolute extends AbstractPositioner
{ {
function __construct(AbstractFrameDecorator $frame) /**
* @param AbstractFrameDecorator $frame
*/
function position(AbstractFrameDecorator $frame)
{ {
parent::__construct($frame);
}
function position()
{
$frame = $this->_frame;
$style = $frame->get_style(); $style = $frame->get_style();
$p = $frame->find_positionned_parent(); $p = $frame->find_positionned_parent();
@ -43,7 +39,7 @@ class Absolute extends AbstractPositioner
list($width, $height) = array($frame->get_margin_width(), $frame->get_margin_height()); list($width, $height) = array($frame->get_margin_width(), $frame->get_margin_height());
$orig_style = $this->_frame->get_original_style(); $orig_style = $frame->get_original_style();
$orig_width = $orig_style->width; $orig_width = $orig_style->width;
$orig_height = $orig_style->height; $orig_height = $orig_style->height;
@ -76,14 +72,14 @@ class Absolute extends AbstractPositioner
} else { } else {
if ($right === "auto") { if ($right === "auto") {
// B or F // B or F
$x += $left; $x += (float)$left;
} else { } else {
if ($orig_width === "auto") { if ($orig_width === "auto") {
// D - TODO change width // D - TODO change width
$x += $left; $x += (float)$left;
} else { } else {
// H - Everything is fixed: left + width win // H - Everything is fixed: left + width win
$x += $left; $x += (float)$left;
} }
} }
} }
@ -96,28 +92,27 @@ class Absolute extends AbstractPositioner
} else { } else {
if ($orig_height === "auto") { if ($orig_height === "auto") {
// C // C
$y += $h - $height - $bottom; $y += (float)$h - $height - (float)$bottom;
} else { } else {
// G // G
$y += $h - $height - $bottom; $y += (float)$h - $height - (float)$bottom;
} }
} }
} else { } else {
if ($bottom === "auto") { if ($bottom === "auto") {
// B or F // B or F
$y += $top; $y += (float)$top;
} else { } else {
if ($orig_height === "auto") { if ($orig_height === "auto") {
// D - TODO change height // D - TODO change height
$y += $top; $y += (float)$top;
} else { } else {
// H - Everything is fixed: top + height win // H - Everything is fixed: top + height win
$y += $top; $y += (float)$top;
} }
} }
} }
$frame->set_position($x, $y); $frame->set_position($x, $y);
} }
} }

View File

@ -22,30 +22,26 @@ abstract class AbstractPositioner
{ {
/** /**
* @var \Dompdf\FrameDecorator\AbstractFrameDecorator * @param AbstractFrameDecorator $frame
* @return mixed
*/ */
protected $_frame; abstract function position(AbstractFrameDecorator $frame);
//........................................................................ /**
* @param AbstractFrameDecorator $frame
function __construct(AbstractFrameDecorator $frame) * @param $offset_x
* @param $offset_y
* @param bool $ignore_self
*/
function move(AbstractFrameDecorator $frame, $offset_x, $offset_y, $ignore_self = false)
{ {
$this->_frame = $frame; list($x, $y) = $frame->get_position();
}
//........................................................................
abstract function position();
function move($offset_x, $offset_y, $ignore_self = false)
{
list($x, $y) = $this->_frame->get_position();
if (!$ignore_self) { if (!$ignore_self) {
$this->_frame->set_position($x + $offset_x, $y + $offset_y); $frame->set_position($x + $offset_x, $y + $offset_y);
} }
foreach ($this->_frame->get_children() as $child) { foreach ($frame->get_children() as $child) {
$child->move($offset_x, $offset_y); $child->move($offset_x, $offset_y);
} }
} }

View File

@ -18,17 +18,8 @@ use Dompdf\FrameDecorator\AbstractFrameDecorator;
*/ */
class Block extends AbstractPositioner { class Block extends AbstractPositioner {
function position(AbstractFrameDecorator $frame)
function __construct(AbstractFrameDecorator $frame)
{ {
parent::__construct($frame);
}
//........................................................................
function position()
{
$frame = $this->_frame;
$style = $frame->get_style(); $style = $frame->get_style();
$cb = $frame->get_containing_block(); $cb = $frame->get_containing_block();
$p = $frame->find_block_parent(); $p = $frame->find_block_parent();
@ -49,10 +40,10 @@ class Block extends AbstractPositioner {
// Relative positionning // Relative positionning
if ($style->position === "relative") { if ($style->position === "relative") {
$top = $style->length_in_pt($style->top, $cb["h"]); $top = (float)$style->length_in_pt($style->top, $cb["h"]);
//$right = $style->length_in_pt($style->right, $cb["w"]); //$right = (float)$style->length_in_pt($style->right, $cb["w"]);
//$bottom = $style->length_in_pt($style->bottom, $cb["h"]); //$bottom = (float)$style->length_in_pt($style->bottom, $cb["h"]);
$left = $style->length_in_pt($style->left, $cb["w"]); $left = (float)$style->length_in_pt($style->left, $cb["w"]);
$x += $left; $x += $left;
$y += $top; $y += $top;

View File

@ -17,15 +17,11 @@ use Dompdf\FrameDecorator\AbstractFrameDecorator;
class Fixed extends AbstractPositioner class Fixed extends AbstractPositioner
{ {
function __construct(AbstractFrameDecorator $frame) /**
* @param AbstractFrameDecorator $frame
*/
function position(AbstractFrameDecorator $frame)
{ {
parent::__construct($frame);
}
function position()
{
$frame = $this->_frame;
$style = $frame->get_original_style(); $style = $frame->get_original_style();
$root = $frame->get_root(); $root = $frame->get_root();
$initialcb = $root->get_containing_block(); $initialcb = $root->get_containing_block();
@ -37,14 +33,14 @@ class Fixed extends AbstractPositioner
} }
// Compute the margins of the @page style // Compute the margins of the @page style
$margin_top = $initialcb_style->length_in_pt($initialcb_style->margin_top, $initialcb["h"]); $margin_top = (float)$initialcb_style->length_in_pt($initialcb_style->margin_top, $initialcb["h"]);
$margin_right = $initialcb_style->length_in_pt($initialcb_style->margin_right, $initialcb["w"]); $margin_right = (float)$initialcb_style->length_in_pt($initialcb_style->margin_right, $initialcb["w"]);
$margin_bottom = $initialcb_style->length_in_pt($initialcb_style->margin_bottom, $initialcb["h"]); $margin_bottom = (float)$initialcb_style->length_in_pt($initialcb_style->margin_bottom, $initialcb["h"]);
$margin_left = $initialcb_style->length_in_pt($initialcb_style->margin_left, $initialcb["w"]); $margin_left = (float)$initialcb_style->length_in_pt($initialcb_style->margin_left, $initialcb["w"]);
// The needed computed style of the element // The needed computed style of the element
$height = $style->length_in_pt($style->height, $initialcb["h"]); $height = (float)$style->length_in_pt($style->height, $initialcb["h"]);
$width = $style->length_in_pt($style->width, $initialcb["w"]); $width = (float)$style->length_in_pt($style->width, $initialcb["w"]);
$top = $style->length_in_pt($style->top, $initialcb["h"]); $top = $style->length_in_pt($style->top, $initialcb["h"]);
$right = $style->length_in_pt($style->right, $initialcb["w"]); $right = $style->length_in_pt($style->right, $initialcb["w"]);
@ -53,16 +49,15 @@ class Fixed extends AbstractPositioner
$y = $margin_top; $y = $margin_top;
if (isset($top)) { if (isset($top)) {
$y = $top + $margin_top; $y = (float)$top + $margin_top;
if ($top === "auto") { if ($top === "auto") {
$y = $margin_top; $y = $margin_top;
if (isset($bottom) && $bottom !== "auto") { if (isset($bottom) && $bottom !== "auto") {
$y = $initialcb["h"] - $bottom - $margin_bottom; $y = $initialcb["h"] - $bottom - $margin_bottom;
$margin_height = $this->_frame->get_margin_height(); if ($frame->is_auto_height()) {
if ($margin_height !== "auto") {
$y -= $margin_height;
} else {
$y -= $height; $y -= $height;
} else {
$y -= $frame->get_margin_height();
} }
} }
} }
@ -70,16 +65,15 @@ class Fixed extends AbstractPositioner
$x = $margin_left; $x = $margin_left;
if (isset($left)) { if (isset($left)) {
$x = $left + $margin_left; $x = (float)$left + $margin_left;
if ($left === "auto") { if ($left === "auto") {
$x = $margin_left; $x = $margin_left;
if (isset($right) && $right !== "auto") { if (isset($right) && $right !== "auto") {
$x = $initialcb["w"] - $right - $margin_right; $x = $initialcb["w"] - $right - $margin_right;
$margin_width = $this->_frame->get_margin_width(); if ($frame->is_auto_width()) {
if ($margin_width !== "auto") {
$x -= $margin_width;
} else {
$x -= $width; $x -= $width;
} else {
$x -= $frame->get_margin_width();
} }
} }
} }

View File

@ -10,7 +10,6 @@ namespace Dompdf\Positioner;
use Dompdf\FrameDecorator\AbstractFrameDecorator; use Dompdf\FrameDecorator\AbstractFrameDecorator;
use Dompdf\FrameDecorator\Inline as InlineFrameDecorator; use Dompdf\FrameDecorator\Inline as InlineFrameDecorator;
use Dompdf\FrameDecorator\Block as BlockFrameDecorator;
use Dompdf\Exception; use Dompdf\Exception;
/** /**
@ -21,33 +20,31 @@ use Dompdf\Exception;
class Inline extends AbstractPositioner class Inline extends AbstractPositioner
{ {
function __construct(AbstractFrameDecorator $frame) /**
{ * @param AbstractFrameDecorator $frame
parent::__construct($frame); * @throws Exception
} */
function position(AbstractFrameDecorator $frame)
//........................................................................
function position()
{ {
/** /**
* Find our nearest block level parent and access its lines property. * Find our nearest block level parent and access its lines property.
* @var BlockFrameDecorator * @var BlockFrameDecorator
*/ */
$p = $this->_frame->find_block_parent(); $p = $frame->find_block_parent();
// Debugging code: // Debugging code:
// Helpers::pre_r("\nPositioning:"); // Helpers::pre_r("\nPositioning:");
// Helpers::pre_r("Me: " . $this->_frame->get_node()->nodeName . " (" . spl_object_hash($this->_frame->get_node()) . ")"); // Helpers::pre_r("Me: " . $frame->get_node()->nodeName . " (" . spl_object_hash($frame->get_node()) . ")");
// Helpers::pre_r("Parent: " . $p->get_node()->nodeName . " (" . spl_object_hash($p->get_node()) . ")"); // Helpers::pre_r("Parent: " . $p->get_node()->nodeName . " (" . spl_object_hash($p->get_node()) . ")");
// End debugging // End debugging
if (!$p) if (!$p) {
throw new Exception("No block-level parent found. Not good."); throw new Exception("No block-level parent found. Not good.");
}
$f = $this->_frame; $f = $frame;
$cb = $f->get_containing_block(); $cb = $f->get_containing_block();
$line = $p->get_current_line_box(); $line = $p->get_current_line_box();
@ -61,13 +58,12 @@ class Inline extends AbstractPositioner
} }
} }
$f = $this->_frame; $f = $frame;
if (!$is_fixed && $f->get_parent() && if (!$is_fixed && $f->get_parent() &&
$f->get_parent() instanceof InlineFrameDecorator && $f->get_parent() instanceof InlineFrameDecorator &&
$f->is_text_node() $f->is_text_node()
) { ) {
$min_max = $f->get_reflower()->get_min_max_width(); $min_max = $f->get_reflower()->get_min_max_width();
// If the frame doesn't fit in the current line, a line break occurs // If the frame doesn't fit in the current line, a line break occurs
@ -77,6 +73,5 @@ class Inline extends AbstractPositioner
} }
$f->set_position($cb["x"] + $line->w, $line->y); $f->set_position($cb["x"] + $line->w, $line->y);
} }
} }

Some files were not shown because too many files have changed in this diff Show More