Cellmap

Maps table cells to the table grid.

Defined (1)

The class is defined in the following location(s).

/lib/dompdf/include/cellmap.cls.php  
  1. class Cellmap { 
  2.  
  3. /** 
  4. * Border style weight lookup for collapsed border resolution. 
  5. * @var array 
  6. */ 
  7. static protected $_BORDER_STYLE_SCORE = array( 
  8. "inset" => 1,  
  9. "groove" => 2,  
  10. "outset" => 3,  
  11. "ridge" => 4,  
  12. "dotted" => 5,  
  13. "dashed" => 6,  
  14. "solid" => 7,  
  15. "double" => 8,  
  16. "hidden" => 9,  
  17. "none" => 0,  
  18. ); 
  19.  
  20. /** 
  21. * The table object this cellmap is attached to. 
  22. * @var Table_Frame_Decorator 
  23. */ 
  24. protected $_table; 
  25.  
  26. /** 
  27. * The total number of rows in the table 
  28. * @var int 
  29. */ 
  30. protected $_num_rows; 
  31.  
  32. /** 
  33. * The total number of columns in the table 
  34. * @var int 
  35. */ 
  36. protected $_num_cols; 
  37.  
  38. /** 
  39. * 2D array mapping <row, column> to frames 
  40. * @var Frame[][] 
  41. */ 
  42. protected $_cells; 
  43.  
  44. /** 
  45. * 1D array of column dimensions 
  46. * @var array 
  47. */ 
  48. protected $_columns; 
  49.  
  50. /** 
  51. * 1D array of row dimensions 
  52. * @var array 
  53. */ 
  54. protected $_rows; 
  55.  
  56. /** 
  57. * 2D array of border specs 
  58. * @var array 
  59. */ 
  60. protected $_borders; 
  61.  
  62. /** 
  63. * 1D Array mapping frames to (multiple) <row, col> pairs, keyed on frame_id. 
  64. * @var Frame[] 
  65. */ 
  66. protected $_frames; 
  67.  
  68. /** 
  69. * Current column when adding cells, 0-based 
  70. * @var int 
  71. */ 
  72. private $__col; 
  73.  
  74. /** 
  75. * Current row when adding cells, 0-based 
  76. * @var int 
  77. */ 
  78. private $__row; 
  79.  
  80. /** 
  81. * Tells wether the columns' width can be modified 
  82. * @var bool 
  83. */ 
  84. private $_columns_locked = false; 
  85.  
  86. /** 
  87. * Tells wether the table has table-layout:fixed 
  88. * @var bool 
  89. */ 
  90. private $_fixed_layout = false; 
  91.  
  92. //........................................................................ 
  93.  
  94. function __construct(Table_Frame_Decorator $table) { 
  95. $this->_table = $table; 
  96. $this->reset(); 
  97.  
  98. function __destruct() { 
  99. clear_object($this); 
  100. //........................................................................ 
  101.  
  102. function reset() { 
  103. $this->_num_rows = 0; 
  104. $this->_num_cols = 0; 
  105.  
  106. $this->_cells = array(); 
  107. $this->_frames = array(); 
  108.  
  109. if ( !$this->_columns_locked ) { 
  110. $this->_columns = array(); 
  111.  
  112. $this->_rows = array(); 
  113.  
  114. $this->_borders = array(); 
  115.  
  116. $this->__col = $this->__row = 0; 
  117.  
  118. //........................................................................ 
  119.  
  120. function lock_columns() {  
  121. $this->_columns_locked = true;  
  122.  
  123. function is_columns_locked() { 
  124. return $this->_columns_locked; 
  125.  
  126. function set_layout_fixed($fixed) {  
  127. $this->_fixed_layout = $fixed;  
  128.  
  129. function is_layout_fixed() { 
  130. return $this->_fixed_layout; 
  131.  
  132. function get_num_rows() { return $this->_num_rows; } 
  133. function get_num_cols() { return $this->_num_cols; } 
  134.  
  135. function &get_columns() { 
  136. return $this->_columns; 
  137.  
  138. function set_columns($columns) { 
  139. $this->_columns = $columns; 
  140.  
  141. function &get_column($i) { 
  142. if ( !isset($this->_columns[$i]) ) { 
  143. $this->_columns[$i] = array( 
  144. "x" => 0,  
  145. "min-width" => 0,  
  146. "max-width" => 0,  
  147. "used-width" => null,  
  148. "absolute" => 0,  
  149. "percent" => 0,  
  150. "auto" => true,  
  151. ); 
  152.  
  153. return $this->_columns[$i]; 
  154.  
  155. function &get_rows() { 
  156. return $this->_rows; 
  157.  
  158. function &get_row($j) { 
  159. if ( !isset($this->_rows[$j]) ) { 
  160. $this->_rows[$j] = array( 
  161. "y" => 0,  
  162. "first-column" => 0,  
  163. "height" => null,  
  164. ); 
  165.  
  166. return $this->_rows[$j]; 
  167.  
  168. function get_border($i, $j, $h_v, $prop = null) { 
  169. if ( !isset($this->_borders[$i][$j][$h_v]) ) { 
  170. $this->_borders[$i][$j][$h_v] = array( 
  171. "width" => 0,  
  172. "style" => "solid",  
  173. "color" => "black",  
  174. ); 
  175.  
  176. if ( isset($prop) ) { 
  177. return $this->_borders[$i][$j][$h_v][$prop]; 
  178.  
  179. return $this->_borders[$i][$j][$h_v]; 
  180.  
  181. function get_border_properties($i, $j) { 
  182. return array( 
  183. "top" => $this->get_border($i, $j, "horizontal"),  
  184. "right" => $this->get_border($i, $j+1, "vertical"),  
  185. "bottom" => $this->get_border($i+1, $j, "horizontal"),  
  186. "left" => $this->get_border($i, $j, "vertical"),  
  187. ); 
  188.  
  189. //........................................................................ 
  190.  
  191. function get_spanned_cells(Frame $frame) { 
  192. $key = $frame->get_id(); 
  193.  
  194. if ( !isset($this->_frames[$key]) ) { 
  195. throw new DOMPDF_Exception("Frame not found in cellmap"); 
  196.  
  197. return $this->_frames[$key]; 
  198.  
  199.  
  200. function frame_exists_in_cellmap(Frame $frame) { 
  201. $key = $frame->get_id(); 
  202. return isset($this->_frames[$key]); 
  203.  
  204. function get_frame_position(Frame $frame) { 
  205. global $_dompdf_warnings; 
  206.  
  207. $key = $frame->get_id(); 
  208.  
  209. if ( !isset($this->_frames[$key]) ) { 
  210. throw new DOMPDF_Exception("Frame not found in cellmap"); 
  211.  
  212. $col = $this->_frames[$key]["columns"][0]; 
  213. $row = $this->_frames[$key]["rows"][0]; 
  214.  
  215. if ( !isset($this->_columns[$col])) { 
  216. $_dompdf_warnings[] = "Frame not found in columns array. Check your table layout for missing or extra TDs."; 
  217. $x = 0; 
  218. else { 
  219. $x = $this->_columns[$col]["x"]; 
  220.  
  221. if ( !isset($this->_rows[$row])) { 
  222. $_dompdf_warnings[] = "Frame not found in row array. Check your table layout for missing or extra TDs."; 
  223. $y = 0; 
  224. else { 
  225. $y = $this->_rows[$row]["y"]; 
  226.  
  227. return array($x, $y, "x" => $x, "y" => $y); 
  228.  
  229. function get_frame_width(Frame $frame) { 
  230. $key = $frame->get_id(); 
  231.  
  232. if ( !isset($this->_frames[$key]) ) { 
  233. throw new DOMPDF_Exception("Frame not found in cellmap"); 
  234.  
  235. $cols = $this->_frames[$key]["columns"]; 
  236. $w = 0; 
  237. foreach ($cols as $i) { 
  238. $w += $this->_columns[$i]["used-width"]; 
  239.  
  240. return $w; 
  241.  
  242. function get_frame_height(Frame $frame) { 
  243. $key = $frame->get_id(); 
  244.  
  245. if ( !isset($this->_frames[$key]) ) { 
  246. throw new DOMPDF_Exception("Frame not found in cellmap"); 
  247.  
  248. $rows = $this->_frames[$key]["rows"]; 
  249. $h = 0; 
  250. foreach ($rows as $i) { 
  251. if ( !isset($this->_rows[$i]) ) { 
  252. throw new Exception("The row #$i could not be found, please file an issue in the tracker with the HTML code"); 
  253.  
  254. $h += $this->_rows[$i]["height"]; 
  255.  
  256. return $h; 
  257.  
  258.  
  259. //........................................................................ 
  260.  
  261. function set_column_width($j, $width) { 
  262. if ( $this->_columns_locked ) { 
  263. return; 
  264.  
  265. $col =& $this->get_column($j); 
  266. $col["used-width"] = $width; 
  267. $next_col =& $this->get_column($j+1); 
  268. $next_col["x"] = $next_col["x"] + $width; 
  269.  
  270. function set_row_height($i, $height) { 
  271. $row =& $this->get_row($i); 
  272.  
  273. if ( $row["height"] !== null && $height <= $row["height"] ) { 
  274. return; 
  275.  
  276. $row["height"] = $height; 
  277. $next_row =& $this->get_row($i+1); 
  278. $next_row["y"] = $row["y"] + $height; 
  279.  
  280.  
  281. //........................................................................ 
  282.  
  283.  
  284. protected function _resolve_border($i, $j, $h_v, $border_spec) { 
  285. $n_width = $border_spec["width"]; 
  286. $n_style = $border_spec["style"]; 
  287.  
  288. if ( !isset($this->_borders[$i][$j][$h_v]) ) { 
  289. $this->_borders[$i][$j][$h_v] = $border_spec; 
  290. return $this->_borders[$i][$j][$h_v]["width"]; 
  291.  
  292. $border = &$this->_borders[$i][$j][$h_v]; 
  293.  
  294. $o_width = $border["width"]; 
  295. $o_style = $border["style"]; 
  296.  
  297. if ( ($n_style === "hidden" || 
  298. $n_width > $o_width || 
  299. $o_style === "none") 
  300.  
  301. or 
  302.  
  303. ($o_width == $n_width && 
  304. in_array($n_style, self::$_BORDER_STYLE_SCORE) && 
  305. self::$_BORDER_STYLE_SCORE[ $n_style ] > self::$_BORDER_STYLE_SCORE[ $o_style ]) ) { 
  306. $border = $border_spec; 
  307.  
  308. return $border["width"]; 
  309.  
  310. //........................................................................ 
  311.  
  312. function add_frame(Frame $frame) { 
  313.  
  314. $style = $frame->get_style(); 
  315. $display = $style->display; 
  316.  
  317. $collapse = $this->_table->get_style()->border_collapse == "collapse"; 
  318.  
  319. // Recursively add the frames within tables, table-row-groups and table-rows 
  320. if ( $display === "table-row" || 
  321. $display === "table" || 
  322. $display === "inline-table" || 
  323. in_array($display, Table_Frame_Decorator::$ROW_GROUPS) ) { 
  324.  
  325. $start_row = $this->__row; 
  326. foreach ( $frame->get_children() as $child ) { 
  327. $this->add_frame( $child ); 
  328.  
  329. if ( $display === "table-row" ) { 
  330. $this->add_row(); 
  331.  
  332. $num_rows = $this->__row - $start_row - 1; 
  333. $key = $frame->get_id(); 
  334.  
  335. // Row groups always span across the entire table 
  336. $this->_frames[$key]["columns"] = range(0, max(0, $this->_num_cols-1)); 
  337. $this->_frames[$key]["rows"] = range($start_row, max(0, $this->__row - 1)); 
  338. $this->_frames[$key]["frame"] = $frame; 
  339.  
  340. if ( $display !== "table-row" && $collapse ) { 
  341.  
  342. $bp = $style->get_border_properties(); 
  343.  
  344. // Resolve the borders 
  345. for ( $i = 0; $i < $num_rows+1; $i++) { 
  346. $this->_resolve_border($start_row + $i, 0, "vertical", $bp["left"]); 
  347. $this->_resolve_border($start_row + $i, $this->_num_cols, "vertical", $bp["right"]); 
  348.  
  349. for ( $j = 0; $j < $this->_num_cols; $j++) { 
  350. $this->_resolve_border($start_row, $j, "horizontal", $bp["top"]); 
  351. $this->_resolve_border($this->__row, $j, "horizontal", $bp["bottom"]); 
  352.  
  353.  
  354. return; 
  355.  
  356. $node = $frame->get_node(); 
  357.  
  358. // Determine where this cell is going 
  359. $colspan = $node->getAttribute("colspan"); 
  360. $rowspan = $node->getAttribute("rowspan"); 
  361.  
  362. if ( !$colspan ) { 
  363. $colspan = 1; 
  364. $node->setAttribute("colspan", 1); 
  365.  
  366. if ( !$rowspan ) { 
  367. $rowspan = 1; 
  368. $node->setAttribute("rowspan", 1); 
  369. $key = $frame->get_id(); 
  370.  
  371. $bp = $style->get_border_properties(); 
  372.  
  373.  
  374. // Add the frame to the cellmap 
  375. $max_left = $max_right = 0; 
  376.  
  377. // Find the next available column (fix by Ciro Mondueri) 
  378. $ac = $this->__col; 
  379. while ( isset($this->_cells[$this->__row][$ac]) ) { 
  380. $ac++; 
  381.  
  382. $this->__col = $ac; 
  383.  
  384. // Rows: 
  385. for ( $i = 0; $i < $rowspan; $i++ ) { 
  386. $row = $this->__row + $i; 
  387.  
  388. $this->_frames[$key]["rows"][] = $row; 
  389.  
  390. for ( $j = 0; $j < $colspan; $j++) { 
  391. $this->_cells[$row][$this->__col + $j] = $frame; 
  392.  
  393. if ( $collapse ) { 
  394. // Resolve vertical borders 
  395. $max_left = max($max_left, $this->_resolve_border($row, $this->__col, "vertical", $bp["left"])); 
  396. $max_right = max($max_right, $this->_resolve_border($row, $this->__col + $colspan, "vertical", $bp["right"])); 
  397.  
  398. $max_top = $max_bottom = 0; 
  399.  
  400. // Columns: 
  401. for ( $j = 0; $j < $colspan; $j++ ) { 
  402. $col = $this->__col + $j; 
  403. $this->_frames[$key]["columns"][] = $col; 
  404.  
  405. if ( $collapse ) { 
  406. // Resolve horizontal borders 
  407. $max_top = max($max_top, $this->_resolve_border($this->__row, $col, "horizontal", $bp["top"])); 
  408. $max_bottom = max($max_bottom, $this->_resolve_border($this->__row + $rowspan, $col, "horizontal", $bp["bottom"])); 
  409.  
  410. $this->_frames[$key]["frame"] = $frame; 
  411.  
  412. // Handle seperated border model 
  413. if ( !$collapse ) { 
  414. list($h, $v) = $this->_table->get_style()->border_spacing; 
  415.  
  416. // Border spacing is effectively a margin between cells 
  417. $v = $style->length_in_pt($v) / 2; 
  418. $h = $style->length_in_pt($h) / 2; 
  419. $style->margin = "$v $h"; 
  420.  
  421. // The additional 1/2 width gets added to the table proper 
  422. else { 
  423. // Drop the frame's actual border 
  424. $style->border_left_width = $max_left / 2; 
  425. $style->border_right_width = $max_right / 2; 
  426. $style->border_top_width = $max_top / 2; 
  427. $style->border_bottom_width = $max_bottom / 2; 
  428. $style->margin = "none"; 
  429.  
  430. if ( !$this->_columns_locked ) { 
  431. // Resolve the frame's width 
  432. if ( $this->_fixed_layout ) { 
  433. list($frame_min, $frame_max) = array(0, 10e-10); 
  434. else { 
  435. list($frame_min, $frame_max) = $frame->get_min_max_width(); 
  436.  
  437. $width = $style->width; 
  438.  
  439. $val = null; 
  440. if ( is_percent($width) ) { 
  441. $var = "percent"; 
  442. $val = (float)rtrim($width, "% ") / $colspan; 
  443. else if ( $width !== "auto" ) { 
  444. $var = "absolute"; 
  445. $val = $style->length_in_pt($frame_min) / $colspan; 
  446.  
  447. $min = 0; 
  448. $max = 0; 
  449. for ( $cs = 0; $cs < $colspan; $cs++ ) { 
  450.  
  451. // Resolve the frame's width(s) with other cells 
  452. $col =& $this->get_column( $this->__col + $cs ); 
  453.  
  454. // Note: $var is either 'percent' or 'absolute'. We compare the 
  455. // requested percentage or absolute values with the existing widths 
  456. // and adjust accordingly. 
  457. if ( isset($var) && $val > $col[$var] ) { 
  458. $col[$var] = $val; 
  459. $col["auto"] = false; 
  460.  
  461. $min += $col["min-width"]; 
  462. $max += $col["max-width"]; 
  463.  
  464. if ( $frame_min > $min ) { 
  465. // The frame needs more space. Expand each sub-column 
  466. // FIXME try to avoid putting this dummy value when table-layout:fixed 
  467. $inc = ($this->is_layout_fixed() ? 10e-10 : ($frame_min - $min) / $colspan); 
  468. for ($c = 0; $c < $colspan; $c++) { 
  469. $col =& $this->get_column($this->__col + $c); 
  470. $col["min-width"] += $inc; 
  471.  
  472. if ( $frame_max > $max ) { 
  473. // FIXME try to avoid putting this dummy value when table-layout:fixed 
  474. $inc = ($this->is_layout_fixed() ? 10e-10 : ($frame_max - $max) / $colspan); 
  475. for ($c = 0; $c < $colspan; $c++) { 
  476. $col =& $this->get_column($this->__col + $c); 
  477. $col["max-width"] += $inc; 
  478.  
  479. $this->__col += $colspan; 
  480. if ( $this->__col > $this->_num_cols ) 
  481. $this->_num_cols = $this->__col; 
  482.  
  483.  
  484. //........................................................................ 
  485.  
  486. function add_row() { 
  487.  
  488. $this->__row++; 
  489. $this->_num_rows++; 
  490.  
  491. // Find the next available column 
  492. $i = 0; 
  493. while ( isset($this->_cells[$this->__row][$i]) ) { 
  494. $i++; 
  495.  
  496. $this->__col = $i; 
  497.  
  498.  
  499. //........................................................................ 
  500.  
  501. /** 
  502. * Remove a row from the cellmap. 
  503. * @param Frame 
  504. */ 
  505. function remove_row(Frame $row) { 
  506.  
  507. $key = $row->get_id(); 
  508. if ( !isset($this->_frames[$key]) ) { 
  509. return; // Presumably this row has alredy been removed 
  510.  
  511. $this->_row = $this->_num_rows--; 
  512.  
  513. $rows = $this->_frames[$key]["rows"]; 
  514. $columns = $this->_frames[$key]["columns"]; 
  515.  
  516. // Remove all frames from this row 
  517. foreach ( $rows as $r ) { 
  518. foreach ( $columns as $c ) { 
  519. if ( isset($this->_cells[$r][$c]) ) { 
  520. $id = $this->_cells[$r][$c]->get_id(); 
  521.  
  522. $this->_frames[$id] = null; 
  523. unset($this->_frames[$id]); 
  524.  
  525. $this->_cells[$r][$c] = null; 
  526. unset($this->_cells[$r][$c]); 
  527.  
  528. $this->_rows[$r] = null; 
  529. unset($this->_rows[$r]); 
  530.  
  531. $this->_frames[$key] = null; 
  532. unset($this->_frames[$key]); 
  533.  
  534.  
  535. /** 
  536. * Remove a row group from the cellmap. 
  537. * @param Frame $group The group to remove 
  538. */ 
  539. function remove_row_group(Frame $group) { 
  540.  
  541. $key = $group->get_id(); 
  542. if ( !isset($this->_frames[$key]) ) { 
  543. return; // Presumably this row has alredy been removed 
  544.  
  545. $iter = $group->get_first_child(); 
  546. while ($iter) { 
  547. $this->remove_row($iter); 
  548. $iter = $iter->get_next_sibling(); 
  549.  
  550. $this->_frames[$key] = null; 
  551. unset($this->_frames[$key]); 
  552.  
  553. /** 
  554. * Update a row group after rows have been removed 
  555. * @param Frame $group The group to update 
  556. * @param Frame $last_row The last row in the row group 
  557. */ 
  558. function update_row_group(Frame $group, Frame $last_row) { 
  559.  
  560. $g_key = $group->get_id(); 
  561. $r_key = $last_row->get_id(); 
  562.  
  563. $r_rows = $this->_frames[$r_key]["rows"]; 
  564. $this->_frames[$g_key]["rows"] = range( $this->_frames[$g_key]["rows"][0], end($r_rows) ); 
  565.  
  566.  
  567. //........................................................................ 
  568.  
  569. function assign_x_positions() { 
  570. // Pre-condition: widths must be resolved and assigned to columns and 
  571. // column[0]["x"] must be set. 
  572.  
  573. if ( $this->_columns_locked ) { 
  574. return; 
  575.  
  576. $x = $this->_columns[0]["x"]; 
  577. foreach ( array_keys($this->_columns) as $j ) { 
  578. $this->_columns[$j]["x"] = $x; 
  579. $x += $this->_columns[$j]["used-width"]; 
  580.  
  581.  
  582. function assign_frame_heights() { 
  583. // Pre-condition: widths and heights of each column & row must be 
  584. // calcluated 
  585.  
  586. foreach ( $this->_frames as $arr ) { 
  587. $frame = $arr["frame"]; 
  588.  
  589. $h = 0; 
  590. foreach( $arr["rows"] as $row ) { 
  591. if ( !isset($this->_rows[$row]) ) { 
  592. // The row has been removed because of a page split, so skip it. 
  593. continue; 
  594.  
  595. $h += $this->_rows[$row]["height"]; 
  596.  
  597. if ( $frame instanceof Table_Cell_Frame_Decorator ) { 
  598. $frame->set_cell_height($h); 
  599. else { 
  600. $frame->get_style()->height = $h; 
  601.  
  602.  
  603. //........................................................................ 
  604.  
  605. /** 
  606. * Re-adjust frame height if the table height is larger than its content 
  607. */ 
  608. function set_frame_heights($table_height, $content_height) { 
  609.  
  610.  
  611. // Distribute the increased height proportionally amongst each row 
  612. foreach ( $this->_frames as $arr ) { 
  613. $frame = $arr["frame"]; 
  614.  
  615. $h = 0; 
  616. foreach ($arr["rows"] as $row ) { 
  617. if ( !isset($this->_rows[$row]) ) { 
  618. continue; 
  619.  
  620. $h += $this->_rows[$row]["height"]; 
  621.  
  622. if ( $content_height > 0 ) { 
  623. $new_height = ($h / $content_height) * $table_height; 
  624. else { 
  625. $new_height = 0; 
  626.  
  627. if ( $frame instanceof Table_Cell_Frame_Decorator ) { 
  628. $frame->set_cell_height($new_height); 
  629. else { 
  630. $frame->get_style()->height = $new_height; 
  631.  
  632.  
  633. //........................................................................ 
  634.  
  635. // Used for debugging: 
  636. function __toString() { 
  637. $str = ""; 
  638. $str .= "Columns:<br/>"; 
  639. $str .= pre_r($this->_columns, true); 
  640. $str .= "Rows:<br/>"; 
  641. $str .= pre_r($this->_rows, true); 
  642.  
  643. $str .= "Frames:<br/>"; 
  644. $arr = array(); 
  645. foreach ( $this->_frames as $key => $val ) { 
  646. $arr[$key] = array("columns" => $val["columns"], "rows" => $val["rows"]); 
  647.  
  648. $str .= pre_r($arr, true); 
  649.  
  650. if ( php_sapi_name() == "cli" ) { 
  651. $str = strip_tags(str_replace(array("<br/>", "<b>", "</b>"),  
  652. array("\n", chr(27)."[01;33m", chr(27)."[0m"),  
  653. $str)); 
  654.  
  655. return $str;