Cellmap.php 23 KB

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