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