PageRenderTime 4ms CodeModel.GetById 3ms app.highlight 19ms RepoModel.GetById 1ms app.codeStats 1ms

/lib/pdf/box.container.php

http://simpleinvoices.googlecode.com/
PHP | 1116 lines | 625 code | 179 blank | 312 comment | 151 complexity | 29f44f54ef079acfcb4930d6f42be2ee MD5 | raw file
   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->getCSSProperty(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->getCSSProperty(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->getCSSProperty(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->getCSSProperty(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->getCSSProperty(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]->getCSSProperty(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->getCSSProperty(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->getCSSProperty(CSS_WHITE_SPACE);
 563    $pseudo_nowrap = $this->getCSSProperty(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->getCSSProperty(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->getCSSProperty(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->getCSSProperty(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->getCSSProperty(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]->getCSSProperty(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]->getCSSProperty(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]->getCSSProperty(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]->getCSSProperty(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]->getCSSProperty(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]->getCSSProperty(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]->getCSSProperty(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]->getCSSProperty(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]->getCSSProperty(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->getCSSProperty(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->getCSSProperty(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->getCSSProperty(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?>