1<?php 2// $Header: /cvsroot/html2ps/box.generic.formatted.php,v 1.21 2007/02/18 09:55:10 Konstantin Exp $ 3 4require_once(HTML2PS_DIR.'doc.anchor.class.php'); 5require_once(HTML2PS_DIR.'layout.vertical.php'); 6 7class 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?>