4 * @link http://dompdf.github.com/
5 * @author Benj Carson <benjcarson@digitaljunkies.ca>
6 * @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License
8 namespace Dompdf\FrameDecorator
;
14 use Dompdf\Frame\Factory
;
17 * Decorates Frames for table layout
21 class Table
extends AbstractFrameDecorator
23 public static $VALID_CHILDREN = array(
34 public static $ROW_GROUPS = array(
41 * The Cellmap object for this table. The cellmap maps table cells
42 * to rows and columns, and aids in calculating column widths.
44 * @var \Dompdf\Cellmap
49 * The minimum width of the table, in pt
53 protected $_min_width;
56 * The maximum width of the table, in pt
60 protected $_max_width;
63 * Table header rows. Each table header is duplicated when a table
71 * Table footer rows. Each table footer is duplicated when a table
81 * @param Frame $frame the frame to decorate
82 * @param Dompdf $dompdf
84 public function __construct(Frame
$frame, Dompdf
$dompdf)
86 parent
::__construct($frame, $dompdf);
87 $this->_cellmap
= new Cellmap($this);
89 if ($frame->get_style()->table_layout
=== "fixed") {
90 $this->_cellmap
->set_layout_fixed(true);
93 $this->_min_width
= null;
94 $this->_max_width
= null;
95 $this->_headers
= array();
96 $this->_footers
= array();
100 public function reset()
103 $this->_cellmap
->reset();
104 $this->_min_width
= null;
105 $this->_max_width
= null;
106 $this->_headers
= array();
107 $this->_footers
= array();
108 $this->_reflower
->reset();
111 //........................................................................
114 * split the table at $row. $row and all subsequent rows will be
115 * added to the clone. This method is overidden in order to remove
116 * frames from the cellmap properly.
118 * @param Frame $child
119 * @param bool $force_pagebreak
123 public function split(Frame
$child = null, $force_pagebreak = false)
125 if (is_null($child)) {
131 // If $child is a header or if it is the first non-header row, do
132 // not duplicate headers, simply move the table to the next page.
133 if (count($this->_headers
) && !in_array($child, $this->_headers
, true) &&
134 !in_array($child->get_prev_sibling(), $this->_headers
, true)
137 $first_header = null;
139 // Insert copies of the table headers before $child
140 foreach ($this->_headers
as $header) {
142 $new_header = $header->deep_copy();
144 if (is_null($first_header)) {
145 $first_header = $new_header;
148 $this->insert_child_before($new_header, $child);
151 parent
::split($first_header);
153 } elseif (in_array($child->get_style()->display
, self
::$ROW_GROUPS)) {
155 // Individual rows should have already been handled
156 parent
::split($child);
163 $this->_cellmap
->remove_row($iter);
164 $iter = $iter->get_next_sibling();
167 parent
::split($child);
172 * Return a copy of this frame with $node as its node
174 * @param DOMNode $node
178 public function copy(DOMNode
$node)
180 $deco = parent
::copy($node);
182 // In order to keep columns' widths through pages
183 $deco->_cellmap
->set_columns($this->_cellmap
->get_columns());
184 $deco->_cellmap
->lock_columns();
190 * Static function to locate the parent table of a frame
192 * @param Frame $frame
194 * @return Table the table that is an ancestor of $frame
196 public static function find_parent_table(Frame
$frame)
199 while ($frame = $frame->get_parent())
200 if ($frame->is_table())
207 * Return this table's Cellmap
209 * @return \Dompdf\Cellmap
211 public function get_cellmap()
213 return $this->_cellmap
;
217 * Return the minimum width of this table
221 public function get_min_width()
223 return $this->_min_width
;
227 * Return the maximum width of this table
231 public function get_max_width()
233 return $this->_max_width
;
237 * Set the minimum width of the table
239 * @param float $width the new minimum width
241 public function set_min_width($width)
243 $this->_min_width
= $width;
247 * Set the maximum width of the table
249 * @param float $width the new maximum width
251 public function set_max_width($width)
253 $this->_max_width
= $width;
257 * Restructure tree so that the table has the correct structure.
258 * Invalid children (i.e. all non-table-rows) are moved below the
261 public function normalise()
263 // Store frames generated by invalid tags and move them outside the table
264 $erroneous_frames = array();
266 $iter = $this->get_first_child();
269 $iter = $iter->get_next_sibling();
271 $display = $child->get_style()->display
;
275 if ($display === "table-row") {
276 // Add the previous anonymous row
277 $this->insert_child_before($table_row, $child);
279 $table_row->normalise();
285 // add the child to the anonymous row
286 $table_row->append_child($child);
291 if ($display === "table-row") {
296 if ($display === "table-cell") {
297 // Create an anonymous table row
298 $tr = $this->get_node()->ownerDocument
->createElement("tr");
300 $frame = new Frame($tr);
302 $css = $this->get_style()->get_stylesheet();
303 $style = $css->create_style();
304 $style->inherit($this->get_style());
306 // Lookup styles for tr tags. If the user wants styles to work
307 // better, they should make the tr explicit... I'm not going to
308 // try to guess what they intended.
309 if ($tr_style = $css->lookup("tr")) {
310 $style->merge($tr_style);
313 // Okay, I have absolutely no idea why I need this clone here, but
314 // if it's omitted, php (as of 2004-07-28) segfaults.
315 $frame->set_style(clone $style);
316 $table_row = Factory
::decorate_frame($frame, $this->_dompdf
, $this->_root
);
318 // Add the cell to the row
319 $table_row->append_child($child);
325 if (!in_array($display, self
::$VALID_CHILDREN)) {
326 $erroneous_frames[] = $child;
330 // Normalise other table parts (i.e. row groups)
331 foreach ($child->get_children() as $grandchild) {
332 if ($grandchild->get_style()->display
=== "table-row") {
333 $grandchild->normalise();
337 // Add headers and footers
338 if ($display === "table-header-group") {
339 $this->_headers
[] = $child;
340 } elseif ($display === "table-footer-group") {
341 $this->_footers
[] = $child;
346 if ($anon_row && $table_row instanceof DOMNode
) {
347 // Add the row to the table
348 $this->_frame
->append_child($table_row);
349 $table_row->normalise();
350 $this->_cellmap
->add_row();
353 foreach ($erroneous_frames as $frame) {
354 $this->move_after($frame);
358 //........................................................................
361 * Moves the specified frame and it's corresponding node outside of
364 * @param Frame $frame the frame to move
366 public function move_after(Frame
$frame)
368 $this->get_parent()->insert_child_after($frame, $this);