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?>