1<?php
2// $Header: /cvsroot/html2ps/box.block.php,v 1.56 2007/01/24 18:55:43 Konstantin Exp $
3
4/**
5 * @package HTML2PS
6 * @subpackage Document
7 *
8 * Class defined in this file handles the layout of block HTML elements
9 */
10
11/**
12 * @package HTML2PS
13 * @subpackage Document
14 *
15 * The BlockBox class describes the layout and behavior of HTML element having
16 * 'display: block' CSS property.
17 *
18 * @link http://www.w3.org/TR/CSS21/visuren.html#block-box CSS 2.1 Block-level elements and block boxes
19 */
20class BlockBox extends GenericContainerBox {
21  /**
22   * Create empty block element
23   */
24  function BlockBox() {
25    $this->GenericContainerBox();
26  }
27
28  /**
29   * Create new block element and automatically fill in its contents using
30   * parsed HTML data
31   *
32   * @param mixed $root the HTML element corresponding to the element being created
33   *
34   * @return BlockBox new BlockBox object (with contents filled)
35   *
36   * @see GenericContainerBox::create_content()
37   */
38  function &create(&$root, &$pipeline) {
39    $box = new BlockBox();
40    $box->readCSS($pipeline->get_current_css_state());
41    $box->create_content($root, $pipeline);
42    return $box;
43  }
44
45  /**
46   * Create new block element and automatically initialize its contents
47   * with the given text string
48   *
49   * @param string $content The text string to be put inside the block box
50   *
51   * @return BlockBox new BlockBox object (with contents filled)
52   *
53   * @see InlineBox
54   * @see InlineBox::create_from_text()
55   */
56  function &create_from_text($content, &$pipeline) {
57    $box = new BlockBox();
58    $box->readCSS($pipeline->get_current_css_state());
59    $box->add_child(InlineBox::create_from_text($content,
60                                                $box->get_css_property(CSS_WHITE_SPACE),
61                                                $pipeline));
62    return $box;
63  }
64
65  /**
66   * Layout current block element
67   *
68   * @param GenericContainerBox $parent The document element which should be treated as the parent of current element
69   * @param FlowContext $context The flow context containing the additional layout data
70   *
71   * @see FlowContext
72   * @see GenericContainerBox
73   * @see InlineBlockBox::reflow
74   *
75   * @todo this 'reflow' skeleton is common for all element types; thus, we probably should move the generic 'reflow'
76   * definition to the GenericFormattedBox class, leaving only box-specific 'reflow_static' definitions in specific classes.
77   *
78   * @todo make relative positioning more CSS 2.1 compliant; currently, 'bottom' and 'right' CSS properties are ignored.
79   *
80   * @todo check whether percentage values should be really ignored during relative positioning
81   */
82  function reflow(&$parent, &$context) {
83    switch ($this->get_css_property(CSS_POSITION)) {
84    case POSITION_STATIC:
85      $this->reflow_static($parent, $context);
86      return;
87
88    case POSITION_RELATIVE:
89      /**
90       * CSS 2.1:
91       * Once a box has been laid out according to the normal flow or floated, it may be shifted relative
92       * to this position. This is called relative positioning. Offsetting a box (B1) in this way has no
93       * effect on the box (B2) that follows: B2 is given a position as if B1 were not offset and B2 is
94       * not re-positioned after B1's offset is applied. This implies that relative positioning may cause boxes
95       * to overlap. However, if relative positioning causes an 'overflow:auto' box to have overflow, the UA must
96       * allow the user to access this content, which, through the creation of scrollbars, may affect layout.
97       *
98       * @link http://www.w3.org/TR/CSS21/visuren.html#x28 CSS 2.1 Relative positioning
99       */
100      $this->reflow_static($parent, $context);
101      $this->offsetRelative();
102      return;
103
104    case POSITION_ABSOLUTE:
105      /**
106       * If this box is positioned absolutely, it is not laid out as usual box;
107       * The reference to this element is stored in the flow context for
108       * futher reference.
109       */
110      $this->guess_corner($parent);
111      return;
112
113    case POSITION_FIXED:
114      /**
115       * If this box have 'position: fixed', it is not laid out as usual box;
116       * The reference to this element is stored in the flow context for
117       * futher reference.
118       */
119      $this->guess_corner($parent);
120      return;
121    };
122  }
123
124  /**
125   * Reflow absolutely positioned block box. Note that according to CSS 2.1
126   * the only types of boxes which could be absolutely positioned are
127   * 'block' and 'table'
128   *
129   * @param FlowContext $context A flow context object containing the additional layout data.
130   *
131   * @link http://www.w3.org/TR/CSS21/visuren.html#dis-pos-flo CSS 2.1: Relationships between 'display', 'position', and 'float'
132   */
133  function reflow_absolute(&$context) {
134    $parent_node =& $this->get_parent_node();
135    parent::reflow($parent_node, $context);
136
137    $width_strategy =& new StrategyWidthAbsolutePositioned();
138    $width_strategy->apply($this, $context);
139
140    $position_strategy =& new StrategyPositionAbsolute();
141    $position_strategy->apply($this);
142
143    $this->reflow_content($context);
144
145    /**
146     * As absolute-positioned box generated new flow context, extend the height to fit all floats
147     */
148    $this->fitFloats($context);
149  }
150
151  /**
152   * Reflow fixed-positioned block box. Note that according to CSS 2.1
153   * the only types of boxes which could be absolutely positioned are
154   * 'block' and 'table'
155   *
156   * @param FlowContext $context A flow context object containing the additional layout data.
157   *
158   * @link http://www.w3.org/TR/CSS21/visuren.html#dis-pos-flo CSS 2.1: Relationships between 'display', 'position', and 'float'
159   *
160   * @todo it seems that percentage-constrained fixed block width will be calculated incorrectly; we need
161   * to use containing block width instead of $this->get_width() when applying the width constraint
162   */
163  function reflow_fixed(&$context) {
164    GenericFormattedBox::reflow($this->parent, $context);
165
166    /**
167     * As fixed-positioned elements are placed relatively to page (so that one element may be shown
168     * several times on different pages), we cannot calculate its position at the moment.
169     * The real position of the element is calculated when it is to be shown - once for each page.
170     *
171     * @see BlockBox::show_fixed()
172     */
173    $this->put_left(0);
174    $this->put_top(0);
175
176    /**
177     * As sometimes left/right values may not be set, we need to use the "fit" width here.
178     * If box have a width constraint, 'get_max_width' will return constrained value;
179     * othersise, an intrictic width will be returned.
180     *
181     * @see GenericContainerBox::get_max_width()
182     */
183    $this->put_full_width($this->get_max_width($context));
184
185    /**
186     * Update the width, as it should be calculated based upon containing block width, not real parent.
187     * After this we should remove width constraints or we may encounter problem
188     * in future when we'll try to call get_..._width functions for this box
189     *
190     * @todo Update the family of get_..._width function so that they would apply constraint
191     * using the containing block width, not "real" parent width
192     */
193    $containing_block =& $this->_get_containing_block();
194    $wc = $this->get_css_property(CSS_WIDTH);
195    $this->put_full_width($wc->apply($this->get_width(),
196                                     $containing_block['right'] - $containing_block['left']));
197    $this->setCSSProperty(CSS_WIDTH, new WCNone());
198
199    /**
200     * Layout element's children
201     */
202    $this->reflow_content($context);
203
204    /**
205     * As fixed-positioned box generated new flow context, extend the height to fit all floats
206     */
207    $this->fitFloats($context);
208  }
209
210  /**
211   * Layout static-positioned block box.
212   *
213   * Note that static-positioned boxes may be floating boxes
214   *
215   * @param GenericContainerBox $parent The document element which should be treated as the parent of current element
216   * @param FlowContext $context The flow context containing the additional layout data
217   *
218   * @see FlowContext
219   * @see GenericContainerBox
220   */
221  function reflow_static(&$parent, &$context) {
222    if ($this->get_css_property(CSS_FLOAT) === FLOAT_NONE) {
223      $this->reflow_static_normal($parent, $context);
224    } else {
225      $this->reflow_static_float($parent, $context);
226    }
227  }
228
229  /**
230   * Layout normal (non-floating) static-positioned block box.
231   *
232   * @param GenericContainerBox $parent The document element which should be treated as the parent of current element
233   * @param FlowContext $context The flow context containing the additional layout data
234   *
235   * @see FlowContext
236   * @see GenericContainerBox
237   */
238  function reflow_static_normal(&$parent, &$context) {
239    GenericFormattedBox::reflow($parent, $context);
240
241    if ($parent) {
242      /**
243       * Child block will fill the whole content width of the parent block.
244       *
245       * 'margin-left' + 'border-left-width' + 'padding-left' + 'width' + 'padding-right' +
246       * 'border-right-width' + 'margin-right' = width of containing block
247       *
248       * See CSS 2.1 for more detailed explanation
249       *
250       * @link http://www.w3.org/TR/CSS21/visudet.html#blockwidth CSS 2.1. 10.3.3 Block-level, non-replaced elements in normal flow
251       */
252
253      /**
254       * Calculate margin values if they have been set as a percentage; replace percentage-based values
255       * with fixed lengths.
256       */
257      $this->_calc_percentage_margins($parent);
258      $this->_calc_percentage_padding($parent);
259
260      /**
261       * Calculate width value if it had been set as a percentage; replace percentage-based value
262       * with fixed value
263       */
264      $this->put_full_width($parent->get_width());
265      $this->_calc_percentage_width($parent, $context);
266
267      /**
268       * Calculate 'auto' values of width and margins. Unlike tables, DIV width is either constrained
269       * by some CSS rules or expanded to the parent width; thus, we can calculate 'auto' margin
270       * values immediately.
271       *
272       * @link http://www.w3.org/TR/CSS21/visudet.html#Computing_widths_and_margins CSS 2.1 Calculating widths and margins
273       */
274      $this->_calc_auto_width_margins($parent);
275
276      /**
277       * Collapse top margin
278       *
279       * @see GenericFormattedBox::collapse_margin()
280       *
281       * @link http://www.w3.org/TR/CSS21/box.html#collapsing-margins CSS 2.1 Collapsing margins
282       */
283      $y = $this->collapse_margin($parent, $context);
284
285      /**
286       * At this moment we have top parent/child collapsed margin at the top of context object
287       * margin stack
288       */
289
290      /**
291       * Apply 'clear' property; the current Y coordinate can be modified as a result of 'clear'.
292       */
293      $y = $this->apply_clear($y, $context);
294
295      /**
296       * Store calculated Y coordinate as current Y coordinate in the parent box
297       * No more content will be drawn abowe this mark; current box padding area will
298       * start below.
299       */
300      $parent->_current_y = $y;
301
302      /**
303       * Terminate current parent line-box (as current box is not inline)
304       */
305      $parent->close_line($context);
306
307      /**
308       * Add current box to the parent's line-box; we will close the line box below
309       * after content will be reflown, so the line box will contain only current box.
310       */
311      $parent->append_line($this);
312
313      /**
314       * Now, place the current box upper left content corner. Note that we should not
315       * use get_extra_top here, as _current_y value already culculated using the top margin value
316       * of the current box! The top content edge should be offset from that level only of padding and
317       * border width.
318       */
319      $border  = $this->get_css_property(CSS_BORDER);
320      $padding = $this->get_css_property(CSS_PADDING);
321
322      $this->moveto( $parent->get_left() + $this->get_extra_left(),
323                     $parent->_current_y - $border->top->get_width()  - $padding->top->value );
324    }
325
326    /**
327     * Reflow element's children
328     */
329    $this->reflow_content($context);
330
331    if ($this->get_css_property(CSS_OVERFLOW) != OVERFLOW_VISIBLE) {
332      $this->fitFloats($context);
333    }
334
335    /**
336     * After child elements have been reflown, we should the top collapsed margin stack value
337     * replaced by the value of last child bottom collapsed margin;
338     * if no children contained, then this value should be reset to 0.
339     *
340     * Note that invisible and
341     * whitespaces boxes would not affect the collapsed margin value, so we need to
342     * use 'get_first' function instead of just accessing the $content array.
343     *
344     * @see GenericContainerBox::get_first
345     */
346    if (!is_null($this->get_first())) {
347      $cm = 0;
348    } else {
349      $cm = $context->get_collapsed_margin();
350    };
351
352    /**
353     * Update the bottom  value, collapsing the latter value with
354     * current box bottom margin.
355     *
356     * Note that we need to remove TWO values from the margin stack:
357     * first - the value of collapsed bottom margin of the last child AND
358     * second - the value of collapsed top margin of current element.
359     */
360    $margin = $this->get_css_property(CSS_MARGIN);
361
362    if ($parent) {
363      /**
364       * Terminate parent's line box (it contains the current box only)
365       */
366      $parent->close_line($context);
367
368      $parent->_current_y = $this->collapse_margin_bottom($parent, $context);
369    };
370  }
371
372  function show(&$driver) {
373    if ($this->get_css_property(CSS_FLOAT)    != FLOAT_NONE ||
374        $this->get_css_property(CSS_POSITION) == POSITION_RELATIVE) {
375      // These boxes will be rendered separately
376      return true;
377    };
378
379    return parent::show($driver);
380  }
381
382  function show_postponed(&$driver) {
383    return parent::show($driver);
384  }
385
386  /**
387   * Show fixed positioned block box using the specified output driver
388   *
389   * Note that 'show_fixed' is called to box _nested_ to the fixed-positioned boxes too!
390   * Thus, we need to check whether actual 'position' values is 'fixed' for this box
391   * and only in that case attempt to move box
392   *
393   * @param OutputDriver $driver The output device driver object
394   */
395  function show_fixed(&$driver) {
396    $position = $this->get_css_property(CSS_POSITION);
397
398    if ($position == POSITION_FIXED) {
399      /**
400       * Calculate the distance between the top page edge and top box content edge
401       */
402      $bottom = $this->get_css_property(CSS_BOTTOM);
403      $top    = $this->get_css_property(CSS_TOP);
404
405      if (!$top->isAuto()) {
406        if ($top->isPercentage()) {
407          $vertical_offset = $driver->getPageMaxHeight() / 100 * $top->getPercentage();
408        } else {
409          $vertical_offset = $top->getPoints();
410        };
411
412      } elseif (!$bottom->isAuto()) {
413        if ($bottom->isPercentage()) {
414          $vertical_offset = $driver->getPageMaxHeight() * (100 - $bottom->getPercentage())/100 - $this->get_height();
415        } else {
416          $vertical_offset = $driver->getPageMaxHeight() - $bottom->getPoints() - $this->get_height();
417        };
418
419      } else {
420        $vertical_offset = 0;
421      };
422
423      /**
424       * Calculate the distance between the right page edge and right box content edge
425       */
426      $left  = $this->get_css_property(CSS_LEFT);
427      $right = $this->get_css_property(CSS_RIGHT);
428
429      if (!$left->isAuto()) {
430        if ($left->isPercentage()) {
431          $horizontal_offset = $driver->getPageWidth() / 100 * $left->getPercentage();
432        } else {
433          $horizontal_offset = $left->getPoints();
434        };
435
436      } elseif (!$right->isAuto()) {
437        if ($right->isPercentage()) {
438          $horizontal_offset = $driver->getPageWidth() * (100 - $right->getPercentage())/100 - $this->get_width();
439        } else {
440          $horizontal_offset = $driver->getPageWidth() - $right->getPoints() - $this->get_width();
441        };
442
443      } else {
444        $horizontal_offset = 0;
445      };
446
447      /**
448       * Offset current box to the required position on the current page (note that
449       * fixed-positioned element are placed relatively to the viewport - page in our case)
450       */
451      $this->moveto($driver->getPageLeft() + $horizontal_offset,
452                    $driver->getPageTop()  - $vertical_offset);
453    };
454
455    /**
456     * After box have benn properly positioned, render it as usual.
457     */
458    return GenericContainerBox::show_fixed($driver);
459  }
460
461  function isBlockLevel() {
462    return true;
463  }
464}
465?>