1<?php 2// $Header: /cvsroot/html2ps/box.container.php,v 1.68 2007/05/06 18:49:29 Konstantin Exp $ 3 4require_once(HTML2PS_DIR.'strategy.width.min.php'); 5require_once(HTML2PS_DIR.'strategy.width.min.nowrap.php'); 6require_once(HTML2PS_DIR.'strategy.width.max.php'); 7require_once(HTML2PS_DIR.'strategy.width.max.natural.php'); 8 9/** 10 * @package HTML2PS 11 * @subpackage Document 12 * 13 * This file contains the abstract class describing the behavior of document element 14 * containing some other document elements. 15 */ 16 17/** 18 * @package HTML2PS 19 * @subpackage Document 20 * 21 * The GenericContainerBox class is a common superclass for all document elements able 22 * to contain other elements. This class does provide the line-box handling utilies and 23 * some minor float related-functions. 24 * 25 */ 26class GenericContainerBox extends GenericFormattedBox { 27 /** 28 * @var Array A list of contained elements (of type GenericFormattedBox) 29 * @access public 30 */ 31 var $content; 32 33 var $_first_line; 34 35 /** 36 * @var Array A list of child nodes in the current line box; changes dynamically 37 * during the reflow process. 38 * @access private 39 */ 40 var $_line; 41 42 /** 43 * Sometimes floats may appear inside the line box, consider the following code, 44 * for example: "<div>text<div style='float:left'>float</div>word</div>". In 45 * this case, the floating DIV should be rendered below the "text word" line; 46 * thus, we need to keep a list of deferred floating elements and render them 47 * when current line box closes. 48 * 49 * @var Array A list of floats which should be flown after current line box ends; 50 * @access private 51 */ 52 var $_deferred_floats; 53 54 /** 55 * @var float Current output X value inside the current element 56 * @access public 57 */ 58 var $_current_x; 59 60 /** 61 * @var float Current output Y value inside the current element 62 * @access public 63 */ 64 var $_current_y; 65 66 function destroy() { 67 for ($i=0, $size = count($this->content); $i < $size; $i++) { 68 $this->content[$i]->destroy(); 69 }; 70 unset($this->content); 71 72 parent::destroy(); 73 } 74 75 /** 76 * Render current container box using the specified output method. 77 * 78 * @param OutputDriver $driver The output driver object 79 * 80 * @return Boolean flag indicating the success or 'null' value in case of critical rendering 81 * error 82 */ 83 function show(&$driver) { 84 GenericFormattedBox::show($driver); 85 86 $overflow = $this->get_css_property(CSS_OVERFLOW); 87 88 /** 89 * Sometimes the content may overflow container boxes. This situation arise, for example, 90 * for relative-positioned child boxes, boxes having constrained height and in some 91 * other cases. If the container box does not have CSS 'overflow' property 92 * set to 'visible' value, the content should be visually clipped using container box 93 * padding area. 94 */ 95 if ($overflow !== OVERFLOW_VISIBLE) { 96 $driver->save(); 97 $this->_setupClip($driver); 98 }; 99 100 /** 101 * Render child elements 102 */ 103 for ($i=0, $size = count($this->content); $i < $size; $i++) { 104 $child =& $this->content[$i]; 105 106 /** 107 * We'll check the visibility property here 108 * Reason: all boxes (except the top-level one) are contained in some other box, 109 * so every box will pass this check. The alternative is to add this check into every 110 * box class show member. 111 * 112 * The only exception of absolute positioned block boxes which are drawn separately; 113 * their show method is called explicitly; the similar check should be performed there 114 */ 115 if ($child->isVisibleInFlow()) { 116 /** 117 * To reduce the drawing overhead, we'll check if some part if current child element 118 * belongs to current output page. If not, there will be no reason to draw this 119 * child this time. 120 * 121 * @see OutputDriver::contains() 122 * 123 * @todo In rare cases the element content may be placed outside the element itself; 124 * in such situantion content may be visible on the page, while element is not. 125 * This situation should be resolved somehow. 126 */ 127 if ($driver->contains($child)) { 128 if (is_null($child->show($driver))) { 129 return null; 130 }; 131 }; 132 }; 133 } 134 135 /** 136 * Restore previous clipping mode, if it have been modified for non-'overflow: visible' 137 * box. 138 */ 139 if ($overflow !== OVERFLOW_VISIBLE) { 140 $driver->restore(); 141 }; 142 143 return true; 144 } 145 146 /** 147 * Render current fixed-positioned container box using the specified output method. Unlike 148 * the 'show' method, there's no check if current page viewport contains current element, as 149 * fixed-positioned may be drawn on the page margins, outside the viewport. 150 * 151 * @param OutputDriver $driver The output driver object 152 * 153 * @return Boolean flag indicating the success or 'null' value in case of critical rendering 154 * error 155 * 156 * @see GenericContainerBox::show() 157 * 158 * @todo the 'show' and 'show_fixed' method code are almost the same except the child element 159 * method called in the inner loop; also, no check is done if current viewport contains this element, 160 * thus sllowinf printing data on page margins, where no data should be printed normally 161 * I suppose some more generic method containing the common code should be made. 162 */ 163 function show_fixed(&$driver) { 164 GenericFormattedBox::show($driver); 165 166 $overflow = $this->get_css_property(CSS_OVERFLOW); 167 168 /** 169 * Sometimes the content may overflow container boxes. This situation arise, for example, 170 * for relative-positioned child boxes, boxes having constrained height and in some 171 * other cases. If the container box does not have CSS 'overflow' property 172 * set to 'visible' value, the content should be visually clipped using container box 173 * padding area. 174 */ 175 if ($overflow !== OVERFLOW_VISIBLE) { 176 // Save graphics state (of course, BEFORE the clipping area will be set) 177 $driver->save(); 178 $this->_setupClip($driver); 179 }; 180 181 /** 182 * Render child elements 183 */ 184 $size = count($this->content); 185 for ($i=0; $i < $size; $i++) { 186 /** 187 * We'll check the visibility property here 188 * Reason: all boxes (except the top-level one) are contained in some other box, 189 * so every box will pass this check. The alternative is to add this check into every 190 * box class show member. 191 * 192 * The only exception of absolute positioned block boxes which are drawn separately; 193 * their show method is called explicitly; the similar check should be performed there 194 */ 195 $child =& $this->content[$i]; 196 if ($child->get_css_property(CSS_VISIBILITY) === VISIBILITY_VISIBLE) { 197 // Fixed-positioned blocks are displayed separately; 198 // If we call them now, they will be drawn twice 199 if ($child->get_css_property(CSS_POSITION) != POSITION_FIXED) { 200 if (is_null($child->show_fixed($driver))) { 201 return null; 202 }; 203 }; 204 }; 205 } 206 207 /** 208 * Restore previous clipping mode, if it have been modified for non-'overflow: visible' 209 * box. 210 */ 211 if ($overflow !== OVERFLOW_VISIBLE) { 212 $driver->restore(); 213 }; 214 215 return true; 216 } 217 218 function _find(&$box) { 219 $size = count($this->content); 220 for ($i=0; $i<$size; $i++) { 221 if ($this->content[$i]->uid == $box->uid) { 222 return $i; 223 }; 224 } 225 return null; 226 } 227 228 // Inserts new child box at the specified (zero-based) offset; 0 stands for first child 229 // 230 // @param $index index to insert child at 231 // @param $box child to be inserted 232 // 233 function insert_child($index, &$box) { 234 $box->parent =& $this; 235 236 // Offset the content array 237 for ($i = count($this->content)-1; $i>= $index; $i--) { 238 $this->content[$i+1] =& $this->content[$i]; 239 }; 240 241 $this->content[$index] =& $box; 242 } 243 244 function insert_before(&$what, &$where) { 245 if ($where) { 246 $index = $this->_find($where); 247 248 if (is_null($index)) { 249 return null; 250 }; 251 252 $this->insert_child($index, $what); 253 } else { 254 // If 'where' is not specified, 'what' should become the last child 255 $this->add_child($what); 256 }; 257 258 return $what; 259 } 260 261 function add_child(&$box) { 262 $this->append_child($box); 263 } 264 265 function append_child(&$box) { 266 // In general, this function is called like following: 267 // $box->add_child(create_pdf_box(...)) 268 // As create_pdf_box _may_ return null value (for example, for an empty text node), 269 // we should process the case of $box == null here 270 if ($box) { 271 $box->parent =& $this; 272 $this->content[] =& $box; 273 }; 274 } 275 276 // Get first child of current box which actually will be drawn 277 // on the page. So, whitespace and null boxes will be ignored 278 // 279 // See description of is_null for null box definition. 280 // (not only NullBox is treated as null box) 281 // 282 // @return reference to the first visible child of current box 283 function &get_first() { 284 $size = count($this->content); 285 for ($i=0; $i<$size; $i++) { 286 if (!is_whitespace($this->content[$i]) && 287 !$this->content[$i]->is_null()) { 288 return $this->content[$i]; 289 }; 290 }; 291 292 // We use this construct to avoid notice messages in PHP 4.4 and PHP 5 293 $dummy = null; 294 return $dummy; 295 } 296 297 // Get first text or image child of current box which actually will be drawn 298 // on the page. 299 // 300 // See description of is_null for null box definition. 301 // (not only NullBox is treated as null box) 302 // 303 // @return reference to the first visible child of current box 304 function &get_first_data() { 305 $size = count($this->content); 306 for ($i=0; $i<$size; $i++) { 307 if (!is_whitespace($this->content[$i]) && !$this->content[$i]->is_null()) { 308 if (is_container($this->content[$i])) { 309 $data =& $this->content[$i]->get_first_data(); 310 if (!is_null($data)) { return $data; }; 311 } else { 312 return $this->content[$i]; 313 }; 314 }; 315 }; 316 317 // We use this construct to avoid notice messages in PHP 4.4 and PHP 5 318 $dummy = null; 319 return $dummy; 320 } 321 322 // Get last child of current box which actually will be drawn 323 // on the page. So, whitespace and null boxes will be ignored 324 // 325 // See description of is_null for null box definition. 326 // (not only NullBox is treated as null box) 327 // 328 // @return reference to the last visible child of current box 329 function &get_last() { 330 for ($i=count($this->content)-1; $i>=0; $i--) { 331 if (!is_whitespace($this->content[$i]) && !$this->content[$i]->is_null()) { 332 return $this->content[$i]; 333 }; 334 }; 335 336 // We use this construct to avoid notice messages in PHP 4.4 and PHP 5 337 $dummy = null; 338 return $dummy; 339 } 340 341 function offset_if_first(&$box, $dx, $dy) { 342 if ($this->is_first($box)) { 343 // The top-level box (page box) should never be offset 344 if ($this->parent) { 345 if (!$this->parent->offset_if_first($box, $dx, $dy)) { 346 $this->offset($dx, $dy); 347 return true; 348 }; 349 }; 350 }; 351 return false; 352 } 353 354 function offset($dx, $dy) { 355 parent::offset($dx, $dy); 356 357 $this->_current_x += $dx; 358 $this->_current_y += $dy; 359 360 // Offset contents 361 $size = count($this->content); 362 for ($i=0; $i < $size; $i++) { 363 $this->content[$i]->offset($dx, $dy); 364 } 365 } 366 367 function GenericContainerBox() { 368 $this->GenericFormattedBox(); 369 370 // By default, box does not have any content 371 $this->content = array(); 372 373 // Initialize line box 374 $this->_line = array(); 375 376 // Initialize floats-related stuff 377 $this->_deferred_floats = array(); 378 379 $this->_additional_text_indent = 0; 380 381 // Current-point 382 $this->_current_x = 0; 383 $this->_current_y = 0; 384 385 // Initialize floating children array 386 $this->_floats = array(); 387 } 388 389 function add_deferred_float(&$float) { 390 $this->_deferred_floats[] =& $float; 391 } 392 393 /** 394 * Create the child nodes of current container object using the parsed HTML data 395 * 396 * @param mixed $root node corresponding to the current container object 397 */ 398 function create_content(&$root, &$pipeline) { 399 // Initialize content 400 $child = $root->first_child(); 401 while ($child) { 402 $box_child =& create_pdf_box($child, $pipeline); 403 $this->add_child($box_child); 404 $child = $child->next_sibling(); 405 }; 406 } 407 408 // Content-handling functions 409 410 function is_container() { 411 return true; 412 } 413 414 function get_content() { 415 return join('', array_map(array($this, 'get_content_callback'), $this->content)); 416 } 417 418 function get_content_callback(&$node) { 419 return $node->get_content(); 420 } 421 422 // Get total height of this box content (including floats, if any) 423 // Note that floats can be contained inside children, so we'll need to use 424 // this function recusively 425 function get_real_full_height() { 426 $content_size = count($this->content); 427 428 $overflow = $this->get_css_property(CSS_OVERFLOW); 429 430 // Treat items with overflow: hidden specifically, 431 // as floats flown out of this boxes will not be visible 432 if ($overflow == OVERFLOW_HIDDEN) { 433 return $this->get_full_height(); 434 }; 435 436 // Check if this object is totally empty 437 $first = $this->get_first(); 438 if (is_null($first)) { 439 return 0; 440 }; 441 442 // Initialize the vertical extent taken by content using the 443 // very first child 444 $max_top = $first->get_top_margin(); 445 $min_bottom = $first->get_bottom_margin(); 446 447 for ($i=0; $i<$content_size; $i++) { 448 if (!$this->content[$i]->is_null()) { 449 // Check if top margin of current child is to the up 450 // of vertical extent top margin 451 $max_top = max($max_top, $this->content[$i]->get_top_margin()); 452 453 /** 454 * Check if current child bottom margin will extend 455 * the vertical space OR if it contains floats extending 456 * this, unless this child have overflow: hidden, because this 457 * will prevent additional content to be visible 458 */ 459 if (!$this->content[$i]->is_container()) { 460 $min_bottom = min($min_bottom, 461 $this->content[$i]->get_bottom_margin()); 462 } else { 463 $content_overflow = $this->content[$i]->get_css_property(CSS_OVERFLOW); 464 465 if ($content_overflow == OVERFLOW_HIDDEN) { 466 $min_bottom = min($min_bottom, 467 $this->content[$i]->get_bottom_margin()); 468 } else { 469 $min_bottom = min($min_bottom, 470 $this->content[$i]->get_bottom_margin(), 471 $this->content[$i]->get_top_margin() - 472 $this->content[$i]->get_real_full_height()); 473 }; 474 }; 475 }; 476 } 477 478 return max(0, $max_top - $min_bottom) + $this->_get_vert_extra(); 479 } 480 481 // LINE-LENGTH RELATED FUNCTIONS 482 483 function _line_length() { 484 $sum = 0; 485 $size = count($this->_line); 486 487 for ($i=0; $i < $size; $i++) { 488 // Note that the line length should include the inline boxes margin/padding 489 // as inline boxes are not directly included to the parent line box, 490 // we'll need to check the parent of current line box element, 491 // and, if it is an inline box, AND this element is last or first contained element 492 // add correcponsing padding value 493 $element =& $this->_line[$i]; 494 495 if (isset($element->wrapped) && !is_null($element->wrapped)) { 496 if ($i==0) { 497 $sum += $element->get_full_width() - $element->getWrappedWidth(); 498 } else { 499 $sum += $element->getWrappedWidthAndHyphen(); 500 }; 501 } else { 502 $sum += $element->get_full_width(); 503 }; 504 505 if ($element->parent) { 506 $first = $element->parent->get_first(); 507 $last = $element->parent->get_last(); 508 509 if (!is_null($first) && $first->uid === $element->uid) { 510 $sum += $element->parent->get_extra_line_left(); 511 } 512 513 if (!is_null($last) && $last->uid === $element->uid) { 514 $sum += $element->parent->get_extra_line_right(); 515 } 516 }; 517 } 518 519 if ($this->_first_line) { 520 $ti = $this->get_css_property(CSS_TEXT_INDENT); 521 $sum += $ti->calculate($this); 522 $sum += $this->_additional_text_indent; 523 }; 524 525 return $sum; 526 } 527 528 function _line_length_delta(&$context) { 529 return max($this->get_available_width($context) - $this->_line_length(),0); 530 } 531 532 /** 533 * Get the last box in current line box 534 */ 535 function &last_in_line() { 536 $size = count($this->_line); 537 if ($size < 1) { 538 $dummy = null; 539 return $dummy; 540 }; 541 542 return $this->_line[$size-1]; 543 } 544 545 // WIDTH 546 547 function get_min_width_natural(&$context) { 548 $content_size = count($this->content); 549 550 /** 551 * If box does not have any context, its minimal width is determined by extra horizontal space: 552 * padding, border width and margins 553 */ 554 if ($content_size == 0) { 555 $min_width = $this->_get_hor_extra(); 556 return $min_width; 557 }; 558 559 /** 560 * If we're in 'nowrap' mode, minimal and maximal width will be equal 561 */ 562 $white_space = $this->get_css_property(CSS_WHITE_SPACE); 563 $pseudo_nowrap = $this->get_css_property(CSS_HTML2PS_NOWRAP); 564 if ($white_space == WHITESPACE_NOWRAP || 565 $pseudo_nowrap == NOWRAP_NOWRAP) { 566 $min_width = $this->get_min_nowrap_width($context); 567 return $min_width; 568 } 569 570 /** 571 * We need to add text indent size to the width of the first item 572 */ 573 $start_index = 0; 574 while ($start_index < $content_size && 575 $this->content[$start_index]->out_of_flow()) { 576 $start_index++; 577 }; 578 579 if ($start_index < $content_size) { 580 $ti = $this->get_css_property(CSS_TEXT_INDENT); 581 $minw = 582 $ti->calculate($this) + 583 $this->content[$start_index]->get_min_width_natural($context); 584 } else { 585 $minw = 0; 586 }; 587 588 for ($i=$start_index; $i<$content_size; $i++) { 589 $item =& $this->content[$i]; 590 if (!$item->out_of_flow()) { 591 $minw = max($minw, $item->get_min_width($context)); 592 }; 593 } 594 595 /** 596 * Apply width constraint to min width. Return maximal value 597 */ 598 $wc = $this->get_css_property(CSS_WIDTH); 599 $containing_block =& $this->_get_containing_block(); 600 601 $min_width = $minw; 602 return $min_width; 603 } 604 605 function get_min_width(&$context) { 606 $strategy = new StrategyWidthMin(); 607 return $strategy->apply($this, $context); 608 } 609 610 function get_min_nowrap_width(&$context) { 611 $strategy = new StrategyWidthMinNowrap(); 612 return $strategy->apply($this, $context); 613 } 614 615 // Note: <table width="100%" inside some block box cause this box to expand 616 // $limit - maximal width which should not be exceeded; by default, there's no limit at all 617 // 618 function get_max_width_natural(&$context, $limit=10E6) { 619 $strategy = new StrategyWidthMaxNatural($limit); 620 return $strategy->apply($this, $context); 621 } 622 623 function get_max_width(&$context, $limit=10E6) { 624 $strategy = new StrategyWidthMax($limit); 625 return $strategy->apply($this, $context); 626 } 627 628 function close_line(&$context, $lastline = false) { 629 // Align line-box using 'text-align' property 630 $size = count($this->_line); 631 632 if ($size > 0) { 633 $last_item =& $this->_line[$size-1]; 634 if (is_whitespace($last_item)) { 635 $last_item->width = 0; 636 $last_item->height = 0; 637 }; 638 }; 639 640 // Note that text-align should not be applied to the block boxes! 641 // As block boxes will be alone in the line-box, we can check 642 // if the very first box in the line is inline; if not - no justification should be made 643 // 644 if ($size > 0) { 645 if (is_inline($this->_line[0])) { 646 $cb = CSSTextAlign::value2pdf($this->get_css_property(CSS_TEXT_ALIGN)); 647 $cb($this, $context, $lastline); 648 } else { 649 // Nevertheless, CENTER tag and P/DIV with ALIGN attribute set should affect the 650 // position of non-inline children. 651 $cb = CSSPseudoAlign::value2pdf($this->get_css_property(CSS_HTML2PS_ALIGN)); 652 $cb($this, $context, $lastline); 653 }; 654 }; 655 656 // Apply vertical align to all of the line content 657 // first, we need to aling all baseline-aligned boxes to determine the basic line-box height, top and bottom edges 658 // then, SUP and SUP positioned boxes (as they can extend the top and bottom edges, but not affected themselves) 659 // then, MIDDLE, BOTTOM and TOP positioned boxes in the given order 660 // 661 $baselined = array(); 662 $baseline = 0; 663 $height = 0; 664 for ($i=0; $i < $size; $i++) { 665 $vertical_align = $this->_line[$i]->get_css_property(CSS_VERTICAL_ALIGN); 666 667 if ($vertical_align == VA_BASELINE) { 668 // Add current baseline-aligned item to the baseline 669 // 670 $baselined[] =& $this->_line[$i]; 671 672 $baseline = max($baseline, 673 $this->_line[$i]->default_baseline); 674 }; 675 }; 676 677 $size_baselined = count($baselined); 678 for ($i=0; $i < $size_baselined; $i++) { 679 $baselined[$i]->baseline = $baseline; 680 681 $height = max($height, 682 $baselined[$i]->get_full_height() + $baselined[$i]->getBaselineOffset(), 683 $baselined[$i]->get_ascender() + $baselined[$i]->get_descender()); 684 685 }; 686 687 // SUB vertical align 688 // 689 for ($i=0; $i < $size; $i++) { 690 $vertical_align = $this->_line[$i]->get_css_property(CSS_VERTICAL_ALIGN); 691 if ($vertical_align == VA_SUB) { 692 $this->_line[$i]->baseline = 693 $baseline + $this->_line[$i]->get_full_height()/2; 694 }; 695 } 696 697 // SUPER vertical align 698 // 699 for ($i=0; $i < $size; $i++) { 700 $vertical_align = $this->_line[$i]->get_css_property(CSS_VERTICAL_ALIGN); 701 if ($vertical_align == VA_SUPER) { 702 $this->_line[$i]->baseline = $this->_line[$i]->get_full_height()/2; 703 }; 704 } 705 706 // MIDDLE vertical align 707 // 708 $middle = 0; 709 for ($i=0; $i < $size; $i++) { 710 $vertical_align = $this->_line[$i]->get_css_property(CSS_VERTICAL_ALIGN); 711 if ($vertical_align == VA_MIDDLE) { 712 $middle = max($middle, $this->_line[$i]->get_full_height() / 2); 713 }; 714 }; 715 716 if ($middle * 2 > $height) { 717 // Offset already aligned items 718 // 719 for ($i=0; $i < $size; $i++) { 720 $this->_line[$i]->baseline += ($middle - $height/2); 721 }; 722 $height = $middle * 2; 723 }; 724 725 for ($i=0; $i < $size; $i++) { 726 $vertical_align = $this->_line[$i]->get_css_property(CSS_VERTICAL_ALIGN); 727 if ($vertical_align == VA_MIDDLE) { 728 $this->_line[$i]->baseline = $this->_line[$i]->default_baseline + ($height/2 - $this->_line[$i]->get_full_height()/2); 729 }; 730 } 731 732 // BOTTOM vertical align 733 // 734 $bottom = 0; 735 for ($i=0; $i < $size; $i++) { 736 $vertical_align = $this->_line[$i]->get_css_property(CSS_VERTICAL_ALIGN); 737 if ($vertical_align == VA_BOTTOM) { 738 $bottom = max($bottom, $this->_line[$i]->get_full_height()); 739 }; 740 }; 741 742 if ($bottom > $height) { 743 // Offset already aligned items 744 // 745 for ($i=0; $i < $size; $i++) { 746 $this->_line[$i]->baseline += ($bottom - $height); 747 }; 748 $height = $bottom; 749 }; 750 751 for ($i=0; $i < $size; $i++) { 752 $vertical_align = $this->_line[$i]->get_css_property(CSS_VERTICAL_ALIGN); 753 if ($vertical_align == VA_BOTTOM) { 754 $this->_line[$i]->baseline = $this->_line[$i]->default_baseline + $height - $this->_line[$i]->get_full_height(); 755 }; 756 } 757 758 // TOP vertical align 759 // 760 $bottom = 0; 761 for ($i=0; $i < $size; $i++) { 762 $vertical_align = $this->_line[$i]->get_css_property(CSS_VERTICAL_ALIGN); 763 if ($vertical_align == VA_TOP) { 764 $bottom = max($bottom, $this->_line[$i]->get_full_height()); 765 }; 766 }; 767 768 if ($bottom > $height) { 769 $height = $bottom; 770 }; 771 772 for ($i=0; $i < $size; $i++) { 773 $vertical_align = $this->_line[$i]->get_css_property(CSS_VERTICAL_ALIGN); 774 if ($vertical_align == VA_TOP) { 775 $this->_line[$i]->baseline = $this->_line[$i]->default_baseline; 776 }; 777 } 778 779 // Calculate the bottom Y coordinate of last line box 780 // 781 $line_bottom = $this->_current_y; 782 foreach ($this->_line AS $line_element) { 783 // This line is required; say, we have sequence of text and image inside the container, 784 // AND image have greater baseline than text; in out case, text will be offset to the bottom 785 // of the page and we lose the gap between text and container bottom edge, unless we'll re-extend 786 // containier height 787 788 // Note that we're using the colapsed margin value to get the Y coordinate to extend height to, 789 // as bottom margin may be collapsed with parent 790 791 $effective_bottom = 792 $line_element->get_top() - 793 $line_element->get_height() - 794 $line_element->get_extra_bottom(); 795 796 $this->extend_height($effective_bottom); 797 $line_bottom = min($effective_bottom, $line_bottom); 798 } 799 800 $this->extend_height($line_bottom); 801 802 // Clear the line box 803 $this->_line = array(); 804 805 // Reset current X coordinate to the far left 806 $this->_current_x = $this->get_left(); 807 808 // Extend Y coordinate 809 $this->_current_y = $line_bottom; 810 811 // Render the deferred floats 812 for ($i = 0, $size = count($this->_deferred_floats); $i < $size; $i++) { 813 $this->_deferred_floats[$i]->reflow_static_float($this, $context); 814 }; 815 // Clear deferred float list 816 $this->_deferred_floats = array(); 817 818 // modify the current-x value, so that next inline box will not intersect any floating boxes 819 $this->_current_x = $context->float_left_x($this->_current_x, $this->_current_y); 820 821 $this->_first_line = false; 822 } 823 824 function append_line(&$item) { 825 $this->_line[] =& $item; 826 } 827 828 // Line box should be treated as empty in following cases: 829 // 1. It is really empty (so, it contains 0 boxes) 830 // 2. It contains only whitespace boxes 831 function line_box_empty() { 832 $size = count($this->_line); 833 if ($size == 0) { return true; } 834 835 // Scan line box 836 for ($i=0; $i<$size; $i++) { 837 if (!is_whitespace($this->_line[$i]) && 838 !$this->_line[$i]->is_null()) { return false; }; 839 } 840 841 // No non-whitespace boxes were found 842 return true; 843 } 844 845 function reflow_anchors(&$viewport, &$anchors, $page_heights) { 846 GenericFormattedBox::reflow_anchors($viewport, $anchors, $page_heights); 847 848 $size = count($this->content); 849 for ($i=0; $i<$size; $i++) { 850 $this->content[$i]->reflow_anchors($viewport, $anchors, $page_heights); 851 } 852 } 853 854 function fitFloats(&$context) { 855 $float_bottom = $context->float_bottom(); 856 if (!is_null($float_bottom)) { 857 $this->extend_height($float_bottom); 858 }; 859 860 $float_right = $context->float_right(); 861 if (!is_null($float_right)) { 862 $this->extend_width($float_right); 863 }; 864 } 865 866 function reflow_content(&$context) { 867 $text_indent = $this->get_css_property(CSS_TEXT_INDENT); 868 869 $this->close_line($context); 870 871 $this->_first_line = true; 872 873 // If first child is inline - apply text-indent 874 $first = $this->get_first(); 875 if (!is_null($first)) { 876 if (is_inline($first)) { 877 $this->_current_x += $text_indent->calculate($this); 878 $this->_current_x += $this->_additional_text_indent; 879 }; 880 }; 881 882 $this->height = 0; 883 // Reset current Y value 884 $this->_current_y = $this->get_top(); 885 886 $size = count($this->content); 887 for ($i=0; $i < $size; $i++) { 888 $child =& $this->content[$i]; 889 $child->reflow($this, $context); 890 }; 891 892 $this->close_line($context, true); 893 } 894 895 function reflow_inline() { 896 $size = count($this->content); 897 for ($i=0; $i<$size; $i++) { 898 $this->content[$i]->reflow_inline(); 899 }; 900 } 901 902 function reflow_text(&$viewport) { 903 $size = count($this->content); 904 for ($i=0; $i<$size; $i++) { 905 if (is_null($this->content[$i]->reflow_text($viewport))) { 906 return null; 907 }; 908 } 909 return true; 910 } 911 912 /** 913 * Position/size current box as floating one 914 */ 915 function reflow_static_float(&$parent, &$context) { 916 // Defer the float rendering till the next line box 917 if (!$parent->line_box_empty()) { 918 $parent->add_deferred_float($this); 919 return; 920 }; 921 922 // Calculate margin values if they have been set as a percentage 923 $this->_calc_percentage_margins($parent); 924 $this->_calc_percentage_padding($parent); 925 926 // Calculate width value if it have been set as a percentage 927 $this->_calc_percentage_width($parent, $context); 928 929 // Calculate margins and/or width is 'auto' values have been specified 930 $this->_calc_auto_width_margins($parent); 931 932 // Determine the actual width of the floating box 933 // Note that get_max_width returns both content and extra width 934 $this->put_full_width($this->get_max_width_natural($context, $this->parent->get_width())); 935 936 // We need to call this function before determining the horizontal coordinate 937 // as after vertical offset the additional space to the left may apperar 938 $y = $this->apply_clear($parent->_current_y, $context); 939 940 // determine the position of top-left floating box corner 941 if ($this->get_css_property(CSS_FLOAT) === FLOAT_RIGHT) { 942 $context->float_right_xy($parent, $this->get_full_width(), $x, $y); 943 $x -= $this->get_full_width(); 944 } else { 945 $context->float_left_xy($parent, $this->get_full_width(), $x, $y); 946 }; 947 948 // Note that $x and $y contain just a free space corner coordinate; 949 // If our float has a margin/padding space, we'll need to offset ot a little; 950 // Remember that float margins are never collapsed! 951 $this->moveto($x + $this->get_extra_left(), $y - $this->get_extra_top()); 952 953 // Reflow contents. 954 // Note that floating box creates a new float flow context for it children. 955 956 $context->push_floats(); 957 958 // Floating box create a separate margin collapsing context 959 $context->push_collapsed_margin(0); 960 961 $this->reflow_content($context); 962 963 $context->pop_collapsed_margin(); 964 965 // Floats and boxes with overflow: hidden 966 // should completely enclose its child floats 967 $this->fitFloats($context); 968 969 // restore old float flow context 970 $context->pop_floats(); 971 972 // Add this box to the list of floats in current context 973 $context->add_float($this); 974 975 // Now fix the value of _current_x for the parent box; it is required 976 // in the following case: 977 // <body><img align="left">some text 978 // in such situation floating image is flown immediately, but it the close_line call have been made before, 979 // so _current_x value of container box will be still equal to ots left content edge; by calling float_left_x again, 980 // we'll force "some text" to be offset to the right 981 $parent->_current_x = $context->float_left_x($parent->_current_x, $parent->_current_y); 982 } 983 984 function reflow_whitespace(&$linebox_started, &$previous_whitespace) { 985 $previous_whitespace = false; 986 $linebox_started = false; 987 988 $size = count($this->content); 989 for ($i=0; $i<$size; $i++) { 990 $child =& $this->content[$i]; 991 992 $child->reflow_whitespace($linebox_started, $previous_whitespace); 993 }; 994 995 // remove the last whitespace in block box 996 $this->remove_last_whitespace(); 997 998 // Non-inline box have terminated; we may be sure that line box will be closed 999 // at this moment and new line box after this will be generated 1000 if (!is_inline($this)) { 1001 $linebox_started = false; 1002 }; 1003 1004 return; 1005 } 1006 1007 function remove_last_whitespace() { 1008 if (count($this->content) == 0) { 1009 return; 1010 }; 1011 1012 $i = count($this->content)-1; 1013 $last = $this->content[$i]; 1014 while ($i >= 0 && is_whitespace($this->content[$i])) { 1015 $this->remove($this->content[$i]); 1016 1017 $i --; 1018 if ($i >= 0) { 1019 $last = $this->content[$i]; 1020 }; 1021 }; 1022 1023 if ($i >= 0) { 1024 if (is_container($this->content[$i])) { 1025 $this->content[$i]->remove_last_whitespace(); 1026 }; 1027 }; 1028 } 1029 1030 function remove(&$box) { 1031 $size = count($this->content); 1032 for ($i=0; $i<$size; $i++) { 1033 if ($this->content[$i]->uid === $box->uid) { 1034 $this->content[$i] = NullBox::create(); 1035 }; 1036 }; 1037 1038 return; 1039 } 1040 1041 function is_first(&$box) { 1042 $first =& $this->get_first(); 1043 1044 // Check if there's no first box at all 1045 // 1046 if (is_null($first)) { return false; }; 1047 1048 return $first->uid == $box->uid; 1049 } 1050 1051 function is_null() { 1052 $size = count($this->content); 1053 for ($i=0; $i<$size; $i++) { 1054 if (!$this->content[$i]->is_null()) { return false; }; 1055 }; 1056 return true; 1057 } 1058 1059 // Calculate the available widths - e.g. content width minus space occupied by floats; 1060 // as floats may not fill the whole height of this box, this value depends on Y-coordinate. 1061 // We use current_Y in calculations 1062 // 1063 function get_available_width(&$context) { 1064 $left_float_width = $context->float_left_x($this->get_left(), $this->_current_y) - $this->get_left(); 1065 $right_float_width = $this->get_right() - $context->float_right_x($this->get_right(), $this->_current_y); 1066 return $this->get_width() - $left_float_width - $right_float_width; 1067 } 1068 1069 function pre_reflow_images() { 1070 $size = count($this->content); 1071 for ($i=0; $i<$size; $i++) { 1072 $this->content[$i]->pre_reflow_images(); 1073 }; 1074 } 1075 1076 function _setupClip(&$driver) { 1077 if (!is_null($this->parent)) { 1078 $this->parent->_setupClip($driver); 1079 }; 1080 1081 $overflow = $this->get_css_property(CSS_OVERFLOW); 1082 if ($overflow !== OVERFLOW_VISIBLE && !$GLOBALS['g_config']['debugnoclip']) { 1083 $driver->moveto( $this->get_left_border() , $this->get_top_border()); 1084 $driver->lineto( $this->get_right_border(), $this->get_top_border()); 1085 $driver->lineto( $this->get_right_border(), $this->get_bottom_border()); 1086 $driver->lineto( $this->get_left_border() , $this->get_bottom_border()); 1087 $driver->closepath(); 1088 $driver->clip(); 1089 }; 1090 } 1091 1092 /** 1093 * DOMish functions 1094 */ 1095 function &get_element_by_id($id) { 1096 if (isset($GLOBALS['__html_box_id_map'])) { 1097 return $GLOBALS['__html_box_id_map'][$id]; 1098 } else { 1099 $dummy = null; 1100 return $dummy; 1101 }; 1102 } 1103 1104 /* 1105 * this is just a fake at the moment 1106 */ 1107 function get_body() { 1108 return $this; 1109 } 1110 1111 function getChildNodes() { 1112 return $this->content; 1113 } 1114} 1115 1116?>