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.generic.formatted.php,v 1.21 2007/02/18 09:55:10 Konstantin Exp $
3
 
4
require_once(HTML2PS_DIR.'doc.anchor.class.php');
5
require_once(HTML2PS_DIR.'layout.vertical.php');
6
 
7
class GenericFormattedBox extends GenericBox {
8
  var $uid;
9
 
10
  function _get_collapsable_top_margin_internal() {
11
    $positive_margin = 0;
12
    $negative_margin = 0;
13
 
14
    $current_box = $this;
15
 
16
    $border  = $current_box->get_css_property(CSS_BORDER);
17
    $padding = $current_box->get_css_property(CSS_PADDING);
18
    if ($border->top->get_width() > 0 ||
19
        $padding->top->value > 0) {
20
      return 0;
21
    };
22
 
23
    while (!is_null($current_box) &&
24
           $current_box->isBlockLevel()) {
25
      $margin  = $current_box->get_css_property(CSS_MARGIN);
26
      $border  = $current_box->get_css_property(CSS_BORDER);
27
      $padding = $current_box->get_css_property(CSS_PADDING);
28
 
29
      $top_margin = $margin->top->value;
30
 
31
      if ($top_margin >= 0) {
32
        $positive_margin = max($positive_margin, $top_margin);
33
      } else {
34
        $negative_margin = min($negative_margin, $top_margin);
35
      };
36
 
37
      if ($border->top->get_width() > 0 ||
38
          $padding->top->value > 0) {
39
        $current_box = null;
40
      } else {
41
        $current_box = $current_box->get_first();
42
      };
43
    };
44
 
45
    return $positive_margin /*- $negative_margin*/;
46
  }
47
 
48
  function _get_collapsable_top_margin_external() {
49
    $positive_margin = 0;
50
    $negative_margin = 0;
51
 
52
    $current_box = $this;
53
    while (!is_null($current_box) &&
54
           $current_box->isBlockLevel()) {
55
      $margin  = $current_box->get_css_property(CSS_MARGIN);
56
      $border  = $current_box->get_css_property(CSS_BORDER);
57
      $padding = $current_box->get_css_property(CSS_PADDING);
58
 
59
      $top_margin = $margin->top->value;
60
 
61
      if ($top_margin >= 0) {
62
        $positive_margin = max($positive_margin, $top_margin);
63
      } else {
64
        $negative_margin = min($negative_margin, $top_margin);
65
      };
66
 
67
      if ($border->top->get_width() > 0 ||
68
          $padding->top->value > 0) {
69
        $current_box = null;
70
      } else {
71
        $current_box = $current_box->get_first();
72
      };
73
    };
74
 
75
    return $positive_margin + $negative_margin;
76
  }
77
 
78
  function _get_collapsable_bottom_margin_external() {
79
    $positive_margin = 0;
80
    $negative_margin = 0;
81
 
82
    $current_box = $this;
83
    while (!is_null($current_box) &&
84
           $current_box->isBlockLevel()) {
85
      $margin  = $current_box->get_css_property(CSS_MARGIN);
86
      $border  = $current_box->get_css_property(CSS_BORDER);
87
      $padding = $current_box->get_css_property(CSS_PADDING);
88
 
89
      $bottom_margin = $margin->bottom->value;
90
 
91
      if ($bottom_margin >= 0) {
92
        $positive_margin = max($positive_margin, $bottom_margin);
93
      } else {
94
        $negative_margin = min($negative_margin, $bottom_margin);
95
      };
96
 
97
      if ($border->bottom->get_width() > 0 ||
98
          $padding->bottom->value > 0) {
99
        $current_box = null;
100
      } else {
101
        $current_box = $current_box->get_last();
102
      };
103
    };
104
 
105
    return $positive_margin + $negative_margin;
106
  }
107
 
108
  function collapse_margin_bottom(&$parent, &$context) {
109
    /**
110
     * Now, if there's a parent for this box, we extend its height to fit current box.
111
     * If parent generated new flow context (like table cell or floating box), its content
112
     * area should include the current box bottom margin (bottom margin does not colllapse).
113
     * See CSS 2.1 for more detailed explanations.
114
     *
115
     * @see FlowContext::container_uid()
116
     *
117
     * @link http://www.w3.org/TR/CSS21/visudet.html#Computing_widths_and_margins CSS 2.1 8.3.1 Calculating widths and margins
118
     */
119
    $parent_border  = $parent->get_css_property(CSS_BORDER);
120
    $parent_padding = $parent->get_css_property(CSS_PADDING);
121
 
122
    /**
123
     * The  bottom margin  of an  in-flow block-level  element  with a
124
     * 'height'  of 'auto'  and 'min-height'  less than  the element's
125
     * used height  and 'max-height'  greater than the  element's used
126
     * height  is adjoining  to its  last in-flow  block-level child's
127
     * bottom margin if the element has NO BOTTOM PADDING OR BORDER.
128
     */
129
 
130
    $last =& $parent->get_last();
131
    $is_last = !is_null($last) && $this->uid == $last->uid;
132
 
133
    if (!is_null($last) &&
134
        $is_last &&                                  // This element is a last in-flow block level element AND
135
        $parent->uid != $context->container_uid() && // Parent element did not generate new flow context (like table-cell) AND
136
        $parent_border->bottom->get_width() == 0  && // Parent have NO bottom border AND
137
        $parent_padding->bottom->value == 0)  {      // Parent have NO bottom padding AND
138
      $parent->extend_height($this->get_bottom_border());
139
    } else {
140
      // Otherwise (in particular, if this box is not last), bottom
141
      // margin of the current box will be contained inside the current box
142
      $parent->extend_height($this->get_bottom_margin());
143
    }
144
 
145
    $cm = $context->get_collapsed_margin();
146
    $context->pop_collapsed_margin();
147
    $context->pop_collapsed_margin();
148
 
149
    /**
150
     * shift current parent 'watermark' to the current box margin edge;
151
     * all content now will be drawn below this mark (with a small exception
152
     * of elements having negative vertical margins, of course).
153
     */
154
    if ($is_last &&
155
        ($parent_border->bottom->get_width() > 0 ||
156
         $parent_padding->bottom->value > 0)) {
157
      $context->push_collapsed_margin( 0 );
158
      return $this->get_bottom_border() - $cm;
159
    } else {
160
      $collapsable = $this->_get_collapsable_bottom_margin_external();
161
      $context->push_collapsed_margin( $collapsable );
162
 
163
      return $this->get_bottom_border();
164
    };
165
  }
166
 
167
  function collapse_margin(&$parent, &$context) {
168
    // Do margin collapsing
169
 
170
    // Margin collapsing is done as follows:
171
    // 1. If previous sibling was an inline element (so, parent line box was not empty),
172
    //    then no collapsing will take part
173
    // 2. If NO previous element exists at all, then collapse current box top margin
174
    //    with parent's collapsed top margin.
175
    // 2.1. If parent element was float, no collapsing should be
176
    // 3. If there's previous block element, collapse current box top margin
177
    //    with previous elemenent's collapsed bottom margin
178
 
179
    // Check if current parent line box contains inline elements only. In this case the only
180
    // margin will be current box margin
181
 
182
    if (!$parent->line_box_empty()) {
183
      // Case (1). Previous element was inline element; no collapsing
184
 
185
      $parent->close_line($context);
186
 
187
      $vmargin = $this->_get_collapsable_top_margin_external();
188
    } else {
189
      $parent_first = $this->parent->get_first();
190
 
191
      if (is_null($parent_first) || // Unfortunately, we sometimes get null as a value of $parent_first; this should be checked
192
          $parent_first->uid == $this->uid) {
193
        // Case (2). No previous block element at all; Collapse with parent margins
194
        $collapsable = $this->_get_collapsable_top_margin_external();
195
        $collapsed   = $context->get_collapsed_margin();
196
 
197
        $vmargin = max(0, $collapsable - $collapsed);
198
 
199
      } else {
200
        // Case (3). There's a previous block element
201
 
202
        $collapsable = $this->_get_collapsable_top_margin_external();
203
        $collapsed   = $context->get_collapsed_margin();
204
 
205
        // In this case, base position is a bottom border of the previous element
206
        // $vmargin - offset from a base position - should be at least $collapsed
207
        // (value of collapsed bottom margins from the previous element and its
208
        // children). If current element have $collapsable - collapsed top margin
209
        // (from itself and children too) greater that this value, we should
210
        // offset it further to the bottom
211
 
212
        $vmargin = max($collapsable, $collapsed);
213
      };
214
    };
215
 
216
    // Determine the base Y coordinate of box margin edge
217
    $y = $parent->_current_y - $vmargin;
218
 
219
    $internal_margin = $this->_get_collapsable_top_margin_internal();
220
    $context->push_collapsed_margin($internal_margin);
221
 
222
    return $y;
223
  }
224
 
225
  function GenericFormattedBox() {
226
    $this->GenericBox();
227
 
228
    // Layout data
229
    $this->baseline = 0;
230
    $this->parent = null;
231
  }
232
 
233
  function readCSS(&$state) {
234
    parent::readCSS($state);
235
 
236
    $this->_readCSS($state,
237
                    array(CSS_OVERFLOW,
238
                          CSS_PAGE_BREAK_AFTER,
239
                          CSS_PAGE_BREAK_BEFORE,
240
                          CSS_PAGE_BREAK_INSIDE,
241
                          CSS_ORPHANS,
242
                          CSS_WIDOWS,
243
                          CSS_POSITION,
244
                          CSS_TEXT_ALIGN,
245
                          CSS_WHITE_SPACE,
246
                          CSS_CLEAR,
247
                          CSS_CONTENT,
248
                          CSS_HTML2PS_PSEUDOELEMENTS,
249
                          CSS_FLOAT,
250
                          CSS_Z_INDEX,
251
                          CSS_HTML2PS_ALIGN,
252
                          CSS_HTML2PS_NOWRAP,
253
                          CSS_DIRECTION,
254
                          CSS_PAGE));
255
 
256
    $this->_readCSSLengths($state,
257
                           array(CSS_BACKGROUND,
258
                                 CSS_BORDER,
259
                                 CSS_BOTTOM,
260
                                 CSS_TOP,
261
                                 CSS_LEFT,
262
                                 CSS_RIGHT,
263
                                 CSS_MARGIN,
264
                                 CSS_PADDING,
265
                                 CSS_TEXT_INDENT,
266
                                 CSS_HTML2PS_COMPOSITE_WIDTH,
267
                                 CSS_HEIGHT,
268
                                 CSS_MIN_HEIGHT,
269
                                 CSS_MAX_HEIGHT,
270
                                 CSS_LETTER_SPACING
271
                                 ));
272
 
273
    /**
274
     * CSS 2.1,  p 8.5.2:
275
     *
276
     * If an  element's border  color is not  specified with  a border
277
     * property,  user agents  must  use the  value  of the  element's
278
     * 'color' property as the computed value for the border color.
279
     */
280
    $border =& $this->get_css_property(CSS_BORDER);
281
    $color  =& $this->get_css_property(CSS_COLOR);
282
 
283
    if ($border->top->isDefaultColor()) {
284
      $border->top->setColor($color);
285
    };
286
 
287
    if ($border->right->isDefaultColor()) {
288
      $border->right->setColor($color);
289
    };
290
 
291
    if ($border->bottom->isDefaultColor()) {
292
      $border->bottom->setColor($color);
293
    };
294
 
295
    if ($border->left->isDefaultColor()) {
296
      $border->left->setColor($color);
297
    };
298
 
299
    $this->setCSSProperty(CSS_BORDER, $border);
300
 
301
    $this->_height_constraint =& HCConstraint::create($this);
302
    $this->height = 0;
303
 
304
    // 'width'
305
    $wc =& $this->get_css_property(CSS_WIDTH);
306
    $this->width = $wc->apply(0,0);
307
 
308
    // 'PSEUDO-CSS' properties
309
 
310
    // '-localalign'
311
    switch ($state->get_property(CSS_HTML2PS_LOCALALIGN)) {
312
    case LA_LEFT:
313
      break;
314
    case LA_RIGHT:
315
      $margin =& $this->get_css_property(CSS_MARGIN);
316
      $margin->left->auto = true;
317
      $this->setCSSProperty(CSS_MARGIN, $margin);
318
      break;
319
    case LA_CENTER:
320
      $margin =& $this->get_css_property(CSS_MARGIN);
321
      $margin->left->auto  = true;
322
      $margin->right->auto = true;
323
      $this->setCSSProperty(CSS_MARGIN, $margin);
324
      break;
325
    };
326
  }
327
 
328
  function _calc_percentage_margins(&$parent) {
329
    $margin = $this->get_css_property(CSS_MARGIN);
330
    $containing_block =& $this->_get_containing_block();
331
    $margin->calcPercentages($containing_block['right'] - $containing_block['left']);
332
    $this->setCSSProperty(CSS_MARGIN, $margin);
333
  }
334
 
335
  function _calc_percentage_padding(&$parent) {
336
    $padding = $this->get_css_property(CSS_PADDING);
337
    $containing_block =& $this->_get_containing_block();
338
    $padding->calcPercentages($containing_block['right'] - $containing_block['left']);
339
    $this->setCSSProperty(CSS_PADDING, $padding);
340
  }
341
 
342
  function apply_clear($y, &$context) {
343
    return LayoutVertical::apply_clear($this, $y, $context);
344
  }
345
 
346
 
347
  /**
348
   * CSS 2.1:
349
   * 10.2 Content width: the 'width' property
350
   * Values have the following meanings:
351
   * <percentage> Specifies a percentage width. The percentage is calculated with respect to the width of the generated box's containing block.
352
   *
353
   * If the containing block's width depends on this element's width,
354
   * then the resulting layout is undefined in CSS 2.1.
355
   */
356
  function _calc_percentage_width(&$parent, &$context) {
357
    $wc = $this->get_css_property(CSS_WIDTH);
358
    if ($wc->isFraction()) {
359
      $containing_block =& $this->_get_containing_block();
360
 
361
      // Calculate actual width
362
      $width = $wc->apply($this->width, $containing_block['right'] - $containing_block['left']);
363
 
364
      // Assign calculated width
365
      $this->put_width($width);
366
 
367
      // Remove any width constraint
368
      $this->setCSSProperty(CSS_WIDTH, new WCConstant($width));
369
    }
370
  }
371
 
372
  function _calc_auto_width_margins(&$parent) {
373
    $float = $this->get_css_property(CSS_FLOAT);
374
 
375
    if ($float !== FLOAT_NONE) {
376
      $this->_calc_auto_width_margins_float($parent);
377
    } else {
378
      $this->_calc_auto_width_margins_normal($parent);
379
    }
380
  }
381
 
382
  // 'auto' margin value became 0, 'auto' width is 'shrink-to-fit'
383
  function _calc_auto_width_margins_float(&$parent) {
384
    // If 'width' is set to 'auto' the used value is the "shrink-to-fit" width
385
    // TODO
386
    if (false) {
387
      // Calculation of the shrink-to-fit width is similar to calculating the
388
      // width of a table cell using the automatic table layout
389
      // algorithm. Roughly: calculate the preferred width by formatting the
390
      // content without breaking lines other than where explicit line breaks
391
      // occur, and also calculate the preferred minimum width, e.g., by trying
392
      // all possible line breaks. CSS 2.1 does not define the exact
393
      //  algorithm. Thirdly, find the available width: in this case, this is
394
      // the width of the containing block minus minus the used values of
395
      // 'margin-left', 'border-left-width', 'padding-left', 'padding-right',
396
      //  'border-right-width', 'margin-right', and the widths of any relevant
397
      // scroll bars.
398
 
399
      // Then the shrink-to-fit width is: min(max(preferred minimum width, available width), preferred width).
400
 
401
      // Store used value
402
    };
403
 
404
    // If 'margin-left', or 'margin-right' are computed as 'auto', their used value is '0'.
405
    $margin = $this->get_css_property(CSS_MARGIN);
406
    if ($margin->left->auto) { $margin->left->value = 0; }
407
    if ($margin->right->auto) { $margin->right->value = 0; }
408
    $this->setCSSProperty(CSS_MARGIN, $margin);
409
 
410
    $this->width = $this->get_width();
411
  }
412
 
413
  // 'margin-left' + 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' + 'margin-right' = width of containing block
414
  function _calc_auto_width_margins_normal(&$parent) {
415
    // get the containing block width
416
    $containing_block =& $this->_get_containing_block();
417
    $parent_width = $containing_block['right'] - $containing_block['left'];
418
 
419
    // If 'width' is set to 'auto', any other 'auto' values become '0'  and 'width' follows from the resulting equality.
420
 
421
    // If both 'margin-left' and 'margin-right' are 'auto', their used values are equal.
422
    // This horizontally centers the element with respect to the edges of the containing block.
423
 
424
    $margin = $this->get_css_property(CSS_MARGIN);
425
    if ($margin->left->auto && $margin->right->auto) {
426
      $margin_value = ($parent_width - $this->get_full_width()) / 2;
427
      $margin->left->value = $margin_value;
428
      $margin->right->value = $margin_value;
429
    } else {
430
      // If there is exactly one value specified as 'auto', its used value follows from the equality.
431
      if ($margin->left->auto) {
432
        $margin->left->value = $parent_width - $this->get_full_width();
433
      } elseif ($margin->right->auto) {
434
        $margin->right->value = $parent_width - $this->get_full_width();
435
      };
436
    };
437
    $this->setCSSProperty(CSS_MARGIN, $margin);
438
 
439
    $this->width = $this->get_width();
440
  }
441
 
442
  function get_descender() {
443
    return 0;
444
  }
445
 
446
  function get_ascender() {
447
    return 0;
448
  }
449
 
450
  function _get_vert_extra() {
451
    return
452
      $this->get_extra_top() +
453
      $this->get_extra_bottom();
454
  }
455
 
456
  function _get_hor_extra() {
457
    return
458
      $this->get_extra_left() +
459
      $this->get_extra_right();
460
  }
461
 
462
  // Width:
463
  // 'get-min-width' stub
464
  function get_min_width(&$context) {
465
    die("OOPS! Unoverridden get_min_width called in class ".get_class($this)." inside ".get_class($this->parent));
466
  }
467
 
468
  function get_preferred_width(&$context) {
469
    return $this->get_max_width($context) - $this->_get_hor_extra();
470
  }
471
 
472
  function get_preferred_minimum_width(&$context) {
473
    return $this->get_min_width($context);
474
  }
475
 
476
  // 'get-max-width' stub
477
  function get_max_width(&$context) {
478
    die("OOPS! Unoverridden get_max_width called in class ".get_class($this)." inside ".get_class($this->parent));
479
  }
480
 
481
  function get_max_width_natural(&$context) {
482
    return $this->get_max_width($context);
483
  }
484
 
485
  function get_full_width() {
486
    return $this->get_width() + $this->_get_hor_extra();
487
  }
488
 
489
  function put_full_width($value) {
490
    // Calculate value of additional horizontal space consumed by margins and padding
491
    $this->width = $value - $this->_get_hor_extra();
492
  }
493
 
494
  function &_get_containing_block() {
495
    $position = $this->get_css_property(CSS_POSITION);
496
 
497
    switch ($position) {
498
    case POSITION_ABSOLUTE:
499
      $containing_block =& $this->_get_containing_block_absolute();
500
      return $containing_block;
501
    case POSITION_FIXED:
502
      $containing_block =& $this->_get_containing_block_fixed();
503
      return $containing_block;
504
    case POSITION_STATIC:
505
    case POSITION_RELATIVE:
506
      $containing_block =& $this->_get_containing_block_static();
507
      return $containing_block;
508
    default:
509
      die(sprintf('Unexpected position enum value: %d', $position));
510
    };
511
  }
512
 
513
  function &_get_containing_block_fixed() {
514
    $media = $GLOBALS['g_media'];
515
 
516
    $containing_block = array();
517
    $containing_block['left']   = mm2pt($media->margins['left']);
518
    $containing_block['right']  = mm2pt($media->margins['left'] + $media->real_width());
519
    $containing_block['top']    = mm2pt($media->margins['bottom'] + $media->real_height());
520
    $containing_block['bottom'] = mm2pt($media->margins['bottom']);
521
 
522
    return $containing_block;
523
  }
524
 
525
  // Get the position and size of containing block for current
526
  // ABSOLUTE POSITIONED element. It is assumed that this function
527
  // is called for ABSOLUTE positioned boxes ONLY
528
  //
529
  // @return associative array with 'top', 'bottom', 'right' and 'left'
530
  // indices in data space describing the position of containing block
531
  //
532
  function &_get_containing_block_absolute() {
533
    $parent =& $this->parent;
534
 
535
    // No containing block at all...
536
    // How could we get here?
537
    if (is_null($parent)) {
538
      trigger_error("No containing block found for absolute-positioned element",
539
                    E_USER_ERROR);
540
    };
541
 
542
    // CSS 2.1:
543
    // If the element has 'position: absolute', the containing block is established by the
544
    // nearest ancestor with a 'position' of 'absolute', 'relative' or 'fixed', in the following way:
545
    // - In the case that the ancestor is inline-level, the containing block depends on
546
    //   the 'direction' property of the ancestor:
547
    //   1. If the 'direction' is 'ltr', the top and left of the containing block are the top and left
548
    //      content edges of the first box generated by the ancestor, and the bottom and right are the
549
    //      bottom and right content edges of the last box of the ancestor.
550
    //   2. If the 'direction' is 'rtl', the top and right are the top and right edges of the first
551
    //      box generated by the ancestor, and the bottom and left are the bottom and left content
552
    //      edges of the last box of the ancestor.
553
    // - Otherwise, the containing block is formed by the padding edge of the ancestor.
554
    // TODO: inline-level ancestors
555
    while ((!is_null($parent->parent)) &&
556
           ($parent->get_css_property(CSS_POSITION) === POSITION_STATIC)) {
557
      $parent =& $parent->parent;
558
    }
559
 
560
    // Note that initial containg block (containig BODY element) will be formed by BODY margin edge,
561
    // unlike other blocks which are formed by padding edges
562
 
563
    if ($parent->parent) {
564
      // Normal containing block
565
      $containing_block = array();
566
      $containing_block['left']   = $parent->get_left_padding();
567
      $containing_block['right']  = $parent->get_right_padding();
568
      $containing_block['top']    = $parent->get_top_padding();
569
      $containing_block['bottom'] = $parent->get_bottom_padding();
570
    } else {
571
      // Initial containing block
572
      $containing_block = array();
573
      $containing_block['left']   = $parent->get_left_margin();
574
      $containing_block['right']  = $parent->get_right_margin();
575
      $containing_block['top']    = $parent->get_top_margin();
576
      $containing_block['bottom'] = $parent->get_bottom_margin();
577
    };
578
 
579
    return $containing_block;
580
  }
581
 
582
  function &_get_containing_block_static() {
583
    $parent =& $this->parent;
584
 
585
    // No containing block at all...
586
    // How could we get here?
587
 
588
    if (is_null($parent)) {
589
      die("No containing block found for static-positioned element");
590
    };
591
 
592
    while (!is_null($parent->parent) &&
593
           !$parent->isBlockLevel() &&
594
           !$parent->isCell()) {
595
      $parent =& $parent->parent;
596
    };
597
 
598
    // Note that initial containg block (containing BODY element)
599
    // will be formed by BODY margin edge,
600
    // unlike other blocks which are formed by content edges
601
 
602
    $containing_block = array();
603
    $containing_block['left']   = $parent->get_left();
604
    $containing_block['right']  = $parent->get_right();
605
    $containing_block['top']    = $parent->get_top();
606
    $containing_block['bottom'] = $parent->get_bottom();
607
 
608
    return $containing_block;
609
  }
610
 
611
  // Height constraint
612
  function get_height_constraint() {
613
    return $this->_height_constraint;
614
  }
615
 
616
  function put_height_constraint(&$wc) {
617
    $this->_height_constraint = $wc;
618
  }
619
 
620
  // Extends the box height to cover the given Y coordinate
621
  // If box height is already big enough, no changes will be made
622
  //
623
  // @param $y_coord Y coordinate should be covered by the box
624
  //
625
  function extend_height($y_coord) {
626
    $this->put_height(max($this->get_height(), $this->get_top() - $y_coord));
627
  }
628
 
629
  function extend_width($x_coord) {
630
    $this->put_width(max($this->get_width(), $x_coord - $this->get_left()));
631
  }
632
 
633
  function get_extra_bottom() {
634
    $border = $this->get_css_property(CSS_BORDER);
635
    return
636
      $this->get_margin_bottom() +
637
      $border->bottom->get_width() +
638
      $this->get_padding_bottom();
639
  }
640
 
641
  function get_extra_left() {
642
    $border = $this->get_css_property(CSS_BORDER);
643
 
644
    $left_border = $border->left;
645
 
646
    return
647
      $this->get_margin_left() +
648
      $left_border->get_width() +
649
      $this->get_padding_left();
650
  }
651
 
652
  function get_extra_right() {
653
    $border = $this->get_css_property(CSS_BORDER);
654
    $right_border = $border->right;
655
    return
656
      $this->get_margin_right() +
657
      $right_border->get_width() +
658
      $this->get_padding_right();
659
  }
660
 
661
  function get_extra_top() {
662
    $border = $this->get_css_property(CSS_BORDER);
663
    return
664
      $this->get_margin_top() +
665
      $border->top->get_width() +
666
      $this->get_padding_top();
667
  }
668
 
669
  function get_extra_line_left() { return 0; }
670
  function get_extra_line_right() { return 0; }
671
 
672
  function get_margin_bottom() {
673
    $margin = $this->get_css_property(CSS_MARGIN);
674
    return $margin->bottom->value;
675
  }
676
 
677
  function get_margin_left() {
678
    $margin = $this->get_css_property(CSS_MARGIN);
679
    return $margin->left->value;
680
  }
681
 
682
  function get_margin_right() {
683
    $margin = $this->get_css_property(CSS_MARGIN);
684
    return $margin->right->value;
685
  }
686
 
687
  function get_margin_top() {
688
    $margin = $this->get_css_property(CSS_MARGIN);
689
    return $margin->top->value;
690
  }
691
 
692
  function get_padding_right() {
693
    $padding = $this->get_css_property(CSS_PADDING);
694
    return $padding->right->value;
695
  }
696
 
697
  function get_padding_left() {
698
    $padding = $this->get_css_property(CSS_PADDING);
699
    return $padding->left->value;
700
  }
701
 
702
  function get_padding_top() {
703
    $padding = $this->get_css_property(CSS_PADDING);
704
    return $padding->top->value;
705
  }
706
 
707
  function get_border_top_width() {
708
    return $this->border->top->width;
709
  }
710
 
711
  function get_padding_bottom() {
712
    $padding = $this->get_css_property(CSS_PADDING);
713
    return $padding->bottom->value;
714
  }
715
 
716
  function get_left_border() {
717
    $padding = $this->get_css_property(CSS_PADDING);
718
    $border  = $this->get_css_property(CSS_BORDER);
719
 
720
    return
721
      $this->get_left() -
722
      $padding->left->value -
723
      $border->left->get_width();
724
  }
725
 
726
  function get_right_border() {
727
    $padding = $this->get_css_property(CSS_PADDING);
728
    $border  = $this->get_css_property(CSS_BORDER);
729
 
730
    return
731
      $this->get_left() +
732
      $this->get_width() +
733
      $padding->right->value +
734
      $border->right->get_width();
735
  }
736
 
737
  function get_top_border()     {
738
    $border = $this->get_css_property(CSS_BORDER);
739
 
740
    return
741
      $this->get_top_padding() +
742
      $border->top->get_width();
743
  }
744
 
745
  function get_bottom_border()  {
746
    $border = $this->get_css_property(CSS_BORDER);
747
    return
748
      $this->get_bottom_padding()  -
749
      $border->bottom->get_width();
750
  }
751
 
752
  function get_left_padding() {
753
    $padding = $this->get_css_property(CSS_PADDING);
754
    return $this->get_left() - $padding->left->value;
755
  }
756
 
757
  function get_right_padding() {
758
    $padding = $this->get_css_property(CSS_PADDING);
759
    return $this->get_left() + $this->get_width() + $padding->right->value;
760
  }
761
 
762
  function get_top_padding()    {
763
    $padding = $this->get_css_property(CSS_PADDING);
764
 
765
    return
766
      $this->get_top() +
767
      $padding->top->value;
768
  }
769
 
770
  function get_bottom_padding() {
771
    $padding = $this->get_css_property(CSS_PADDING);
772
    return $this->get_bottom() - $padding->bottom->value;
773
  }
774
 
775
  function get_left_margin()    {
776
    return
777
      $this->get_left() -
778
      $this->get_extra_left();
779
  }
780
 
781
  function get_right_margin()   {
782
    return
783
      $this->get_right() +
784
      $this->get_extra_right();
785
  }
786
 
787
  function get_bottom_margin()  {
788
    return
789
      $this->get_bottom() -
790
      $this->get_extra_bottom();
791
  }
792
 
793
  function get_top_margin() {
794
    $margin = $this->get_css_property(CSS_MARGIN);
795
 
796
    return
797
      $this->get_top_border() +
798
      $margin->top->value;
799
  }
800
 
801
  // Geometry
802
  function contains_point_margin($x, $y) {
803
    // Actually, we treat a small area around the float as "inside" float;
804
    // it will help us to prevent incorrectly positioning float due the rounding errors
805
    $eps = 0.1;
806
    return
807
      $this->get_left_margin()-$eps   <= $x &&
808
      $this->get_right_margin()+$eps  >= $x &&
809
      $this->get_top_margin()+$eps    >= $y &&
810
      $this->get_bottom_margin()      <  $y;
811
  }
812
 
813
  function get_width() {
814
    $wc = $this->get_css_property(CSS_WIDTH);
815
 
816
    if ($this->parent) {
817
      return $wc->apply($this->width, $this->parent->width);
818
    } else {
819
      return $wc->apply($this->width, $this->width);
820
    }
821
  }
822
 
823
  // Unlike real/constrained width, or min/max width,
824
  // expandable width shows the size current box CAN be expanded;
825
  // it is pretty obvious that width-constrained boxes will never be expanded;
826
  // any other box can be expanded up to its parent _expandable_ width -
827
  // as parent can be expanded too.
828
  //
829
  function get_expandable_width() {
830
    $wc = $this->get_css_property(CSS_WIDTH);
831
    if ($wc->isNull() && $this->parent) {
832
      return $this->parent->get_expandable_width();
833
    } else {
834
      return $this->get_width();
835
    };
836
  }
837
 
838
  function put_width($value) {
839
    // TODO: constraints
840
    $this->width = $value;
841
  }
842
 
843
  function get_height() {
844
    if ($this->_height_constraint->applicable($this)) {
845
      return $this->_height_constraint->apply($this->height, $this);
846
    } else {
847
      return $this->height;
848
    };
849
  }
850
 
851
  function get_height_padded() {
852
    return $this->get_height() + $this->get_padding_top() + $this->get_padding_bottom();
853
  }
854
 
855
  function put_height($value) {
856
    if ($this->_height_constraint->applicable($this)) {
857
      $this->height = $this->_height_constraint->apply($value, $this);
858
    } else {
859
      $this->height = $value;
860
    };
861
  }
862
 
863
  function put_full_height($value) {
864
    $this->put_height($value - $this->_get_vert_extra());
865
  }
866
 
867
  // Returns total height of current element:
868
  // top padding + top margin + content + bottom padding + bottom margin + top border + bottom border
869
  function get_full_height() {
870
    return $this->get_height() +
871
      $this->get_extra_top() +
872
      $this->get_extra_bottom();
873
  }
874
 
875
  function get_real_full_height() {
876
    return $this->get_full_height();
877
  }
878
 
879
  function out_of_flow() {
880
    $position = $this->get_css_property(CSS_POSITION);
881
    $display  = $this->get_css_property(CSS_DISPLAY);
882
 
883
    return
884
      $position == POSITION_ABSOLUTE ||
885
      $position == POSITION_FIXED ||
886
      $display == 'none';
887
  }
888
 
889
  function moveto($x, $y) { $this->offset($x - $this->get_left(), $y - $this->get_top()); }
890
 
891
  function show(&$viewport) {
892
    $border     = $this->get_css_property(CSS_BORDER);
893
    $background = $this->get_css_property(CSS_BACKGROUND);
894
 
895
    // Draw border of the box
896
    $border->show($viewport, $this);
897
 
898
    // Render background of the box
899
    $background->show($viewport, $this);
900
 
901
    parent::show($viewport);
902
 
903
    return true;
904
  }
905
 
906
  function show_fixed(&$viewport) {
907
    return $this->show($viewport);
908
  }
909
 
910
  function is_null() {
911
    return false;
912
  }
913
 
914
  function line_break_allowed() {
915
    $white_space = $this->get_css_property(CSS_WHITE_SPACE);
916
    $nowrap      = $this->get_css_property(CSS_HTML2PS_NOWRAP);
917
 
918
    return
919
      ($white_space === WHITESPACE_NORMAL ||
920
       $white_space === WHITESPACE_PRE_WRAP ||
921
       $white_space === WHITESPACE_PRE_LINE) &&
922
      $nowrap === NOWRAP_NORMAL;
923
  }
924
 
925
  function get_left_background()   { return $this->get_left_padding();   }
926
  function get_right_background()  { return $this->get_right_padding();  }
927
  function get_top_background()    { return $this->get_top_padding();    }
928
  function get_bottom_background() { return $this->get_bottom_padding(); }
929
 
930
  function isVisibleInFlow() {
931
    $visibility = $this->get_css_property(CSS_VISIBILITY);
932
    $position   = $this->get_css_property(CSS_POSITION);
933
 
934
    return
935
      $visibility === VISIBILITY_VISIBLE &&
936
      $position !== POSITION_FIXED;
937
  }
938
 
939
  function reflow_footnote(&$parent, &$context) {
940
    $this->reflow_static($parent, $context);
941
  }
942
 
943
  /**
944
   * The  'top'  and 'bottom'  properties  move relatively  positioned
945
   * element(s) up  or down without  changing their size.  'top' moves
946
   * the boxes down,  and 'bottom' moves them up.  Since boxes are not
947
   * split or stretched as a result of 'top' or 'bottom', the computed
948
   * values  are always:  top =  -bottom.  If both  are 'auto',  their
949
   * computed  values are  both  '0'. If  one  of them  is 'auto',  it
950
   * becomes the negative of the other. If neither is 'auto', 'bottom'
951
   * is ignored  (i.e., the computed  value of 'bottom' will  be minus
952
   * the value of 'top').
953
   */
954
  function offsetRelative() {
955
    /**
956
     * Note  that  percentage   positioning  values  are  ignored  for
957
     * relative positioning
958
     */
959
 
960
    /**
961
     * Check if 'top' value is percentage
962
     */
963
    $top = $this->get_css_property(CSS_TOP);
964
    if ($top->isNormal()) {
965
      $top_value = $top->getPoints();
966
    } elseif ($top->isPercentage()) {
967
      $containing_block = $this->_get_containing_block();
968
      $containing_block_height = $containing_block['top'] - $containing_block['bottom'];
969
      $top_value = $containing_block_height * $top->getPercentage() / 100;
970
    } elseif ($top->isAuto()) {
971
      $top_value = null;
972
    }
973
 
974
    /**
975
     * Check if 'bottom' value is percentage
976
     */
977
    $bottom = $this->get_css_property(CSS_BOTTOM);
978
    if ($bottom->isNormal()) {
979
      $bottom_value = $bottom->getPoints();
980
    } elseif ($bottom->isPercentage()) {
981
      $containing_block = $this->_get_containing_block();
982
      $containing_block_height = $containing_block['top'] - $containing_block['bottom'];
983
      $bottom_value = $containing_block_height * $bottom->getPercentage() / 100;
984
    } elseif ($bottom->isAuto()) {
985
      $bottom_value = null;
986
    }
987
 
988
    /**
989
     * Calculate vertical offset for relative positioned box
990
     */
991
    if (!is_null($top_value)) {
992
      $vertical_offset = -$top_value;
993
    } elseif (!is_null($bottom_value)) {
994
      $vertical_offset = $bottom_value;
995
    } else {
996
      $vertical_offset = 0;
997
    };
998
 
999
    /**
1000
     * Check if 'left' value is percentage
1001
     */
1002
    $left = $this->get_css_property(CSS_LEFT);
1003
    if ($left->isNormal()) {
1004
      $left_value = $left->getPoints();
1005
    } elseif ($left->isPercentage()) {
1006
      $containing_block = $this->_get_containing_block();
1007
      $containing_block_width = $containing_block['right'] - $containing_block['left'];
1008
      $left_value = $containing_block_width * $left->getPercentage() / 100;
1009
    } elseif ($left->isAuto()) {
1010
      $left_value = null;
1011
    }
1012
 
1013
    /**
1014
     * Check if 'right' value is percentage
1015
     */
1016
    $right = $this->get_css_property(CSS_RIGHT);
1017
    if ($right->isNormal()) {
1018
      $right_value = $right->getPoints();
1019
    } elseif ($right->isPercentage()) {
1020
      $containing_block = $this->_get_containing_block();
1021
      $containing_block_width = $containing_block['right'] - $containing_block['left'];
1022
      $right_value = $containing_block_width * $right->getPercentage() / 100;
1023
    } elseif ($right->isAuto()) {
1024
      $right_value = null;
1025
    }
1026
 
1027
    /**
1028
     * Calculate vertical offset for relative positioned box
1029
     */
1030
    if (!is_null($left_value)) {
1031
      $horizontal_offset = $left_value;
1032
    } elseif (!is_null($right_value)) {
1033
      $horizontal_offset = -$right_value;
1034
    } else {
1035
      $horizontal_offset = 0;
1036
    };
1037
 
1038
    $this->offset($horizontal_offset,
1039
                  $vertical_offset);
1040
  }
1041
}
1042
?>