123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449 |
- <?php
- /**
- * @package dompdf
- * @link http://dompdf.github.com/
- * @author Benj Carson <benjcarson@digitaljunkies.ca>
- * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
- */
- /**
- * Base reflower class
- *
- * Reflower objects are responsible for determining the width and height of
- * individual frames. They also create line and page breaks as necessary.
- *
- * @access private
- * @package dompdf
- */
- abstract class Frame_Reflower {
- /**
- * Frame for this reflower
- *
- * @var Frame
- */
- protected $_frame;
- /**
- * Cached min/max size
- *
- * @var array
- */
- protected $_min_max_cache;
-
- function __construct(Frame $frame) {
- $this->_frame = $frame;
- $this->_min_max_cache = null;
- }
- function dispose() {
- clear_object($this);
- }
- /**
- * @return DOMPDF
- */
- function get_dompdf() {
- return $this->_frame->get_dompdf();
- }
- /**
- * Collapse frames margins
- * http://www.w3.org/TR/CSS2/box.html#collapsing-margins
- */
- protected function _collapse_margins() {
- $frame = $this->_frame;
- $cb = $frame->get_containing_block();
- $style = $frame->get_style();
-
- if ( !$frame->is_in_flow() ) {
- return;
- }
- $t = $style->length_in_pt($style->margin_top, $cb["h"]);
- $b = $style->length_in_pt($style->margin_bottom, $cb["h"]);
- // Handle 'auto' values
- if ( $t === "auto" ) {
- $style->margin_top = "0pt";
- $t = 0;
- }
- if ( $b === "auto" ) {
- $style->margin_bottom = "0pt";
- $b = 0;
- }
- // Collapse vertical margins:
- $n = $frame->get_next_sibling();
- if ( $n && !$n->is_block() ) {
- while ( $n = $n->get_next_sibling() ) {
- if ( $n->is_block() ) {
- break;
- }
-
- if ( !$n->get_first_child() ) {
- $n = null;
- break;
- }
- }
- }
-
- if ( $n ) {
- $n_style = $n->get_style();
- $b = max($b, $n_style->length_in_pt($n_style->margin_top, $cb["h"]));
- $n_style->margin_top = "0pt";
- $style->margin_bottom = $b."pt";
- }
- // Collapse our first child's margin
- /*$f = $this->_frame->get_first_child();
- if ( $f && !$f->is_block() ) {
- while ( $f = $f->get_next_sibling() ) {
- if ( $f->is_block() ) {
- break;
- }
-
- if ( !$f->get_first_child() ) {
- $f = null;
- break;
- }
- }
- }
- // Margin are collapsed only between block elements
- if ( $f ) {
- $f_style = $f->get_style();
- $t = max($t, $f_style->length_in_pt($f_style->margin_top, $cb["h"]));
- $style->margin_top = $t."pt";
- $f_style->margin_bottom = "0pt";
- }*/
- }
- //........................................................................
- abstract function reflow(Block_Frame_Decorator $block = null);
- //........................................................................
- // Required for table layout: Returns an array(0 => min, 1 => max, "min"
- // => min, "max" => max) of the minimum and maximum widths of this frame.
- // This provides a basic implementation. Child classes should override
- // this if necessary.
- function get_min_max_width() {
- if ( !is_null($this->_min_max_cache) ) {
- return $this->_min_max_cache;
- }
-
- $style = $this->_frame->get_style();
- // Account for margins & padding
- $dims = array($style->padding_left,
- $style->padding_right,
- $style->border_left_width,
- $style->border_right_width,
- $style->margin_left,
- $style->margin_right);
- $cb_w = $this->_frame->get_containing_block("w");
- $delta = $style->length_in_pt($dims, $cb_w);
- // Handle degenerate case
- if ( !$this->_frame->get_first_child() ) {
- return $this->_min_max_cache = array(
- $delta, $delta,
- "min" => $delta,
- "max" => $delta,
- );
- }
- $low = array();
- $high = array();
- for ( $iter = $this->_frame->get_children()->getIterator();
- $iter->valid();
- $iter->next() ) {
- $inline_min = 0;
- $inline_max = 0;
- // Add all adjacent inline widths together to calculate max width
- while ( $iter->valid() && in_array( $iter->current()->get_style()->display, Style::$INLINE_TYPES ) ) {
- $child = $iter->current();
- $minmax = $child->get_min_max_width();
- if ( in_array( $iter->current()->get_style()->white_space, array("pre", "nowrap") ) ) {
- $inline_min += $minmax["min"];
- }
- else {
- $low[] = $minmax["min"];
- }
- $inline_max += $minmax["max"];
- $iter->next();
- }
- if ( $inline_max > 0 ) $high[] = $inline_max;
- if ( $inline_min > 0 ) $low[] = $inline_min;
- if ( $iter->valid() ) {
- list($low[], $high[]) = $iter->current()->get_min_max_width();
- continue;
- }
- }
- $min = count($low) ? max($low) : 0;
- $max = count($high) ? max($high) : 0;
- // Use specified width if it is greater than the minimum defined by the
- // content. If the width is a percentage ignore it for now.
- $width = $style->width;
- if ( $width !== "auto" && !is_percent($width) ) {
- $width = $style->length_in_pt($width, $cb_w);
- if ( $min < $width ) $min = $width;
- if ( $max < $width ) $max = $width;
- }
- $min += $delta;
- $max += $delta;
- return $this->_min_max_cache = array($min, $max, "min"=>$min, "max"=>$max);
- }
- /**
- * Parses a CSS string containing quotes and escaped hex characters
- *
- * @param $string string The CSS string to parse
- * @param $single_trim
- * @return string
- */
- protected function _parse_string($string, $single_trim = false) {
- if ( $single_trim ) {
- $string = preg_replace('/^[\"\']/', "", $string);
- $string = preg_replace('/[\"\']$/', "", $string);
- }
- else {
- $string = trim($string, "'\"");
- }
-
- $string = str_replace(array("\\\n",'\\"',"\\'"),
- array("",'"',"'"), $string);
- // Convert escaped hex characters into ascii characters (e.g. \A => newline)
- $string = preg_replace_callback("/\\\\([0-9a-fA-F]{0,6})/",
- create_function('$matches',
- 'return unichr(hexdec($matches[1]));'),
- $string);
- return $string;
- }
-
- /**
- * Parses a CSS "quotes" property
- *
- * @return array|null An array of pairs of quotes
- */
- protected function _parse_quotes() {
-
- // Matches quote types
- $re = '/(\'[^\']*\')|(\"[^\"]*\")/';
-
- $quotes = $this->_frame->get_style()->quotes;
-
- // split on spaces, except within quotes
- if ( !preg_match_all($re, "$quotes", $matches, PREG_SET_ORDER) ) {
- return null;
- }
-
- $quotes_array = array();
- foreach($matches as &$_quote){
- $quotes_array[] = $this->_parse_string($_quote[0], true);
- }
-
- if ( empty($quotes_array) ) {
- $quotes_array = array('"', '"');
- }
-
- return array_chunk($quotes_array, 2);
- }
- /**
- * Parses the CSS "content" property
- *
- * @return string|null The resulting string
- */
- protected function _parse_content() {
- // Matches generated content
- $re = "/\n".
- "\s(counters?\\([^)]*\\))|\n".
- "\A(counters?\\([^)]*\\))|\n".
- "\s([\"']) ( (?:[^\"']|\\\\[\"'])+ )(?<!\\\\)\\3|\n".
- "\A([\"']) ( (?:[^\"']|\\\\[\"'])+ )(?<!\\\\)\\5|\n" .
- "\s([^\s\"']+)|\n" .
- "\A([^\s\"']+)\n".
- "/xi";
-
- $content = $this->_frame->get_style()->content;
- $quotes = $this->_parse_quotes();
-
- // split on spaces, except within quotes
- if ( !preg_match_all($re, $content, $matches, PREG_SET_ORDER) ) {
- return null;
- }
-
- $text = "";
- foreach ($matches as $match) {
-
- if ( isset($match[2]) && $match[2] !== "" ) {
- $match[1] = $match[2];
- }
-
- if ( isset($match[6]) && $match[6] !== "" ) {
- $match[4] = $match[6];
- }
- if ( isset($match[8]) && $match[8] !== "" ) {
- $match[7] = $match[8];
- }
- if ( isset($match[1]) && $match[1] !== "" ) {
-
- // counters?(...)
- $match[1] = mb_strtolower(trim($match[1]));
- // Handle counter() references:
- // http://www.w3.org/TR/CSS21/generate.html#content
- $i = mb_strpos($match[1], ")");
- if ( $i === false ) {
- continue;
- }
- $args = explode(",", mb_substr($match[1], 8, $i - 8));
- $counter_id = $args[0];
- if ( $match[1][7] === "(" ) {
- // counter(name [,style])
- if ( isset($args[1]) ) {
- $type = trim($args[1]);
- }
- else {
- $type = null;
- }
- $p = $this->_frame->lookup_counter_frame($counter_id);
-
- $text .= $p->counter_value($counter_id, $type);
- }
- else if ( $match[1][7] === "s" ) {
- // counters(name, string [,style])
- if ( isset($args[1]) ) {
- $string = $this->_parse_string(trim($args[1]));
- }
- else {
- $string = "";
- }
- if ( isset($args[2]) ) {
- $type = $args[2];
- }
- else {
- $type = null;
- }
- $p = $this->_frame->lookup_counter_frame($counter_id);
- $tmp = "";
- while ($p) {
- $tmp = $p->counter_value($counter_id, $type) . $string . $tmp;
- $p = $p->lookup_counter_frame($counter_id);
- }
- $text .= $tmp;
- }
- else {
- // countertops?
- continue;
- }
- }
- else if ( isset($match[4]) && $match[4] !== "" ) {
- // String match
- $text .= $this->_parse_string($match[4]);
- }
- else if ( isset($match[7]) && $match[7] !== "" ) {
- // Directive match
- if ( $match[7] === "open-quote" ) {
- // FIXME: do something here
- $text .= $quotes[0][0];
- }
- else if ( $match[7] === "close-quote" ) {
- // FIXME: do something else here
- $text .= $quotes[0][1];
- }
- else if ( $match[7] === "no-open-quote" ) {
- // FIXME:
- }
- else if ( $match[7] === "no-close-quote" ) {
- // FIXME:
- }
- else if ( mb_strpos($match[7],"attr(") === 0 ) {
- $i = mb_strpos($match[7],")");
- if ( $i === false ) {
- continue;
- }
- $attr = mb_substr($match[7], 5, $i - 5);
- if ( $attr == "" ) {
- continue;
- }
-
- $text .= $this->_frame->get_parent()->get_node()->getAttribute($attr);
- }
- else {
- continue;
- }
- }
- }
- return $text;
- }
-
- /**
- * Sets the generated content of a generated frame
- */
- protected function _set_content(){
- $frame = $this->_frame;
- $style = $frame->get_style();
-
- if ( $style->counter_reset && ($reset = $style->counter_reset) !== "none" ) {
- $vars = preg_split('/\s+/', trim($reset), 2);
- $frame->reset_counter($vars[0], isset($vars[1]) ? $vars[1] : 0);
- }
-
- if ( $style->counter_increment && ($increment = $style->counter_increment) !== "none" ) {
- $frame->increment_counters($increment);
- }
-
- if ( $style->content && !$frame->get_first_child() && $frame->get_node()->nodeName === "dompdf_generated" ) {
- $content = $this->_parse_content();
- $node = $frame->get_node()->ownerDocument->createTextNode($content);
-
- $new_style = $style->get_stylesheet()->create_style();
- $new_style->inherit($style);
-
- $new_frame = new Frame($node);
- $new_frame->set_style($new_style);
-
- Frame_Factory::decorate_frame($new_frame, $frame->get_dompdf(), $frame->get_root());
- $frame->append_child($new_frame);
- }
- }
- }
|