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