1<?php 2// $Header: /cvsroot/html2ps/box.table.php,v 1.59 2007/04/01 12:11:24 Konstantin Exp $ 3 4class CellSpan { 5 var $row; 6 var $column; 7 var $size; 8} 9 10/** 11 * It is assumed that every row contains at least one cell 12 */ 13class TableBox extends GenericContainerBox { 14 var $cwc; 15 var $_cached_min_widths; 16 17 function TableBox() { 18 $this->GenericContainerBox(); 19 20 // List of column width constraints 21 $this->cwc = array(); 22 23 $this->_cached_min_widths = null; 24 } 25 26 function readCSS(&$state) { 27 parent::readCSS($state); 28 29 $this->_readCSS($state, 30 array(CSS_BORDER_COLLAPSE, 31 CSS_TABLE_LAYOUT)); 32 33 $this->_readCSSLengths($state, 34 array(CSS_HTML2PS_CELLPADDING, 35 CSS_HTML2PS_CELLSPACING)); 36 } 37 38 function &cell($r, $c) { 39 return $this->content[$r]->content[$c]; 40 } 41 42 function rows_count() { 43 return count($this->content); 44 } 45 46 // NOTE: assumes that rows are already normalized! 47 function cols_count() { 48 return count($this->content[0]->content); 49 } 50 51 // FIXME: just a stub 52 function append_line(&$e) {} 53 54 function &create(&$root, &$pipeline) { 55 $box =& new TableBox(); 56 $box->readCSS($pipeline->get_current_css_state()); 57 58 // This row should not inherit any table specific properties! 59 // 'overflow' for example 60 // 61 $css_state =& $pipeline->get_current_css_state(); 62 $css_state->pushDefaultState(); 63 64 $row =& new TableRowBox($root); 65 $row->readCSS($css_state); 66 67 $box->add_child($row); 68 69 $css_state->popState(); 70 71 // Setup cellspacing / cellpadding values 72 if ($box->get_css_property(CSS_BORDER_COLLAPSE) == BORDER_COLLAPSE) { 73 $handler =& CSS::get_handler(CSS_PADDING); 74 $box->setCSSProperty(CSS_PADDING, $handler->default_value()); 75 }; 76 77 // Set text-align to 'left'; all browsers I've ever seen prevent inheriting of 78 // 'text-align' property by the tables. 79 // Say, in the following example the text inside the table cell will be aligned left, 80 // instead of inheriting 'center' value. 81 // 82 // <div style="text-align: center; background-color: green;"> 83 // <table width="100" bgcolor="red"> 84 // <tr><td>TEST 85 // </table> 86 // </div> 87 $handler =& CSS::get_handler(CSS_TEXT_ALIGN); 88 $handler->css('left', $pipeline); 89 90 // Parse table contents 91 $child = $root->first_child(); 92 $col_index = 0; 93 while ($child) { 94 if ($child->node_type() === XML_ELEMENT_NODE) { 95 if ($child->tagname() === 'colgroup') { 96 // COLGROUP tags do not generate boxes; they contain information on the columns 97 // 98 $col_index = $box->parse_colgroup_tag($child, $col_index); 99 } else { 100 $child_box =& create_pdf_box($child, $pipeline); 101 $box->add_child($child_box); 102 }; 103 }; 104 105 $child = $child->next_sibling(); 106 }; 107 108 $box->normalize($pipeline); 109 $box->normalize_cwc(); 110 $box->normalize_rhc(); 111 $box->normalize_parent(); 112 113 return $box; 114 } 115 116 // Parse the data in COL node; 117 // currently only 'width' attribute is parsed 118 // 119 // @param $root reference to a COL dom node 120 // @param $index index of column corresponding to this node 121 function parse_col(&$root, $index) { 122 if ($root->has_attribute('width')) { 123 // The value if 'width' attrubute is "multi-length"; 124 // it means that it could be: 125 // 1. absolute value (10) 126 // 2. percentage value (10%) 127 // 3. relative value (3* or just *) 128 // 129 130 // TODO: support for relative values 131 132 $value = $root->get_attribute('width'); 133 if (is_percentage($value)) { 134 $this->cwc[$index] = new WCFraction(((int)$value) / 100); 135 } else { 136 $this->cwc[$index] = new WCConstant(px2pt((int)$value)); 137 }; 138 }; 139 } 140 141 // Traverse the COLGROUP node and save the column-specific information 142 // 143 // @param $root COLGROUP node 144 // @param $start_index index of the first column in this column group 145 // @return index of column after the last processed 146 // 147 function parse_colgroup_tag(&$root, $start_index) { 148 $index = $start_index; 149 150 // COLGROUP may contain zero or more COLs 151 // 152 $child = $root->first_child(); 153 while ($child) { 154 if ($child->tagname() === 'col') { 155 $this->parse_col($child, $index); 156 $index ++; 157 }; 158 $child = $child->next_sibling(); 159 }; 160 161 return $index; 162 } 163 164 function normalize_parent() { 165 for ($i=0; $i<count($this->content); $i++) { 166 $this->content[$i]->parent =& $this; 167 168 for ($j=0; $j<count($this->content[$i]->content); $j++) { 169 $this->content[$i]->content[$j]->parent =& $this; 170 171 // Set the column number for the cell to further reference 172 $this->content[$i]->content[$j]->column = $j; 173 174 // Set the column number for the cell to further reference 175 $this->content[$i]->content[$j]->row = $i; 176 } 177 } 178 } 179 180 // Normalize row height constraints 181 // 182 // no return value 183 // 184 function normalize_rhc() { 185 // Initialize the constraint array with the empty constraints 186 $this->rhc = array(); 187 for ($i=0, $size = count($this->content); $i < $size; $i++) { 188 $this->rhc[$i] = new HCConstraint(null, null, null); 189 }; 190 191 // Scan all cells 192 for ($i=0, $num_rows = count($this->content); $i < $num_rows; $i++) { 193 $row =& $this->content[$i]; 194 195 for ($j=0, $num_cells = count($row->content); $j < $num_cells; $j++) { 196 $cell = $row->content[$j]; 197 198 // Ignore cells with rowspans 199 if ($cell->rowspan > 1) { continue; } 200 201 // Put current cell width constraint as a columns with constraint 202 $this->rhc[$i] = merge_height_constraint($this->rhc[$i], $cell->get_height_constraint()); 203 204 // Now reset the cell width constraint; cell width should be affected by ceolumn constraint only 205 $hc = new HCConstraint(null, null, null); 206 $cell->put_height_constraint($hc); 207 }; 208 }; 209 } 210 211 // Normalize column width constraints 212 // Note that cwc array may be partially prefilled by a GOLGROUP/COL-generated constraints! 213 // 214 function normalize_cwc() { 215 // Note we've called 'normalize' method prior to 'normalize_cwc', 216 // so we already have all rows of equal length 217 // 218 for ($i=0, $num_cols = count($this->content[0]->content); $i < $num_cols; $i++) { 219 // Check if there's already COL-generated constraint for this column 220 // 221 if (!isset($this->cwc[$i])) { 222 $this->cwc[$i] = new WCNone; 223 }; 224 } 225 226 // For each column (we should have table already normalized - so lengths of all rows are equal) 227 for ($i=0, $num_cols = count($this->content[0]->content); $i < $num_cols; $i++) { 228 229 // For each row 230 for ($j=0, $num_rows = count($this->content); $j < $num_rows; $j++) { 231 $cell =& $this->content[$j]->content[$i]; 232 233 // Ignore cells with colspans 234 if ($cell->colspan > 1) { continue; } 235 236 // Put current cell width constraint as a columns with constraint 237 $this->cwc[$i] = merge_width_constraint($this->cwc[$i], $cell->get_css_property(CSS_WIDTH)); 238 239 // Now reset the cell width constraint; cell width should be affected by ceolumn constraint only 240 $cell->setCSSProperty(CSS_WIDTH, new WCNone); 241 } 242 } 243 244 // Now fix the overconstrained columns; first of all, sum of all percentage-constrained 245 // columns should be less or equal than 100%. If sum is greater, the last column 246 // percentage is reduced in order to get 100% as a result. 247 $rest = 1; 248 for ($i=0, $num_cols = count($this->content[0]->content); $i < $num_cols; $i++) { 249 // Get current CWC 250 $wc =& $this->cwc[$i]; 251 252 if ($wc->isFraction()) { 253 $wc->fraction = min($rest, $wc->fraction); 254 $rest -= $wc->fraction; 255 }; 256 }; 257 258 /** 259 * Now, let's process cells spanninig several columns. 260 */ 261 262 /** 263 * Let's check if there's any colspanning cells filling the whole table width and 264 * containing non-100% percentage constraint 265 */ 266 267 // For each row 268 for ($j=0; $j<count($this->content); $j++) { 269 /** 270 * Check if the first cell in this row satisfies the above condition 271 */ 272 273 $cell =& $this->content[$j]->content[0]; 274 275 /** 276 * Note that there should be '>='; '==' is not enough, as sometimes cell is declared to span 277 * more columns than there are in the table 278 */ 279 $cell_wc = $cell->get_css_property(CSS_WIDTH); 280 if (!$cell->is_fake() && 281 $cell_wc->isFraction() && 282 $cell->colspan >= count($this->content[$j])) { 283 284 /** 285 * Clear the constraint; anyway, it should be replaced with 100% in this case, as 286 * this cell is the only cell in the row 287 */ 288 289 $wc = new WCNone; 290 $cell->setCSSProperty(CSS_WIDTH, $wc); 291 }; 292 }; 293 } 294 295 /** 296 * Normalize table by adding fake cells for colspans and rowspans 297 * Also, if there is any empty rows (without cells), add at least one fake cell 298 */ 299 function normalize(&$pipeline) { 300 /** 301 * Fix empty rows by adding a fake cell 302 */ 303 for ($i=0; $i<count($this->content); $i++) { 304 $row =& $this->content[$i]; 305 if (count($row->content) == 0) { 306 $this->content[$i]->add_fake_cell_before(0, $pipeline); 307 }; 308 }; 309 310 /** 311 * first, normalize colspans 312 */ 313 for ($i=0; $i<count($this->content); $i++) { 314 $this->content[$i]->normalize($pipeline); 315 }; 316 317 /** 318 * second, normalize rowspans 319 * 320 * We should scan table column-by-column searching for row-spanned cells; 321 * consider the following example: 322 * 323 * <table> 324 * <tr> 325 * <td>A1</td> 326 * <td rowspan="3">B1</td> 327 * <td>C1</td> 328 * </tr> 329 * 330 * <tr> 331 * <td rowspan="2">A2</td> 332 * <td>C2</td> 333 * </tr> 334 * 335 * <tr> 336 * <td>C3</td> 337 * </tr> 338 * </table> 339 */ 340 341 $i_col = 0; 342 do { 343 $flag = false; 344 for ($i_row=0; $i_row<count($this->content); $i_row++) { 345 $row =& $this->content[$i_row]; 346 if ($i_col < count($row->content)) { 347 $flag = true; 348 349 // Check if this rowspan runs off the last row 350 $row->content[$i_col]->rowspan = min($row->content[$i_col]->rowspan, 351 count($this->content) - $i_row); 352 353 if ($row->content[$i_col]->rowspan > 1) { 354 355 // Note that min($i_row + $row->content[$i_col]->rowspan, count($this->content)) is 356 // required, as we cannot be sure that table actually contains the number 357 // of rows used in rowspan 358 // 359 for ($k=$i_row+1; $k<min($i_row + $row->content[$i_col]->rowspan, count($this->content)); $k++) { 360 361 // Note that if rowspanned cell have a colspan, we should insert SEVERAL fake cells! 362 // 363 for ($cs = 0; $cs < $row->content[$i_col]->colspan; $cs++) { 364 $this->content[$k]->add_fake_cell_before($i_col, $pipeline); 365 }; 366 }; 367 }; 368 }; 369 }; 370 371 $i_col ++; 372 } while ($flag); 373 374 // third, make all rows equal in length by padding with fake-cells 375 $length = 0; 376 for ($i=0; $i<count($this->content); $i++) { 377 $length = max($length, count($this->content[$i]->content)); 378 } 379 for ($i=0; $i<count($this->content); $i++) { 380 $row =& $this->content[$i]; 381 while ($length > count($row->content)) { 382 $row->append_fake_cell($pipeline); 383 } 384 } 385 } 386 387 // Overrides default 'add_child' in GenericFormattedBox 388 function add_child(&$item) { 389 // Check if we're trying to add table cell to current table directly, without any table-rows 390 if ($item->isCell()) { 391 // Add cell to the last row 392 $last_row =& $this->content[count($this->content)-1]; 393 $last_row->add_child($item); 394 395 } elseif ($item->isTableRow()) { 396 // If previous row is empty, remove it (get rid of automatically generated table row in constructor) 397 if (count($this->content) > 0) { 398 if (count($this->content[count($this->content)-1]->content) == 0) { 399 array_pop($this->content); 400 } 401 }; 402 403 // Just add passed row 404 $this->content[] =& $item; 405 } elseif ($item->isTableSection()) { 406 // Add table section rows to current table, then drop section box 407 for ($i=0, $size = count($item->content); $i < $size; $i++) { 408 $this->add_child($item->content[$i]); 409 } 410 }; 411 } 412 413 // Table-specific functions 414 415 // PREDICATES 416 function is_constrained_column($index) { 417 return !is_a($this->get_cwc($index),"wcnone"); 418 } 419 420 // ROWSPANS 421 function table_have_rowspan($x,$y) { 422 return $this->content[$y]->content[$x]->rowspan; 423 } 424 425 function table_fit_rowspans($heights) { 426 $spans = $this->get_rowspans(); 427 428 // Scan all cells spanning several rows 429 foreach ($spans as $span) { 430 $cell =& $this->content[$span->row]->content[$span->column]; 431 432 // now check if cell height is less than sum of spanned rows heights 433 $row_heights = array_slice($heights, $span->row, $span->size); 434 435 // Vertical-align current cell 436 // calculate (approximate) row baseline 437 $baseline = $this->content[$span->row]->get_row_baseline(); 438 439 // apply vertical-align 440 $vertical_align = $cell->get_css_property(CSS_VERTICAL_ALIGN); 441 442 $va_fun = CSSVerticalAlign::value2pdf($vertical_align); 443 $va_fun->apply_cell($cell, array_sum($row_heights), $baseline); 444 445 if (array_sum($row_heights) > $cell->get_full_height()) { 446 // Make cell fill all available vertical space 447 $cell->put_full_height(array_sum($row_heights)); 448 }; 449 } 450 } 451 452 function get_rowspans() { 453 $spans = array(); 454 455 for ($i=0; $i<count($this->content); $i++) { 456 $spans = array_merge($spans, $this->content[$i]->get_rowspans($i)); 457 }; 458 459 return $spans; 460 } 461 462 // ROW-RELATED 463 464 /** 465 * Calculate set of row heights 466 * 467 * At the moment (*), we have a sum of total content heights of percentage constraned rows in 468 * $ch variable, and a "free" (e.g. table height - sum of all non-percentage constrained heights) height 469 * in the $h variable. Obviously, percentage-constrained rows should be expanded to fill the free space 470 * 471 * On the other size, there should be a maximal value to expand them to; for example, if sum of 472 * percentage constraints is 33%, then all these rows should fill only 1/3 of the table height, 473 * whatever the content height of other rows is. In this case, other (non-constrained) rows 474 * should be expanded to fill space left. 475 * 476 * In the latter case, if there's no non-constrained rows, the additional space should be filled by 477 * "plain" rows without any constraints 478 * 479 * @param $minheight the minimal allowed height of the row; as we'll need to expand rows later 480 * and rows containing totally empty cells will have zero height 481 * @return array of row heights in media points 482 */ 483 function _row_heights($minheight) { 484 $heights = array(); 485 $cheights = array(); 486 $height = $this->get_height(); 487 488 // Calculate "content" and "constrained" heights of table rows 489 490 for ($i=0; $i<count($this->content); $i++) { 491 $heights[] = max($minheight, $this->content[$i]->row_height()); 492 493 // Apply row height constraint 494 // we need to specify box which parent will serve as a base for height calculation; 495 496 $hc = $this->get_rhc($i); 497 $cheights[] = $hc->apply($heights[$i], $this->content[$i], null); 498 }; 499 500 // Collapse "constrained" heights of percentage-constrained rows, if they're 501 // taking more that available space 502 503 $flags = $this->get_non_percentage_constrained_height_flags(); 504 $h = $height; 505 $ch = 0; 506 for ($i=0; $i<count($heights); $i++) { 507 if ($flags[$i]) { $h -= $cheights[$i]; } else { $ch += $cheights[$i]; }; 508 }; 509 // (*) see note in the function description 510 if ($ch > 0) { 511 $scale = $h / $ch; 512 513 if ($scale < 1) { 514 for ($i=0; $i<count($heights); $i++) { 515 if (!$flags[$i]) { $cheights[$i] *= $scale; }; 516 }; 517 }; 518 }; 519 520 // Expand non-constrained rows, if there's free space still 521 522 $flags = $this->get_non_constrained_height_flags(); 523 $h = $height; 524 $ch = 0; 525 for ($i=0; $i<count($cheights); $i++) { 526 if (!$flags[$i]) { $h -= $cheights[$i]; } else { $ch += $cheights[$i]; }; 527 }; 528 // (*) see note in the function description 529 if ($ch > 0) { 530 $scale = $h / $ch; 531 532 if ($scale < 1) { 533 for ($i=0; $i<count($heights); $i++) { 534 if ($flags[$i]) { $cheights[$i] *= $scale; }; 535 }; 536 }; 537 }; 538 539 // Expand percentage-constrained rows, if there's free space still 540 541 $flags = $this->get_non_percentage_constrained_height_flags(); 542 $h = $height; 543 $ch = 0; 544 for ($i=0; $i<count($cheights); $i++) { 545 if ($flags[$i]) { $h -= $cheights[$i]; } else { $ch += $cheights[$i]; }; 546 }; 547 // (*) see note in the function description 548 if ($ch > 0) { 549 $scale = $h / $ch; 550 551 if ($scale < 1) { 552 for ($i=0; $i<count($heights); $i++) { 553 if (!$flags[$i]) { $cheights[$i] *= $scale; }; 554 }; 555 }; 556 }; 557 558 // Get the actual row height 559 for ($i=0; $i<count($heights); $i++) { 560 $heights[$i] = max($heights[$i], $cheights[$i]); 561 }; 562 563 return $heights; 564 } 565 566 function table_resize_rows(&$heights) { 567 $row_top = $this->get_top(); 568 569 $size = count($heights); 570 for ($i=0; $i<$size; $i++) { 571 $this->content[$i]->table_resize_row($heights[$i], $row_top); 572 $row_top -= $heights[$i]; 573 } 574 575 // Set table height to sum of row heights 576 $this->put_height(array_sum($heights)); 577 } 578 579 // // Calculate given table row height 580 // // 581 // // @param $index zero-based row index 582 // // @return value of row height (in media points) 583 // // 584 // function table_row_height($index) { 585 // // Select row 586 // $row =& $this->content[$index]; 587 588 // // Calculate height of each cell contained in this row 589 // $height = 0; 590 // for ($i=0; $i<count($row->content); $i++) { 591 // if ($this->table_have_rowspan($i, $index) <= 1) { 592 // $height = max($height, $row->content[$i]->get_full_height()); 593 // } 594 // } 595 596 // return $height; 597 // } 598 599 // function get_row_baseline($index) { 600 // // Get current row 601 // $row =& $this->content[$index]; 602 // // Calculate maximal baseline for each cell contained 603 // $baseline = 0; 604 // for ($i = 0; $i < count($row->content); $i++) { 605 // // Cell baseline is the baseline of its first line box inside this cell 606 // if (count($row->content[$i]->content) > 0) { 607 // $baseline = max($baseline, $row->content[$i]->content[0]->baseline); 608 // }; 609 // }; 610 // return $baseline; 611 // } 612 613 // Width constraints 614 function get_cwc($col) { 615 return $this->cwc[$col]; 616 } 617 618 // Get height constraint for the given row 619 // 620 // @param $row number of row (zero-based) 621 // 622 // @return HCConstraint object 623 // 624 function get_rhc($row) { 625 return $this->rhc[$row]; 626 } 627 628 // Width calculation 629 // 630 // Note that if table have no width constraint AND some columns are percentage constrained, 631 // then the width of the table can be determined based on the minimal column width; 632 // e.g. if some column have minimal width of 10px and 10% width constraint, 633 // then table will have minimal width of 100px. If there's several percentage-constrained columns, 634 // then we choose from the generated values the maximal one 635 // 636 // Of course, all of the above can be applied ONLY to table without width constraint; 637 // of theres any w.c. applied to the table, it will have greater than column constraints 638 // 639 // We must take constrained table width into account; if there's a width constraint, 640 // then we must choose the maximal value between the constrained width and sum of minimal 641 // columns widths - so, expanding the constrained width in case it is not enough to fit 642 // the table contents 643 // 644 // @param $context referene to a flow context object 645 // @return minimal box width (including the padding/margin/border width! NOT content width) 646 // 647 function get_min_width(&$context) { 648 $widths = $this->get_table_columns_min_widths($context); 649 $maxw = $this->get_table_columns_max_widths($context); 650 651 // Expand some columns to fit colspanning cells 652 $widths = $this->_table_apply_colspans($widths, $context, 'get_min_width', $widths, $maxw); 653 654 $width = array_sum($widths); 655 $base_width = $width; 656 657 $wc = $this->get_css_property(CSS_WIDTH); 658 if (!$wc->isNull()) { 659 // Check if constrained table width should be expanded to fit the table contents 660 // 661 $width = max($width, $wc->apply(0, $this->parent->get_available_width($context))); 662 } else { 663 // Now check if there's any percentage column width constraints (note that 664 // if we've get here, than the table width is not constrained). Calculate 665 // the table width basing on these values and select the maximal value 666 // 667 for ($i=0; $i<$this->cols_count(); $i++) { 668 $cwc = $this->get_cwc($i); 669 670 $width = max($width, 671 min($cwc->apply_inverse($widths[$i], $base_width), 672 $this->parent->get_available_width($context) - $this->_get_hor_extra())); 673 }; 674 }; 675 676 return $width + $this->_get_hor_extra(); 677 } 678 679 function get_min_width_natural(&$context) { 680 return $this->get_min_width($context); 681 } 682 683 function get_max_width(&$context) { 684 $wc = $this->get_css_property(CSS_WIDTH); 685 686 if ($wc->isConstant()) { 687 return $wc->apply(0, $this->parent->get_available_width($context)); 688 } else { 689 $widths = $this->get_table_columns_max_widths($context); 690 $minwc = $this->get_table_columns_min_widths($context); 691 692 $widths = $this->_table_apply_colspans($widths, $context, 'get_max_width', $minwc, $widths); 693 694 $width = array_sum($widths); 695 $base_width = $width; 696 697 // Now check if there's any percentage column width constraints (note that 698 // if we've get here, than the table width is not constrained). Calculate 699 // the table width based on these values and select the maximal value 700 // 701 for ($i=0; $i<$this->cols_count(); $i++) { 702 $cwc = $this->get_cwc($i); 703 704 $width = max($width, 705 min($cwc->apply_inverse($widths[$i], $base_width), 706 $this->parent->get_available_width($context) - $this->_get_hor_extra())); 707 }; 708 709 return $width + $this->_get_hor_extra(); 710 } 711 } 712 713 function get_max_width_natural(&$context) { 714 return $this->get_max_width($context); 715 } 716 717 function get_width() { 718 $wc = $this->get_css_property(CSS_WIDTH); 719 $pwc = $this->parent->get_css_property(CSS_WIDTH); 720 721 if (!$this->parent->isCell() || 722 !$pwc->isNull() || 723 !$wc->isFraction()) { 724 $width = $wc->apply($this->width, $this->parent->width); 725 } else { 726 $width = $this->width; 727 }; 728 729 // Note that table 'padding' property for is handled differently 730 // by different browsers; for example, IE 6 ignores it completely, 731 // while FF 1.5 subtracts horizontal padding value from constrained 732 // table width. We emulate FF behavior here 733 return $width - 734 $this->get_padding_left() - 735 $this->get_padding_right(); 736 } 737 738 function table_column_widths(&$context) { 739 $table_layout = $this->get_css_property(CSS_TABLE_LAYOUT); 740 switch ($table_layout) { 741 case TABLE_LAYOUT_FIXED: 742// require_once(HTML2PS_DIR.'strategy.table.layout.fixed.php'); 743// $strategy =& new StrategyTableLayoutFixed(); 744// break; 745 case TABLE_LAYOUT_AUTO: 746 default: 747 require_once(HTML2PS_DIR.'strategy.table.layout.auto.php'); 748 $strategy =& new StrategyTableLayoutAuto(); 749 break; 750 }; 751 752 return $strategy->apply($this, $context); 753 } 754 755 // Extend some columns widths (if needed) to fit colspanned cell contents 756 // 757 function _table_apply_colspans($widths, &$context, $width_fun, $minwc, $maxwc) { 758 $colspans = $this->get_colspans(); 759 760 foreach ($colspans as $colspan) { 761 $cell = $this->content[$colspan->row]->content[$colspan->column]; 762 763 // apply colspans to the corresponsing colspanned-cell dimension 764 // 765 $cell_width = $cell->$width_fun($context); 766 767 // Apply cell constraint width, if any AND if table width is constrained 768 // if table width is not constrained, we should not do this, as current value 769 // of $table->get_width is maximal width (parent width), not the actual 770 // width of the table 771 $wc = $this->get_css_property(CSS_WIDTH); 772 if (!$wc->isNull()) { 773 $cell_wc = $cell->get_css_property(CSS_WIDTH); 774 $cell_width = $cell_wc->apply($cell_width, $this->get_width()); 775 776 // On the other side, constrained with cannot be less than cell minimal width 777 $cell_width = max($cell_width, $cell->get_min_width($context)); 778 }; 779 780 // now select the pre-calculated widths of columns covered by this cell 781 // select the list of resizable columns covered by this cell 782 $spanned_widths = array(); 783 $spanned_resizable = array(); 784 785 for ($i=$colspan->column; $i < $colspan->column+$colspan->size; $i++) { 786 $spanned_widths[] = $widths[$i]; 787 $spanned_resizable[] = ($minwc[$i] != $maxwc[$i]); 788 } 789 790 // Sometimes we may encounter the colspan over the empty columns (I mean ALL columns are empty); in this case 791 // we need to make these columns reizable in order to fit colspanned cell contents 792 // 793 if (array_sum($spanned_widths) == 0) { 794 for ($i=0; $i<count($spanned_widths); $i++) { 795 $spanned_widths[$i] = EPSILON; 796 $spanned_resizable[$i] = true; 797 }; 798 }; 799 800 // The same problem may arise when all colspanned columns are not resizable; in this case we'll force all 801 // of them to be resized 802 $any_resizable = false; 803 for ($i=0; $i<count($spanned_widths); $i++) { 804 $any_resizable |= $spanned_resizable[$i]; 805 }; 806 if (!$any_resizable) { 807 for ($i=0; $i<count($spanned_widths); $i++) { 808 $spanned_resizable[$i] = true; 809 }; 810 } 811 812 // Expand resizable columns 813 // 814 $spanned_widths = expand_to_with_flags($cell_width,$spanned_widths,$spanned_resizable); 815 816 // Store modified widths 817 array_splice($widths, $colspan->column, $colspan->size, $spanned_widths); 818 }; 819 820 return $widths; 821 } 822 823 function get_table_columns_max_widths(&$context) { 824 $widths = array(); 825 826 for ($i=0; $i<count($this->content[0]->content); $i++) { 827 $widths[] = 0; 828 }; 829 830 for ($i=0; $i<count($this->content); $i++) { 831 // Calculate column widths for a current row 832 $roww = $this->content[$i]->get_table_columns_max_widths($context); 833 for ($j=0; $j<count($roww); $j++) { 834 // $widths[$j] = max($roww[$j], isset($widths[$j]) ? $widths[$j] : 0); 835 $widths[$j] = max($roww[$j], $widths[$j]); 836 } 837 } 838 839 // Use column width constraints - column should not be wider its constrained width 840 for ($i=0; $i<count($widths); $i++) { 841 $cwc = $this->get_cwc($i); 842 843 // Newertheless, percentage constraints should not be applied IF table 844 // does not have constrained width 845 // 846 if (!is_a($cwc,"wcfraction")) { 847 $widths[$i] = $cwc->apply($widths[$i], $this->get_width()); 848 }; 849 } 850 851 // TODO: colspans 852 853 return $widths; 854 } 855 856 /** 857 * Optimization: calculated widths are cached 858 */ 859 function get_table_columns_min_widths(&$context) { 860 if (!is_null($this->_cached_min_widths)) { 861 return $this->_cached_min_widths; 862 }; 863 864 $widths = array(); 865 866 for ($i=0; $i<count($this->content[0]->content); $i++) { 867 $widths[] = 0; 868 }; 869 870 $content_size = count($this->content); 871 for ($i=0; $i<$content_size; $i++) { 872 // Calculate column widths for a current row 873 $roww = $this->content[$i]->get_table_columns_min_widths($context); 874 875 $row_size = count($roww); 876 for ($j=0; $j<$row_size; $j++) { 877 $widths[$j] = max($roww[$j], $widths[$j]); 878 } 879 } 880 881 $this->_cached_min_widths = $widths; 882 return $widths; 883 } 884 885 function get_colspans() { 886 $colspans = array(); 887 888 for ($i=0; $i<count($this->content); $i++) { 889 $colspans = array_merge($colspans, $this->content[$i]->get_colspans($i)); 890 }; 891 892 return $colspans; 893 } 894 895 function check_constrained_colspan($col) { 896 for ($i=0; $i<$this->rows_count(); $i++) { 897 $cell =& $this->cell($i, $col); 898 $cell_wc = $cell->get_css_property(CSS_WIDTH); 899 900 if ($cell->colspan > 1 && 901 !$cell_wc->isNull()) { 902 return true; 903 }; 904 }; 905 return false; 906 } 907 908 // Tries to change minimal constrained width so that columns will fit into the given 909 // table width 910 // 911 // Note that every width constraint have its own priority; first, the unconstrained columns are collapsed, 912 // then - percentage constrained and after all - columns having fixed width 913 // 914 // @param $width table width 915 // @param $minw array of unconstrained minimal widths 916 // @param $minwc array of constrained minimal widths 917 // @return list of normalized minimal constrained widths 918 // 919 function normalize_min_widths($width, $minw, $minwc) { 920 // Check if sum of constrained widths is too big 921 // Note that we compare sum of constrained width with the MAXIMAL value of table width and 922 // sum of uncostrained minimal width; it will prevent from unneeded collapsing of table cells 923 // if table content will expand its width anyway 924 // 925 $twidth = max($width, array_sum($minw)); 926 927 // compare with sum of minimal constrained widths 928 // 929 if (array_sum($minwc) > $twidth) { 930 $delta = array_sum($minwc) - $twidth; 931 932 // Calculate the amount of difference between minimal and constrained minimal width for each columns 933 $diff = array(); 934 for ($i=0; $i<count($minw); $i++) { 935 // Do no modify width of columns taking part in constrained colspans 936 if (!$this->check_constrained_colspan($i)) { 937 $diff[$i] = $minwc[$i] - $minw[$i]; 938 } else { 939 $diff[$i] = 0; 940 }; 941 } 942 943 // If no difference is found, we can collapse no columns 944 // otherwise scale some columns... 945 $cwdelta = array_sum($diff); 946 947 if ($cwdelta > 0) { 948 for ($i=0; $i<count($minw); $i++) { 949 // $minwc[$i] = max(0,- ($minwc[$i] - $minw[$i]) / $cwdelta * $delta + $minwc[$i]); 950 $minwc[$i] = max(0, -$diff[$i] / $cwdelta * $delta + $minwc[$i]); 951 } 952 } 953 } 954 955 return $minwc; 956 } 957 958 function table_have_colspan($x, $y) { 959 return $this->content[$y]->content[$x]->colspan; 960 } 961 962 // Flow-control 963 function reflow(&$parent, &$context) { 964 if ($this->get_css_property(CSS_FLOAT) === FLOAT_NONE) { 965 $status = $this->reflow_static_normal($parent, $context); 966 } else { 967 $status = $this->reflow_static_float($parent, $context); 968 } 969 970 return $status; 971 } 972 973 function reflow_absolute(&$context) { 974 GenericFormattedBox::reflow($parent, $context); 975 976 // Calculate margin values if they have been set as a percentage 977 $this->_calc_percentage_margins($parent); 978 979 // Calculate width value if it had been set as a percentage 980 $this->_calc_percentage_width($parent, $context); 981 982 $wc = $this->get_css_property(CSS_WIDTH); 983 if (!$wc->isNull()) { 984 $col_width = $this->get_table_columns_min_widths($context); 985 $maxw = $this->get_table_columns_max_widths($context); 986 $col_width = $this->_table_apply_colspans($col_width, $context, 'get_min_width', $col_width, $maxw); 987 988 if (array_sum($col_width) > $this->get_width()) { 989 $wc = new WCConstant(array_sum($col_width)); 990 }; 991 }; 992 993 $position_strategy =& new StrategyPositionAbsolute(); 994 $position_strategy->apply($this); 995 996 $this->reflow_content($context); 997 } 998 999 /** 1000 * TODO: unlike block elements, table unconstrained width is determined 1001 * with its content, so it may be smaller than parent available width! 1002 */ 1003 function reflow_static_normal(&$parent, &$context) { 1004 GenericFormattedBox::reflow($parent, $context); 1005 1006 // Calculate margin values if they have been set as a percentage 1007 $this->_calc_percentage_margins($parent); 1008 1009 // Calculate width value if it had been set as a percentage 1010 $this->_calc_percentage_width($parent, $context); 1011 1012 $wc = $this->get_css_property(CSS_WIDTH); 1013 if (!$wc->isNull()) { 1014 $col_width = $this->get_table_columns_min_widths($context); 1015 $maxw = $this->get_table_columns_max_widths($context); 1016 $col_width = $this->_table_apply_colspans($col_width, $context, 'get_min_width', $col_width, $maxw); 1017 1018 if (array_sum($col_width) > $this->get_width()) { 1019 $wc = new WCConstant(array_sum($col_width)); 1020 }; 1021 }; 1022 1023 // As table width can be deterimined by its contents, we may calculate auto values 1024 // only AFTER the contents have been reflown; thus, we'll offset the table 1025 // as a whole by a value of left margin AFTER the content reflow 1026 1027 // Do margin collapsing 1028 $y = $this->collapse_margin($parent, $context); 1029 1030 // At this moment we have top parent/child collapsed margin at the top of context object 1031 // margin stack 1032 1033 $y = $this->apply_clear($y, $context); 1034 1035 // Store calculated Y coordinate as current Y in the parent box 1036 $parent->_current_y = $y; 1037 1038 // Terminate current parent line-box 1039 $parent->close_line($context); 1040 1041 // And add current box to the parent's line-box (alone) 1042 $parent->append_line($this); 1043 1044 // Determine upper-left _content_ corner position of current box 1045 // Also see note above regarding margins 1046 $border = $this->get_css_property(CSS_BORDER); 1047 $padding = $this->get_css_property(CSS_PADDING); 1048 1049 $this->put_left($parent->_current_x + 1050 $border->left->get_width() + 1051 $padding->left->value); 1052 1053 // Note that top margin already used above during maring collapsing 1054 $this->put_top($parent->_current_y - $border->top->get_width() - $padding->top->value); 1055 1056 /** 1057 * By default, child block box will fill all available parent width; 1058 * note that actual width will be smaller because of non-zero padding, border and margins 1059 */ 1060 $this->put_full_width($parent->get_available_width($context)); 1061 1062 // Reflow contents 1063 $this->reflow_content($context); 1064 1065 // Update the collapsed margin value with current box bottom margin 1066 $margin = $this->get_css_property(CSS_MARGIN); 1067 1068 $context->pop_collapsed_margin(); 1069 $context->pop_collapsed_margin(); 1070 $context->push_collapsed_margin($margin->bottom->value); 1071 1072 // Calculate margins and/or width is 'auto' values have been specified 1073 $this->_calc_auto_width_margins($parent); 1074 $this->offset($margin->left->value, 0); 1075 1076 // Extend parent's height to fit current box 1077 $parent->extend_height($this->get_bottom_margin()); 1078 // Terminate parent's line box 1079 $parent->close_line($context); 1080 } 1081 1082 // Get a list of boolean values indicating if table rows are height constrained 1083 // 1084 // @return array containing 'true' value at index I if I-th row is not height-constrained 1085 // and 'false' otherwise 1086 // 1087 function get_non_constrained_flags() { 1088 $flags = array(); 1089 1090 for ($i=0; $i<count($this->content); $i++) { 1091 $hc = $this->get_rhc($i); 1092 $flags[$i] = 1093 (is_null($hc->constant)) && 1094 (is_null($hc->min)) && 1095 (is_null($hc->max)); 1096 }; 1097 1098 return $flags; 1099 } 1100 1101 // Get a list of boolean values indicating if table rows are height constrained using percentage values 1102 // 1103 // @return array containing 'true' value at index I if I-th row is not height-constrained 1104 // and 'false' otherwise 1105 // 1106 function get_non_percentage_constrained_height_flags() { 1107 $flags = array(); 1108 1109 for ($i=0; $i<count($this->content); $i++) { 1110 $hc = $this->get_rhc($i); 1111 $flags[$i] = 1112 (!is_null($hc->constant) ? !$hc->constant[1] : true) && 1113 (!is_null($hc->min) ? !$hc->min[1] : true) && 1114 (!is_null($hc->max) ? !$hc->max[1] : true); 1115 }; 1116 1117 return $flags; 1118 } 1119 1120 function get_non_constrained_height_flags() { 1121 $flags = array(); 1122 1123 for ($i=0; $i<count($this->content); $i++) { 1124 $hc = $this->get_rhc($i); 1125 1126 $flags[$i] = $hc->is_null(); 1127 }; 1128 1129 return $flags; 1130 } 1131 1132 // Get a list of boolean values indicating if table columns are height constrained 1133 // 1134 // @return array containing 'true' value at index I if I-th columns is not width-constrained 1135 // and 'false' otherwise 1136 // 1137 function get_non_constrained_width_flags() { 1138 $flags = array(); 1139 1140 for ($i=0; $i<$this->cols_count(); $i++) { 1141 $wc = $this->get_cwc($i); 1142 $flags[$i] = is_a($wc,"wcnone"); 1143 }; 1144 1145 return $flags; 1146 } 1147 1148 function get_non_constant_constrained_width_flags() { 1149 $flags = array(); 1150 1151 for ($i=0; $i<$this->cols_count(); $i++) { 1152 $wc = $this->get_cwc($i); 1153 $flags[$i] = !is_a($wc,"WCConstant"); 1154 }; 1155 1156 return $flags; 1157 } 1158 1159 function check_if_column_image_constrained($col) { 1160 for ($i=0; $i<$this->rows_count(); $i++) { 1161 $cell =& $this->cell($i, $col); 1162 for ($j=0; $j<count($cell->content); $j++) { 1163 if (!$cell->content[$j]->is_null() && 1164 !is_a($cell->content[$j], "GenericImgBox")) { 1165 return false; 1166 }; 1167 }; 1168 }; 1169 return true; 1170 } 1171 1172 function get_non_image_constrained_width_flags() { 1173 $flags = array(); 1174 1175 for ($i=0; $i<$this->cols_count(); $i++) { 1176 $flags[$i] = !$this->check_if_column_image_constrained($i); 1177 }; 1178 1179 return $flags; 1180 } 1181 1182 // Get a list of boolean values indicating if table rows are NOT constant constrained 1183 // 1184 // @return array containing 'true' value at index I if I-th row is height-constrained 1185 // and 'false' otherwise 1186 // 1187 function get_non_constant_constrained_flags() { 1188 $flags = array(); 1189 1190 for ($i=0; $i<count($this->content); $i++) { 1191 $hc = $this->get_rhc($i); 1192 $flags[$i] = is_null($hc->constant); 1193 }; 1194 1195 return $flags; 1196 } 1197 1198 function reflow_content(&$context) { 1199 // Reflow content 1200 1201 // Reset current Y value 1202 // 1203 $this->_current_y = $this->get_top(); 1204 1205 // Determine the base table width 1206 // if width constraint exists, the actual table width will not be changed anyway 1207 // 1208 $this->put_width(min($this->get_max_width($context), $this->get_width())); 1209 1210 // Calculate widths of table columns 1211 $columns = $this->table_column_widths($context); 1212 1213 // Collapse table to minimum width (if width is not constrained) 1214 $real_width = array_sum($columns); 1215 $this->put_width($real_width); 1216 1217 // If width is constrained, and is less than calculated, update the width constraint 1218 // 1219 // if ($this->get_width() < $real_width) { 1220 // // $this->put_width_constraint(new WCConstant($real_width)); 1221 // }; 1222 1223 // Flow cells horizontally in each table row 1224 for ($i=0; $i<count($this->content); $i++) { 1225 // Row flow started 1226 // Reset current X coordinate to the far left of the table 1227 $this->_current_x = $this->get_left(); 1228 1229 // Flow each cell in the row 1230 $span = 0; 1231 for ($j=0; $j<count($this->content[$i]->content); $j++) { 1232 // Skip cells covered by colspans (fake cells, anyway) 1233 if ($span == 0) { 1234 // Flow current cell 1235 // Any colspans here? 1236 $span = $this->table_have_colspan($j, $i); 1237 1238 // Get sum of width for the current cell (or several cells in colspan) 1239 // In most cases, $span == 1 here (just a single cell) 1240 $cw = array_sum(array_slice($columns, $j, $span)); 1241 1242 // store calculated width of the current cell 1243 $cell =& $this->content[$i]->content[$j]; 1244 $cell->put_full_width($cw); 1245 $cell->setCSSProperty(CSS_WIDTH, 1246 new WCConstant($cw - 1247 $cell->_get_hor_extra())); 1248 1249 // TODO: check for rowspans 1250 1251 // Flow cell 1252 $this->content[$i]->content[$j]->reflow($this, $context); 1253 1254 // Offset current X value by the cell width 1255 $this->_current_x += $cw; 1256 }; 1257 1258 // Current cell have been processed or skipped 1259 $span = max(0, $span-1); 1260 } 1261 1262 // calculate row height and do vertical align 1263 // $this->table_fit_row($i); 1264 1265 // row height calculation offset current Y coordinate by the row height calculated 1266 // $this->_current_y -= $this->table_row_height($i); 1267 $this->_current_y -= $this->content[$i]->row_height(); 1268 } 1269 1270 // Calculate (and possibly adjust height of table rows) 1271 $heights = $this->_row_heights(0.1); 1272 1273 // adjust row heights to fit cells spanning several rows 1274 foreach ($this->get_rowspans() as $rowspan) { 1275 // Get height of the cell 1276 $cell_height = $this->content[$rowspan->row]->content[$rowspan->column]->get_full_height(); 1277 1278 // Get calculated height of the spanned-over rows 1279 $cell_row_heights = array_slice($heights, $rowspan->row, $rowspan->size); 1280 1281 // Get list of non-constrained columns 1282 $flags = array_slice($this->get_non_constrained_flags(), $rowspan->row, $rowspan->size); 1283 1284 // Expand row heights (only for non-constrained columns) 1285 $new_heights = expand_to_with_flags($cell_height, 1286 $cell_row_heights, 1287 $flags); 1288 1289 // Check if rows could not be expanded 1290 // if (array_sum($new_heights) < $cell_height-1) { 1291 if (array_sum($new_heights) < $cell_height - EPSILON) { 1292 // Get list of non-constant-constrained columns 1293 $flags = array_slice($this->get_non_constant_constrained_flags(), $rowspan->row, $rowspan->size); 1294 1295 // use non-constant-constrained rows 1296 $new_heights = expand_to_with_flags($cell_height, 1297 $cell_row_heights, 1298 $flags); 1299 }; 1300 1301 // Update the rows heights 1302 array_splice($heights, 1303 $rowspan->row, 1304 $rowspan->size, 1305 $new_heights); 1306 } 1307 1308 // Now expand rows to full table height 1309 $table_height = max($this->get_height(), array_sum($heights)); 1310 1311 // Get list of non-constrained columns 1312 $flags = $this->get_non_constrained_height_flags(); 1313 1314 // Expand row heights (only for non-constrained columns) 1315 $heights = expand_to_with_flags($table_height, 1316 $heights, 1317 $flags); 1318 1319 // Check if rows could not be expanded 1320 if (array_sum($heights) < $table_height - EPSILON) { 1321 // Get list of non-constant-constrained columns 1322 $flags = $this->get_non_constant_constrained_flags(); 1323 1324 // use non-constant-constrained rows 1325 $heights = expand_to_with_flags($table_height, 1326 $heights, 1327 $flags); 1328 }; 1329 1330 // Now we calculated row heights, time to actually resize them 1331 $this->table_resize_rows($heights); 1332 1333 // Update size of cells spanning several rows 1334 $this->table_fit_rowspans($heights); 1335 1336 // Expand total table height, if needed 1337 $total_height = array_sum($heights); 1338 if ($total_height > $this->get_height()) { 1339 $hc = new HCConstraint(array($total_height, false), 1340 array($total_height, false), 1341 array($total_height, false)); 1342 $this->put_height_constraint($hc); 1343 }; 1344 } 1345 1346 function isBlockLevel() { 1347 return true; 1348 } 1349} 1350?>