Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
// $Header: /cvsroot/html2ps/box.container.php,v 1.68 2007/05/06 18:49:29 Konstantin Exp $
3
 
4
require_once(HTML2PS_DIR.'strategy.width.min.php');
5
require_once(HTML2PS_DIR.'strategy.width.min.nowrap.php');
6
require_once(HTML2PS_DIR.'strategy.width.max.php');
7
require_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
 */
26
class 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
?>