1<?php
2class FlowContext {
3  var $absolute_positioned;
4  var $fixed_positioned;
5
6  var $viewport;
7  var $_floats;
8  var $collapsed_margins;
9  var $container_uid;
10
11  function add_absolute_positioned(&$box) {
12    $this->absolute_positioned[] =& $box;
13  }
14
15  function add_fixed_positioned(&$box) {
16    $this->fixed_positioned[] =& $box;
17  }
18
19  function add_float(&$float) {
20    $this->_floats[0][] =& $float;
21  }
22
23  function container_uid() {
24    return $this->container_uid[0];
25  }
26
27  function &current_floats() {
28    return $this->_floats[0];
29  }
30
31  // Get the bottom edge coordinate of the bottommost float in
32  // current formatting context
33  //
34  // @return null in case of no floats exists in current context
35  // numeric coordinate value otherwise
36  //
37  function float_bottom() {
38    $floats =& $this->current_floats();
39
40    if (count($floats) == 0) { return null; }
41
42    $bottom = $floats[0]->get_bottom_margin();
43    $size = count($floats);
44    for ($i=1; $i<$size; $i++) {
45      $bottom = min($bottom, $floats[$i]->get_bottom_margin());
46    };
47
48    return $bottom;
49  }
50
51  // Calculates the leftmost x-coordinate not covered by floats in current context
52  // at the given level (y-coordinate)
53  //
54  // @param $x starting X coordinate (no point to the left of this allowed)
55  // @param $y Y coordinate we're searching at
56  // @return the leftmost X coordinate value
57  //
58  function float_left_x($x, $y) {
59    $floats =& $this->current_floats();
60
61    $size = count($floats);
62    for ($i=0; $i<$size; $i++) {
63      $float =& $floats[$i];
64
65      // Process only left-floating boxes
66      if ($float->get_css_property(CSS_FLOAT) == FLOAT_LEFT) {
67        // Check if this float contains given Y-coordinate
68        //
69        // Note that top margin coordinate is inclusive but
70        // bottom margin coordinate is exclusive! The cause is following:
71        // - if we have several floats in one line, their top margin edge Y coordinates will be equal,
72        //   so we must use agreater or equal sign to avod placing all floats at one X coordinate
73        // - on the other side, if we place one float under the other, the top margin Y coordinate
74        //   of bottom float will be equal to bottom margin Y coordinate of the top float and
75        //   we should NOT offset tho bottom float in this case
76        //
77
78        if ($float->get_top_margin() + EPSILON >= $y &&
79            $float->get_bottom_margin() < $y) {
80          $x = max($x, $float->get_right_margin());
81        };
82      };
83    };
84
85    return $x;
86  }
87
88  // Calculates position of left floating box (taking into account the possibility
89  // of "wrapping" float to next line in case we have not enough space at current level (Y coordinate)
90  //
91  // @param $parent reference to a parent box
92  // @param $width width of float being placed. Full width! so, extra horizontal space (padding, margins and borders) is added here too
93  // @param $x [out] X coordinate of float upper-left corner
94  // @param $y [in,out] Y coordinate of float upper-left corner
95  //
96  function float_left_xy(&$parent, $width, &$x, &$y) {
97    // Numbler of floats to clear; we need this because of the following example:
98    // <div style="width: 150px; background-color: red; padding: 5px;">
99    // <div style="float: left; background-color: green; height: 40px; width: 100px;">T</div>
100    // <div style="float: left; background-color: yellow; height: 20px; width: 50px;">T</div>
101    // <div style="float: left; background-color: cyan; height: 20px; width: 50px;">T</div>
102    // in this case the third float will be rendered directly under the second, so only the
103    // second float should be cleared
104
105    $clear = 0;
106
107    $floats =& $this->current_floats();
108
109    // Prepare information about the float bottom coordinates
110    $float_bottoms = array();
111    $size = count($floats);
112    for ($i=0; $i<$size; $i++) {
113      $float_bottoms[] = $floats[$i]->get_bottom_margin();
114    };
115
116    // Note that the sort function SHOULD NOT maintain key-value assotiations!
117    rsort($float_bottoms);
118
119    do {
120      $x  = $this->float_left_x($parent->get_left(), $y);
121
122      // Check if current float will fit into the parent box
123      // OR if there's no parent boxes with constrained width (it will expanded in this case anyway)
124
125      // small value to hide the rounding errors
126      $parent_wc = $parent->get_css_property(CSS_WIDTH);
127      if ($parent->get_right() + EPSILON >= $x + $width ||
128          $parent->mayBeExpanded()) {
129
130        // Will fit;
131        // Check if current float will intersect the existing left-floating box
132        //
133        $x1 = $this->float_right_x($parent->get_right(), $y);
134        if ($x1 + EPSILON > $x + $width) {
135          return;
136        };
137        return;
138      };
139
140      //      print("CLEAR<br/>");
141
142      // No, float does not fit at current level, let's try to 'clear' some previous floats
143      $clear++;
144
145      // Check if we've cleared all existing floats; the loop will be terminated in this case, of course,
146      // but we can get a notice/warning message if we'll try to access the non-existing array element
147      if ($clear <= count($floats)) { $y = min( $y, $float_bottoms[$clear-1] ); };
148
149    } while ($clear <= count($floats)); // We need to check if all floats have been cleared to avoid infinite loop
150
151    // All floats are cleared; fall back to the leftmost X coordinate
152    $x = $parent->get_left();
153  }
154
155  // Get the right edge coordinate of the rightmost float in
156  // current formatting context
157  //
158  // @return null in case of no floats exists in current context
159  // numeric coordinate value otherwise
160  //
161  function float_right() {
162    $floats =& $this->current_floats();
163
164    if (count($floats) == 0) { return null; }
165
166    $right = $floats[0]->get_right_margin();
167    $size = count($floats);
168    for ($i=1; $i<$size; $i++) {
169      $right = max($right, $floats[$i]->get_right_margin());
170    };
171
172    return $right;
173  }
174
175  // Calculates the rightmost x-coordinate not covered by floats in current context
176  // at the given level (y-coordinate)
177  //
178  // @param $x starting X coordinate (no point to the right of this allowed)
179  // @param $y Y coordinate we're searching at
180  // @return the rightmost X coordinate value
181  //
182  function float_right_x($x, $y) {
183    $floats =& $this->current_floats();
184
185    $size = count($floats);
186    for ($i=0; $i<$size; $i++) {
187      $float =& $floats[$i];
188
189      // Process only right-floating boxes
190      if ($float->get_css_property(CSS_FLOAT) == FLOAT_RIGHT) {
191        // Check if this float contains given Y-coordinate
192        //
193        // Note that top margin coordinate is inclusive but
194        // bottom margin coordinate is exclusive! The cause is following:
195        // - if we have several floats in one line, their top margin edge Y coordinates will be equal,
196        //   so we must use agreater or equal sign to avod placing all floats at one X coordinate
197        // - on the other side, if we place one float under the other, the top margin Y coordinate
198        //   of bottom float will be equal to bottom margin Y coordinate of the top float and
199        //   we should NOT offset tho bottom float in this case
200        //
201
202        if ($float->get_top_margin() + EPSILON >= $y &&
203            $float->get_bottom_margin() < $y) {
204          $x = min($x, $float->get_left_margin());
205        };
206      };
207    };
208
209    return $x;
210  }
211
212  // Calculates position of right floating box (taking into account the possibility
213  // of "wrapping" float to next line in case we have not enough space at current level (Y coordinate)
214  //
215  // @param $parent reference to a parent box
216  // @param $width width of float being placed. Full width! so, extra horizontal space (padding, margins and borders) is added here too
217  // @param $x [out] X coordinate of float upper-right corner
218  // @param $y [in,out] Y coordinate of float upper-right corner
219  //
220  function float_right_xy(&$parent, $width, &$x, &$y) {
221    // Numbler of floats to clear; we need this because of the following example:
222    // <div style="width: 150px; background-color: red; padding: 5px;">
223    // <div style="float: left; background-color: green; height: 40px; width: 100px;">T</div>
224    // <div style="float: left; background-color: yellow; height: 20px; width: 50px;">T</div>
225    // <div style="float: left; background-color: cyan; height: 20px; width: 50px;">T</div>
226    // in this case the third float will be rendered directly under the second, so only the
227    // second float should be cleared
228
229    $clear = 0;
230
231    $floats =& $this->current_floats();
232
233    // Prepare information about the float bottom coordinates
234    $float_bottoms = array();
235    $size = count($floats);
236    for ($i=0; $i<$size; $i++) {
237      $float_bottoms[] = $floats[$i]->get_bottom_margin();
238    };
239
240    // Note that the sort function SHOULD NOT maintain key-value assotiations!
241    rsort($float_bottoms);
242
243    do {
244      $x  = $this->float_right_x($parent->get_right(), $y);
245
246      // Check if current float will fit into the parent box
247      // OR if the parent box have width: auto (it will expanded in this case anyway)
248      //
249      if ($parent->get_right() + EPSILON > $x ||
250          $parent->width == WIDTH_AUTO) {
251
252        // Will fit;
253        // Check if current float will intersect the existing left-floating box
254        //
255        $x1 = $this->float_left_x($parent->get_left(), $y);
256        if ($x1 - EPSILON < $x - $width) {
257          return;
258        };
259      };
260
261
262      // No, float does not fit at current level, let's try to 'clear' some previous floats
263      $clear++;
264
265      // Check if we've cleared all existing floats; the loop will be terminated in this case, of course,
266      // but we can get a notice/warning message if we'll try to access the non-existing array element
267      if ($clear <= count($floats)) { $y = min( $y, $float_bottoms[$clear-1] ); };
268
269    } while($clear <= count($floats)); // We need to check if all floats have been cleared to avoid infinite loop
270
271    // All floats are cleared; fall back to the rightmost X coordinate
272    $x = $parent->get_right();
273  }
274
275  function FlowContext() {
276    $this->absolute_positioned = array();
277    $this->fixed_positioned = array();
278
279    $this->viewport = array();
280    $this->_floats = array(array());
281    $this->collapsed_margins = array(0);
282    $this->container_uid = array(1);
283  }
284
285  function get_collapsed_margin() {
286    return $this->collapsed_margins[0];
287  }
288
289  function &get_viewport() {
290    return $this->viewport[0];
291  }
292
293  function pop() {
294    $this->pop_collapsed_margin();
295    $this->pop_floats();
296  }
297
298  function pop_collapsed_margin() {
299    array_shift($this->collapsed_margins);
300  }
301
302  function pop_container_uid() {
303    array_shift($this->container_uid);
304  }
305
306  function pop_floats() {
307    array_shift($this->_floats);
308  }
309
310  function push() {
311    $this->push_collapsed_margin(0);
312    $this->push_floats();
313  }
314
315  function push_collapsed_margin($margin) {
316    array_unshift($this->collapsed_margins, $margin);
317  }
318
319  function push_container_uid($uid) {
320    array_unshift($this->container_uid, $uid);
321  }
322
323  function push_floats() {
324    array_unshift($this->_floats, array());
325  }
326
327  function push_viewport(&$box) {
328    array_unshift($this->viewport, $box);
329  }
330
331  function &point_in_floats($x, $y) {
332    // Scan the floating children list of the current container box
333    $floats =& $this->current_floats();
334    $size = count($floats);
335    for ($i=0; $i<$size; $i++) {
336      if ($floats[$i]->contains_point_margin($x, $y)) {
337        return $floats[$i];
338      }
339    }
340
341    $dummy = null;
342    return $dummy;
343  }
344
345  function pop_viewport() {
346    array_shift($this->viewport);
347  }
348
349  function sort_absolute_positioned_by_z_index() {
350    usort($this->absolute_positioned, "cmp_boxes_by_z_index");
351  }
352}
353
354function cmp_boxes_by_z_index($a, $b) {
355  $a_z = $a->get_css_property(CSS_Z_INDEX);
356  $b_z = $b->get_css_property(CSS_Z_INDEX);
357
358  if ($a_z == $b_z) return 0;
359  return ($a_z < $b_z) ? -1 : 1;
360}
361?>