icingaweb2/library/vendor/dompdf/include/table_frame_reflower.cls.php
Matthias Jentsch 124b42c9f1 Remove tcpdf library and use dompdf instead
Remove tcpdf because of improper CSS support and use Dompdf instead, to be able
to generate pdfs that look similar to the html views

refs #4356
2014-02-12 12:11:01 +01:00

568 lines
16 KiB
PHP

<?php
/**
* @package dompdf
* @link http://www.dompdf.com/
* @author Benj Carson <benjcarson@digitaljunkies.ca>
* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
* @version $Id: table_frame_reflower.cls.php 465 2012-01-30 21:58:11Z fabien.menager $
*/
/**
* Reflows tables
*
* @access private
* @package dompdf
*/
class Table_Frame_Reflower extends Frame_Reflower {
/**
* Cache of results between call to get_min_max_width and assign_widths
*
* @var array
*/
protected $_state;
function __construct(Table_Frame_Decorator $frame) {
$this->_state = null;
parent::__construct($frame);
}
/**
* State is held here so it needs to be reset along with the decorator
*/
function reset() {
$this->_state = null;
$this->_min_max_cache = null;
}
//........................................................................
protected function _assign_widths() {
$style = $this->_frame->get_style();
// Find the min/max width of the table and sort the columns into
// absolute/percent/auto arrays
$min_width = $this->_state["min_width"];
$max_width = $this->_state["max_width"];
$percent_used = $this->_state["percent_used"];
$absolute_used = $this->_state["absolute_used"];
$auto_min = $this->_state["auto_min"];
$absolute =& $this->_state["absolute"];
$percent =& $this->_state["percent"];
$auto =& $this->_state["auto"];
// Determine the actual width of the table
$cb = $this->_frame->get_containing_block();
$columns =& $this->_frame->get_cellmap()->get_columns();
$width = $style->width;
// Calculate padding & border fudge factor
$left = $style->margin_left;
$right = $style->margin_right;
$centered = ( $left === "auto" && $right === "auto" );
$left = $left === "auto" ? 0 : $style->length_in_pt($left, $cb["w"]);
$right = $right === "auto" ? 0 : $style->length_in_pt($right, $cb["w"]);
$delta = $left + $right;
if ( !$centered ) {
$delta += $style->length_in_pt(array(
$style->padding_left,
$style->border_left_width,
$style->border_right_width,
$style->padding_right),
$cb["w"]);
}
$min_table_width = $style->length_in_pt( $style->min_width, $cb["w"] - $delta );
// min & max widths already include borders & padding
$min_width -= $delta;
$max_width -= $delta;
if ( $width !== "auto" ) {
$preferred_width = $style->length_in_pt($width, $cb["w"]) - $delta;
if ( $preferred_width < $min_table_width )
$preferred_width = $min_table_width;
if ( $preferred_width > $min_width )
$width = $preferred_width;
else
$width = $min_width;
} else {
if ( $max_width + $delta < $cb["w"] )
$width = $max_width;
else if ( $cb["w"] - $delta > $min_width )
$width = $cb["w"] - $delta;
else
$width = $min_width;
if ( $width < $min_table_width )
$width = $min_table_width;
}
// Store our resolved width
$style->width = $width;
$cellmap = $this->_frame->get_cellmap();
if ( $cellmap->is_columns_locked() ) {
return;
}
// If the whole table fits on the page, then assign each column it's max width
if ( $width == $max_width ) {
foreach (array_keys($columns) as $i)
$cellmap->set_column_width($i, $columns[$i]["max-width"]);
return;
}
// Determine leftover and assign it evenly to all columns
if ( $width > $min_width ) {
// We have four cases to deal with:
//
// 1. All columns are auto--no widths have been specified. In this
// case we distribute extra space across all columns weighted by max-width.
//
// 2. Only absolute widths have been specified. In this case we
// distribute any extra space equally among 'width: auto' columns, or all
// columns if no auto columns have been specified.
//
// 3. Only percentage widths have been specified. In this case we
// normalize the percentage values and distribute any remaining % to
// width: auto columns. We then proceed to assign widths as fractions
// of the table width.
//
// 4. Both absolute and percentage widths have been specified.
// Case 1:
if ( $absolute_used == 0 && $percent_used == 0 ) {
$increment = $width - $min_width;
foreach (array_keys($columns) as $i)
$cellmap->set_column_width($i, $columns[$i]["min-width"] + $increment * ($columns[$i]["max-width"] / $max_width));
return;
}
// Case 2
if ( $absolute_used > 0 && $percent_used == 0 ) {
if ( count($auto) > 0 )
$increment = ($width - $auto_min - $absolute_used) / count($auto);
// Use the absolutely specified width or the increment
foreach (array_keys($columns) as $i) {
if ( $columns[$i]["absolute"] > 0 && count($auto) )
$cellmap->set_column_width($i, $columns[$i]["min-width"]);
else if ( count($auto) )
$cellmap->set_column_width($i, $columns[$i]["min-width"] + $increment);
else {
// All absolute columns
$increment = ($width - $absolute_used) * $columns[$i]["absolute"] / $absolute_used;
$cellmap->set_column_width($i, $columns[$i]["min-width"] + $increment);
}
}
return;
}
// Case 3:
if ( $absolute_used == 0 && $percent_used > 0 ) {
$scale = null;
$remaining = null;
// Scale percent values if the total percentage is > 100, or if all
// values are specified as percentages.
if ( $percent_used > 100 || count($auto) == 0)
$scale = 100 / $percent_used;
else
$scale = 1;
// Account for the minimum space used by the unassigned auto columns
$used_width = $auto_min;
foreach ($percent as $i) {
$columns[$i]["percent"] *= $scale;
$slack = $width - $used_width;
$w = min($columns[$i]["percent"] * $width/100, $slack);
if ( $w < $columns[$i]["min-width"] )
$w = $columns[$i]["min-width"];
$cellmap->set_column_width($i, $w);
$used_width += $w;
}
// This works because $used_width includes the min-width of each
// unassigned column
if ( count($auto) > 0 ) {
$increment = ($width - $used_width) / count($auto);
foreach ($auto as $i)
$cellmap->set_column_width($i, $columns[$i]["min-width"] + $increment);
}
return;
}
// Case 4:
// First-come, first served
if ( $absolute_used > 0 && $percent_used > 0 ) {
$used_width = $auto_min;
foreach ($absolute as $i) {
$cellmap->set_column_width($i, $columns[$i]["min-width"]);
$used_width += $columns[$i]["min-width"];
}
// Scale percent values if the total percentage is > 100 or there
// are no auto values to take up slack
if ( $percent_used > 100 || count($auto) == 0 )
$scale = 100 / $percent_used;
else
$scale = 1;
$remaining_width = $width - $used_width;
foreach ($percent as $i) {
$slack = $remaining_width - $used_width;
$columns[$i]["percent"] *= $scale;
$w = min($columns[$i]["percent"] * $remaining_width / 100, $slack);
if ( $w < $columns[$i]["min-width"] )
$w = $columns[$i]["min-width"];
$columns[$i]["used-width"] = $w;
$used_width += $w;
}
if ( count($auto) > 0 ) {
$increment = ($width - $used_width) / count($auto);
foreach ($auto as $i)
$cellmap->set_column_width($i, $columns[$i]["min-width"] + $increment);
}
return;
}
} else { // we are over constrained
// Each column gets its minimum width
foreach (array_keys($columns) as $i)
$cellmap->set_column_width($i, $columns[$i]["min-width"]);
}
}
//........................................................................
// Determine the frame's height based on min/max height
protected function _calculate_height() {
$style = $this->_frame->get_style();
$height = $style->height;
$cellmap = $this->_frame->get_cellmap();
$cellmap->assign_frame_heights();
$rows = $cellmap->get_rows();
// Determine our content height
$content_height = 0;
foreach ( $rows as $r )
$content_height += $r["height"];
$cb = $this->_frame->get_containing_block();
if ( !($style->overflow === "visible" ||
($style->overflow === "hidden" && $height === "auto")) ) {
// Only handle min/max height if the height is independent of the frame's content
$min_height = $style->min_height;
$max_height = $style->max_height;
if ( isset($cb["h"]) ) {
$min_height = $style->length_in_pt($min_height, $cb["h"]);
$max_height = $style->length_in_pt($max_height, $cb["h"]);
} else if ( isset($cb["w"]) ) {
if ( mb_strpos($min_height, "%") !== false )
$min_height = 0;
else
$min_height = $style->length_in_pt($min_height, $cb["w"]);
if ( mb_strpos($max_height, "%") !== false )
$max_height = "none";
else
$max_height = $style->length_in_pt($max_height, $cb["w"]);
}
if ( $max_height !== "none" && $min_height > $max_height )
// Swap 'em
list($max_height, $min_height) = array($min_height, $max_height);
if ( $max_height !== "none" && $height > $max_height )
$height = $max_height;
if ( $height < $min_height )
$height = $min_height;
} else {
// Use the content height or the height value, whichever is greater
if ( $height !== "auto" ) {
$height = $style->length_in_pt($height, $cb["h"]);
if ( $height <= $content_height )
$height = $content_height;
else
$cellmap->set_frame_heights($height,$content_height);
} else
$height = $content_height;
}
return $height;
}
//........................................................................
function reflow(Frame_Decorator $block = null) {
/**
* @var Table_Frame_Decorator
*/
$frame = $this->_frame;
// Check if a page break is forced
$page = $frame->get_root();
$page->check_forced_page_break($frame);
// Bail if the page is full
if ( $page->is_full() )
return;
// Let the page know that we're reflowing a table so that splits
// are suppressed (simply setting page-break-inside: avoid won't
// work because we may have an arbitrary number of block elements
// inside tds.)
$page->table_reflow_start();
// Collapse vertical margins, if required
$this->_collapse_margins();
$frame->position();
// Table layout algorithm:
// http://www.w3.org/TR/CSS21/tables.html#auto-table-layout
if ( is_null($this->_state) )
$this->get_min_max_width();
$cb = $frame->get_containing_block();
$style = $frame->get_style();
// This is slightly inexact, but should be okay. Add half the
// border-spacing to the table as padding. The other half is added to
// the cells themselves.
if ( $style->border_collapse === "separate" ) {
list($h, $v) = $style->border_spacing;
$v = $style->length_in_pt($v) / 2;
$h = $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;
}
$this->_assign_widths();
// Adjust left & right margins, if they are auto
$width = $style->width;
$left = $style->margin_left;
$right = $style->margin_right;
$diff = $cb["w"] - $width;
if ( $left === "auto" && $right === "auto" ) {
if ( $diff < 0 ) {
$left = 0;
$right = $diff;
}
else {
$left = $right = $diff / 2;
}
$style->margin_left = "$left pt";
$style->margin_right = "$right pt";
} else {
if ( $left === "auto" ) {
$left = $style->length_in_pt($cb["w"] - $right - $width, $cb["w"]);
}
if ( $right === "auto" ) {
$left = $style->length_in_pt($left, $cb["w"]);
}
}
list($x, $y) = $frame->get_position();
// Determine the content edge
$content_x = $x + $left + $style->length_in_pt(array($style->padding_left,
$style->border_left_width), $cb["w"]);
$content_y = $y + $style->length_in_pt(array($style->margin_top,
$style->border_top_width,
$style->padding_top), $cb["h"]);
if ( isset($cb["h"]) )
$h = $cb["h"];
else
$h = null;
$cellmap = $frame->get_cellmap();
$col =& $cellmap->get_column(0);
$col["x"] = $content_x;
$row =& $cellmap->get_row(0);
$row["y"] = $content_y;
$cellmap->assign_x_positions();
// Set the containing block of each child & reflow
foreach ( $frame->get_children() as $child ) {
// Bail if the page is full
if ( !$page->in_nested_table() && $page->is_full() )
break;
$child->set_containing_block($content_x, $content_y, $width, $h);
$child->reflow();
if ( !$page->in_nested_table() )
// Check if a split has occured
$page->check_page_break($child);
}
// Assign heights to our cells:
$style->height = $this->_calculate_height();
if ( $style->border_collapse === "collapse" ) {
// Unset our borders because our cells are now using them
$style->border_style = "none";
}
$page->table_reflow_end();
// Debugging:
//echo ($this->_frame->get_cellmap());
if ( $block && $style->float === "none" && $frame->is_in_flow() ) {
$block->add_frame_to_line($frame);
$block->add_line();
}
}
//........................................................................
function get_min_max_width() {
if ( !is_null($this->_min_max_cache) )
return $this->_min_max_cache;
$style = $this->_frame->get_style();
$this->_frame->normalise();
// Add the cells to the cellmap (this will calcluate column widths as
// frames are added)
$this->_frame->get_cellmap()->add_frame($this->_frame);
// Find the min/max width of the table and sort the columns into
// absolute/percent/auto arrays
$this->_state = array();
$this->_state["min_width"] = 0;
$this->_state["max_width"] = 0;
$this->_state["percent_used"] = 0;
$this->_state["absolute_used"] = 0;
$this->_state["auto_min"] = 0;
$this->_state["absolute"] = array();
$this->_state["percent"] = array();
$this->_state["auto"] = array();
$columns =& $this->_frame->get_cellmap()->get_columns();
foreach (array_keys($columns) as $i) {
$this->_state["min_width"] += $columns[$i]["min-width"];
$this->_state["max_width"] += $columns[$i]["max-width"];
if ( $columns[$i]["absolute"] > 0 ) {
$this->_state["absolute"][] = $i;
$this->_state["absolute_used"] += $columns[$i]["absolute"];
} else if ( $columns[$i]["percent"] > 0 ) {
$this->_state["percent"][] = $i;
$this->_state["percent_used"] += $columns[$i]["percent"];
} else {
$this->_state["auto"][] = $i;
$this->_state["auto_min"] += $columns[$i]["min-width"];
}
}
// Account for margins & padding
$dims = array($style->border_left_width,
$style->border_right_width,
$style->padding_left,
$style->padding_right,
$style->margin_left,
$style->margin_right);
if ( $style->border_collapse !== "collapse" )
list($dims[]) = $style->border_spacing;
$delta = $style->length_in_pt($dims, $this->_frame->get_containing_block("w"));
$this->_state["min_width"] += $delta;
$this->_state["max_width"] += $delta;
return $this->_min_max_cache = array(
$this->_state["min_width"],
$this->_state["max_width"],
"min" => $this->_state["min_width"],
"max" => $this->_state["max_width"],
);
}
}