Spaces:
Sleeping
Sleeping
| // $Header: /cvsroot/html2ps/box.container.php,v 1.67 2007/01/10 03:40:28 Konstantin Exp $ | |
| require_once(HTML2PS_DIR.'strategy.width.min.php'); | |
| require_once(HTML2PS_DIR.'strategy.width.min.nowrap.php'); | |
| require_once(HTML2PS_DIR.'strategy.width.max.php'); | |
| require_once(HTML2PS_DIR.'strategy.width.max.natural.php'); | |
| /** | |
| * @package HTML2PS | |
| * @subpackage Document | |
| * | |
| * This file contains the abstract class describing the behavior of document element | |
| * containing some other document elements. | |
| */ | |
| /** | |
| * @package HTML2PS | |
| * @subpackage Document | |
| * | |
| * The GenericContainerBox class is a common superclass for all document elements able | |
| * to contain other elements. This class does provide the line-box handling utilies and | |
| * some minor float related-functions. | |
| * | |
| */ | |
| class GenericContainerBox extends GenericFormattedBox { | |
| /** | |
| * @var Array A list of contained elements (of type GenericFormattedBox) | |
| * @access public | |
| */ | |
| var $content; | |
| var $_first_line; | |
| /** | |
| * @var Array A list of child nodes in the current line box; changes dynamically | |
| * during the reflow process. | |
| * @access private | |
| */ | |
| var $_line; | |
| /** | |
| * Sometimes floats may appear inside the line box, consider the following code, | |
| * for example: "<div>text<div style='float:left'>float</div>word</div>". In | |
| * this case, the floating DIV should be rendered below the "text word" line; | |
| * thus, we need to keep a list of deferred floating elements and render them | |
| * when current line box closes. | |
| * | |
| * @var Array A list of floats which should be flown after current line box ends; | |
| * @access private | |
| */ | |
| var $_deferred_floats; | |
| /** | |
| * @var float Current output X value inside the current element | |
| * @access public | |
| */ | |
| var $_current_x; | |
| /** | |
| * @var float Current output Y value inside the current element | |
| * @access public | |
| */ | |
| var $_current_y; | |
| function destroy() { | |
| for ($i=0, $size = count($this->content); $i < $size; $i++) { | |
| $this->content[$i]->destroy(); | |
| }; | |
| unset($this->content); | |
| parent::destroy(); | |
| } | |
| /** | |
| * Render current container box using the specified output method. | |
| * | |
| * @param OutputDriver $driver The output driver object | |
| * | |
| * @return Boolean flag indicating the success or 'null' value in case of critical rendering | |
| * error | |
| */ | |
| function show(&$driver) { | |
| GenericFormattedBox::show($driver); | |
| $overflow = $this->getCSSProperty(CSS_OVERFLOW); | |
| /** | |
| * Sometimes the content may overflow container boxes. This situation arise, for example, | |
| * for relative-positioned child boxes, boxes having constrained height and in some | |
| * other cases. If the container box does not have CSS 'overflow' property | |
| * set to 'visible' value, the content should be visually clipped using container box | |
| * padding area. | |
| */ | |
| if ($overflow !== OVERFLOW_VISIBLE) { | |
| $driver->save(); | |
| $this->_setupClip($driver); | |
| }; | |
| /** | |
| * Render child elements | |
| */ | |
| for ($i=0, $size = count($this->content); $i < $size; $i++) { | |
| $child =& $this->content[$i]; | |
| /** | |
| * We'll check the visibility property here | |
| * Reason: all boxes (except the top-level one) are contained in some other box, | |
| * so every box will pass this check. The alternative is to add this check into every | |
| * box class show member. | |
| * | |
| * The only exception of absolute positioned block boxes which are drawn separately; | |
| * their show method is called explicitly; the similar check should be performed there | |
| */ | |
| if ($child->isVisibleInFlow()) { | |
| /** | |
| * To reduce the drawing overhead, we'll check if some part if current child element | |
| * belongs to current output page. If not, there will be no reason to draw this | |
| * child this time. | |
| * | |
| * @see OutputDriver::contains() | |
| * | |
| * @todo In rare cases the element content may be placed outside the element itself; | |
| * in such situantion content may be visible on the page, while element is not. | |
| * This situation should be resolved somehow. | |
| */ | |
| if ($driver->contains($child)) { | |
| if (is_null($child->show($driver))) { | |
| return null; | |
| }; | |
| }; | |
| }; | |
| } | |
| /** | |
| * Restore previous clipping mode, if it have been modified for non-'overflow: visible' | |
| * box. | |
| */ | |
| if ($overflow !== OVERFLOW_VISIBLE) { | |
| $driver->restore(); | |
| }; | |
| return true; | |
| } | |
| /** | |
| * Render current fixed-positioned container box using the specified output method. Unlike | |
| * the 'show' method, there's no check if current page viewport contains current element, as | |
| * fixed-positioned may be drawn on the page margins, outside the viewport. | |
| * | |
| * @param OutputDriver $driver The output driver object | |
| * | |
| * @return Boolean flag indicating the success or 'null' value in case of critical rendering | |
| * error | |
| * | |
| * @see GenericContainerBox::show() | |
| * | |
| * @todo the 'show' and 'show_fixed' method code are almost the same except the child element | |
| * method called in the inner loop; also, no check is done if current viewport contains this element, | |
| * thus sllowinf printing data on page margins, where no data should be printed normally | |
| * I suppose some more generic method containing the common code should be made. | |
| */ | |
| function show_fixed(&$driver) { | |
| GenericFormattedBox::show($driver); | |
| $overflow = $this->getCSSProperty(CSS_OVERFLOW); | |
| /** | |
| * Sometimes the content may overflow container boxes. This situation arise, for example, | |
| * for relative-positioned child boxes, boxes having constrained height and in some | |
| * other cases. If the container box does not have CSS 'overflow' property | |
| * set to 'visible' value, the content should be visually clipped using container box | |
| * padding area. | |
| */ | |
| if ($overflow !== OVERFLOW_VISIBLE) { | |
| // Save graphics state (of course, BEFORE the clipping area will be set) | |
| $driver->save(); | |
| $this->_setupClip($driver); | |
| }; | |
| /** | |
| * Render child elements | |
| */ | |
| $size = count($this->content); | |
| for ($i=0; $i < $size; $i++) { | |
| /** | |
| * We'll check the visibility property here | |
| * Reason: all boxes (except the top-level one) are contained in some other box, | |
| * so every box will pass this check. The alternative is to add this check into every | |
| * box class show member. | |
| * | |
| * The only exception of absolute positioned block boxes which are drawn separately; | |
| * their show method is called explicitly; the similar check should be performed there | |
| */ | |
| $child =& $this->content[$i]; | |
| if ($child->getCSSProperty(CSS_VISIBILITY) === VISIBILITY_VISIBLE) { | |
| // Fixed-positioned blocks are displayed separately; | |
| // If we call them now, they will be drawn twice | |
| if ($child->getCSSProperty(CSS_POSITION) != POSITION_FIXED) { | |
| if (is_null($child->show_fixed($driver))) { | |
| return null; | |
| }; | |
| }; | |
| }; | |
| } | |
| /** | |
| * Restore previous clipping mode, if it have been modified for non-'overflow: visible' | |
| * box. | |
| */ | |
| if ($overflow !== OVERFLOW_VISIBLE) { | |
| $driver->restore(); | |
| }; | |
| return true; | |
| } | |
| function _find(&$box) { | |
| $size = count($this->content); | |
| for ($i=0; $i<$size; $i++) { | |
| if ($this->content[$i]->uid == $box->uid) { | |
| return $i; | |
| }; | |
| } | |
| return null; | |
| } | |
| // Inserts new child box at the specified (zero-based) offset; 0 stands for first child | |
| // | |
| // @param $index index to insert child at | |
| // @param $box child to be inserted | |
| // | |
| function insert_child($index, &$box) { | |
| $box->parent =& $this; | |
| // Offset the content array | |
| for ($i = count($this->content)-1; $i>= $index; $i--) { | |
| $this->content[$i+1] =& $this->content[$i]; | |
| }; | |
| $this->content[$index] =& $box; | |
| } | |
| function insertBefore(&$what, &$where) { | |
| if ($where) { | |
| $index = $this->_find($where); | |
| if (is_null($index)) { | |
| return null; | |
| }; | |
| $this->insert_child($index, $what); | |
| } else { | |
| // If 'where' is not specified, 'what' should become the last child | |
| $this->add_child($what); | |
| }; | |
| return $what; | |
| } | |
| function add_child(&$box) { | |
| // In general, this function is called like following: | |
| // $box->add_child(create_pdf_box(...)) | |
| // As create_pdf_box _may_ return null value (for example, for an empty text node), | |
| // we should process the case of $box == null here | |
| if ($box) { | |
| $box->parent =& $this; | |
| $this->content[] =& $box; | |
| }; | |
| } | |
| // Get first child of current box which actually will be drawn | |
| // on the page. So, whitespace and null boxes will be ignored | |
| // | |
| // See description of is_null for null box definition. | |
| // (not only NullBox is treated as null box) | |
| // | |
| // @return reference to the first visible child of current box | |
| function &get_first() { | |
| $size = count($this->content); | |
| for ($i=0; $i<$size; $i++) { | |
| if (!is_whitespace($this->content[$i]) && | |
| !$this->content[$i]->is_null()) { | |
| return $this->content[$i]; | |
| }; | |
| }; | |
| // We use this construct to avoid notice messages in PHP 4.4 and PHP 5 | |
| $dummy = null; | |
| return $dummy; | |
| } | |
| // Get first text or image child of current box which actually will be drawn | |
| // on the page. | |
| // | |
| // See description of is_null for null box definition. | |
| // (not only NullBox is treated as null box) | |
| // | |
| // @return reference to the first visible child of current box | |
| function &get_first_data() { | |
| $size = count($this->content); | |
| for ($i=0; $i<$size; $i++) { | |
| if (!is_whitespace($this->content[$i]) && !$this->content[$i]->is_null()) { | |
| if (is_container($this->content[$i])) { | |
| $data =& $this->content[$i]->get_first_data(); | |
| if (!is_null($data)) { return $data; }; | |
| } else { | |
| return $this->content[$i]; | |
| }; | |
| }; | |
| }; | |
| // We use this construct to avoid notice messages in PHP 4.4 and PHP 5 | |
| $dummy = null; | |
| return $dummy; | |
| } | |
| // Get last child of current box which actually will be drawn | |
| // on the page. So, whitespace and null boxes will be ignored | |
| // | |
| // See description of is_null for null box definition. | |
| // (not only NullBox is treated as null box) | |
| // | |
| // @return reference to the last visible child of current box | |
| function &get_last() { | |
| for ($i=count($this->content)-1; $i>=0; $i--) { | |
| if (!is_whitespace($this->content[$i]) && !$this->content[$i]->is_null()) { | |
| return $this->content[$i]; | |
| }; | |
| }; | |
| // We use this construct to avoid notice messages in PHP 4.4 and PHP 5 | |
| $dummy = null; | |
| return $dummy; | |
| } | |
| function offset_if_first(&$box, $dx, $dy) { | |
| if ($this->is_first($box)) { | |
| // The top-level box (page box) should never be offset | |
| if ($this->parent) { | |
| if (!$this->parent->offset_if_first($box, $dx, $dy)) { | |
| $this->offset($dx, $dy); | |
| return true; | |
| }; | |
| }; | |
| }; | |
| return false; | |
| } | |
| function offset($dx, $dy) { | |
| parent::offset($dx, $dy); | |
| $this->_current_x += $dx; | |
| $this->_current_y += $dy; | |
| // Offset contents | |
| $size = count($this->content); | |
| for ($i=0; $i < $size; $i++) { | |
| $this->content[$i]->offset($dx, $dy); | |
| } | |
| } | |
| function GenericContainerBox() { | |
| $this->GenericFormattedBox(); | |
| // By default, box does not have any content | |
| $this->content = array(); | |
| // Initialize line box | |
| $this->_line = array(); | |
| // Initialize floats-related stuff | |
| $this->_deferred_floats = array(); | |
| $this->_additional_text_indent = 0; | |
| // Current-point | |
| $this->_current_x = 0; | |
| $this->_current_y = 0; | |
| // Initialize floating children array | |
| $this->_floats = array(); | |
| } | |
| function add_deferred_float(&$float) { | |
| $this->_deferred_floats[] =& $float; | |
| } | |
| /** | |
| * Create the child nodes of current container object using the parsed HTML data | |
| * | |
| * @param mixed $root node corresponding to the current container object | |
| */ | |
| function create_content(&$root, &$pipeline) { | |
| // Initialize content | |
| $child = $root->first_child(); | |
| while ($child) { | |
| $box_child =& create_pdf_box($child, $pipeline); | |
| $this->add_child($box_child); | |
| $child = $child->next_sibling(); | |
| }; | |
| } | |
| // Content-handling functions | |
| function is_container() { | |
| return true; | |
| } | |
| // Get total height of this box content (including floats, if any) | |
| // Note that floats can be contained inside children, so we'll need to use | |
| // this function recusively | |
| function get_real_full_height() { | |
| $content_size = count($this->content); | |
| $overflow = $this->getCSSProperty(CSS_OVERFLOW); | |
| // Treat items with overflow: hidden specifically, | |
| // as floats flown out of this boxes will not be visible | |
| if ($overflow == OVERFLOW_HIDDEN) { | |
| return $this->get_full_height(); | |
| }; | |
| // Check if this object is totally empty | |
| $first = $this->get_first(); | |
| if (is_null($first)) { | |
| return 0; | |
| }; | |
| // Initialize the vertical extent taken by content using the | |
| // very first child | |
| $max_top = $first->get_top_margin(); | |
| $min_bottom = $first->get_bottom_margin(); | |
| for ($i=0; $i<$content_size; $i++) { | |
| if (!$this->content[$i]->is_null()) { | |
| // Check if top margin of current child is to the up | |
| // of vertical extent top margin | |
| $max_top = max($max_top, $this->content[$i]->get_top_margin()); | |
| /** | |
| * Check if current child bottom margin will extend | |
| * the vertical space OR if it contains floats extending | |
| * this, unless this child have overflow: hidden, because this | |
| * will prevent additional content to be visible | |
| */ | |
| if (!$this->content[$i]->is_container()) { | |
| $min_bottom = min($min_bottom, | |
| $this->content[$i]->get_bottom_margin()); | |
| } else { | |
| $content_overflow = $this->content[$i]->getCSSProperty(CSS_OVERFLOW); | |
| if ($content_overflow == OVERFLOW_HIDDEN) { | |
| $min_bottom = min($min_bottom, | |
| $this->content[$i]->get_bottom_margin()); | |
| } else { | |
| $min_bottom = min($min_bottom, | |
| $this->content[$i]->get_bottom_margin(), | |
| $this->content[$i]->get_top_margin() - | |
| $this->content[$i]->get_real_full_height()); | |
| }; | |
| }; | |
| }; | |
| } | |
| return max(0, $max_top - $min_bottom) + $this->_get_vert_extra(); | |
| } | |
| // LINE-LENGTH RELATED FUNCTIONS | |
| function _line_length() { | |
| $sum = 0; | |
| $size = count($this->_line); | |
| for ($i=0; $i < $size; $i++) { | |
| // Note that the line length should include the inline boxes margin/padding | |
| // as inline boxes are not directly included to the parent line box, | |
| // we'll need to check the parent of current line box element, | |
| // and, if it is an inline box, AND this element is last or first contained element | |
| // add correcponsing padding value | |
| $element =& $this->_line[$i]; | |
| if (isset($element->wrapped) && !is_null($element->wrapped)) { | |
| if ($i==0) { | |
| $sum += $element->get_full_width() - $element->getWrappedWidth(); | |
| } else { | |
| $sum += $element->getWrappedWidthAndHyphen(); | |
| }; | |
| } else { | |
| $sum += $element->get_full_width(); | |
| }; | |
| if ($element->parent) { | |
| $first = $element->parent->get_first(); | |
| $last = $element->parent->get_last(); | |
| if (!is_null($first) && $first->uid === $element->uid) { | |
| $sum += $element->parent->get_extra_line_left(); | |
| } | |
| if (!is_null($last) && $last->uid === $element->uid) { | |
| $sum += $element->parent->get_extra_line_right(); | |
| } | |
| }; | |
| } | |
| if ($this->_first_line) { | |
| $ti = $this->getCSSProperty(CSS_TEXT_INDENT); | |
| $sum += $ti->calculate($this); | |
| $sum += $this->_additional_text_indent; | |
| }; | |
| return $sum; | |
| } | |
| function _line_length_delta(&$context) { | |
| return max($this->get_available_width($context) - $this->_line_length(),0); | |
| } | |
| /** | |
| * Get the last box in current line box | |
| */ | |
| function &last_in_line() { | |
| $size = count($this->_line); | |
| if ($size < 1) { | |
| $dummy = null; | |
| return $dummy; | |
| }; | |
| return $this->_line[$size-1]; | |
| } | |
| // WIDTH | |
| function get_min_width_natural(&$context) { | |
| $content_size = count($this->content); | |
| /** | |
| * If box does not have any context, its minimal width is determined by extra horizontal space: | |
| * padding, border width and margins | |
| */ | |
| if ($content_size == 0) { | |
| $min_width = $this->_get_hor_extra(); | |
| return $min_width; | |
| }; | |
| /** | |
| * If we're in 'nowrap' mode, minimal and maximal width will be equal | |
| */ | |
| $white_space = $this->getCSSProperty(CSS_WHITE_SPACE); | |
| $pseudo_nowrap = $this->getCSSProperty(CSS_HTML2PS_NOWRAP); | |
| if ($white_space == WHITESPACE_NOWRAP || | |
| $pseudo_nowrap == NOWRAP_NOWRAP) { | |
| $min_width = $this->get_min_nowrap_width($context); | |
| return $min_width; | |
| } | |
| /** | |
| * We need to add text indent size to the width of the first item | |
| */ | |
| $start_index = 0; | |
| while ($start_index < $content_size && | |
| $this->content[$start_index]->out_of_flow()) { | |
| $start_index++; | |
| }; | |
| if ($start_index < $content_size) { | |
| $ti = $this->getCSSProperty(CSS_TEXT_INDENT); | |
| $minw = | |
| $ti->calculate($this) + | |
| $this->content[$start_index]->get_min_width_natural($context); | |
| } else { | |
| $minw = 0; | |
| }; | |
| for ($i=$start_index; $i<$content_size; $i++) { | |
| $item =& $this->content[$i]; | |
| if (!$item->out_of_flow()) { | |
| $minw = max($minw, $item->get_min_width($context)); | |
| }; | |
| } | |
| /** | |
| * Apply width constraint to min width. Return maximal value | |
| */ | |
| $wc = $this->getCSSProperty(CSS_WIDTH); | |
| $containing_block =& $this->_get_containing_block(); | |
| $min_width = $minw; | |
| return $min_width; | |
| } | |
| function get_min_width(&$context) { | |
| $strategy = new StrategyWidthMin(); | |
| return $strategy->apply($this, $context); | |
| } | |
| function get_min_nowrap_width(&$context) { | |
| $strategy = new StrategyWidthMinNowrap(); | |
| return $strategy->apply($this, $context); | |
| } | |
| // Note: <table width="100%" inside some block box cause this box to expand | |
| // $limit - maximal width which should not be exceeded; by default, there's no limit at all | |
| // | |
| function get_max_width_natural(&$context, $limit=10E6) { | |
| $strategy = new StrategyWidthMaxNatural($limit); | |
| return $strategy->apply($this, $context); | |
| } | |
| function get_max_width(&$context, $limit=10E6) { | |
| $strategy = new StrategyWidthMax($limit); | |
| return $strategy->apply($this, $context); | |
| } | |
| function close_line(&$context, $lastline = false) { | |
| // Align line-box using 'text-align' property | |
| $size = count($this->_line); | |
| if ($size > 0) { | |
| $last_item =& $this->_line[$size-1]; | |
| if (is_whitespace($last_item)) { | |
| $last_item->width = 0; | |
| $last_item->height = 0; | |
| }; | |
| }; | |
| // Note that text-align should not be applied to the block boxes! | |
| // As block boxes will be alone in the line-box, we can check | |
| // if the very first box in the line is inline; if not - no justification should be made | |
| // | |
| if ($size > 0) { | |
| if (is_inline($this->_line[0])) { | |
| $cb = CSSTextAlign::value2pdf($this->getCSSProperty(CSS_TEXT_ALIGN)); | |
| $cb($this, $context, $lastline); | |
| } else { | |
| // Nevertheless, CENTER tag and P/DIV with ALIGN attribute set should affect the | |
| // position of non-inline children. | |
| $cb = CSSPseudoAlign::value2pdf($this->getCSSProperty(CSS_HTML2PS_ALIGN)); | |
| $cb($this, $context, $lastline); | |
| }; | |
| }; | |
| // Apply vertical align to all of the line content | |
| // first, we need to aling all baseline-aligned boxes to determine the basic line-box height, top and bottom edges | |
| // then, SUP and SUP positioned boxes (as they can extend the top and bottom edges, but not affected themselves) | |
| // then, MIDDLE, BOTTOM and TOP positioned boxes in the given order | |
| // | |
| $baselined = array(); | |
| $baseline = 0; | |
| $height = 0; | |
| for ($i=0; $i < $size; $i++) { | |
| $vertical_align = $this->_line[$i]->getCSSProperty(CSS_VERTICAL_ALIGN); | |
| if ($vertical_align == VA_BASELINE) { | |
| // Add current baseline-aligned item to the baseline | |
| // | |
| $baselined[] =& $this->_line[$i]; | |
| $baseline = max($baseline, | |
| $this->_line[$i]->default_baseline); | |
| }; | |
| }; | |
| $size_baselined = count($baselined); | |
| for ($i=0; $i < $size_baselined; $i++) { | |
| $baselined[$i]->baseline = $baseline; | |
| $height = max($height, | |
| $baselined[$i]->get_full_height() + $baselined[$i]->getBaselineOffset(), | |
| $baselined[$i]->get_ascender() + $baselined[$i]->get_descender()); | |
| }; | |
| // SUB vertical align | |
| // | |
| for ($i=0; $i < $size; $i++) { | |
| $vertical_align = $this->_line[$i]->getCSSProperty(CSS_VERTICAL_ALIGN); | |
| if ($vertical_align == VA_SUB) { | |
| $this->_line[$i]->baseline = | |
| $baseline + $this->_line[$i]->get_full_height()/2; | |
| }; | |
| } | |
| // SUPER vertical align | |
| // | |
| for ($i=0; $i < $size; $i++) { | |
| $vertical_align = $this->_line[$i]->getCSSProperty(CSS_VERTICAL_ALIGN); | |
| if ($vertical_align == VA_SUPER) { | |
| $this->_line[$i]->baseline = $this->_line[$i]->get_full_height()/2; | |
| }; | |
| } | |
| // MIDDLE vertical align | |
| // | |
| $middle = 0; | |
| for ($i=0; $i < $size; $i++) { | |
| $vertical_align = $this->_line[$i]->getCSSProperty(CSS_VERTICAL_ALIGN); | |
| if ($vertical_align == VA_MIDDLE) { | |
| $middle = max($middle, $this->_line[$i]->get_full_height() / 2); | |
| }; | |
| }; | |
| if ($middle * 2 > $height) { | |
| // Offset already aligned items | |
| // | |
| for ($i=0; $i < $size; $i++) { | |
| $this->_line[$i]->baseline += ($middle - $height/2); | |
| }; | |
| $height = $middle * 2; | |
| }; | |
| for ($i=0; $i < $size; $i++) { | |
| $vertical_align = $this->_line[$i]->getCSSProperty(CSS_VERTICAL_ALIGN); | |
| if ($vertical_align == VA_MIDDLE) { | |
| $this->_line[$i]->baseline = $this->_line[$i]->default_baseline + ($height/2 - $this->_line[$i]->get_full_height()/2); | |
| }; | |
| } | |
| // BOTTOM vertical align | |
| // | |
| $bottom = 0; | |
| for ($i=0; $i < $size; $i++) { | |
| $vertical_align = $this->_line[$i]->getCSSProperty(CSS_VERTICAL_ALIGN); | |
| if ($vertical_align == VA_BOTTOM) { | |
| $bottom = max($bottom, $this->_line[$i]->get_full_height()); | |
| }; | |
| }; | |
| if ($bottom > $height) { | |
| // Offset already aligned items | |
| // | |
| for ($i=0; $i < $size; $i++) { | |
| $this->_line[$i]->baseline += ($bottom - $height); | |
| }; | |
| $height = $bottom; | |
| }; | |
| for ($i=0; $i < $size; $i++) { | |
| $vertical_align = $this->_line[$i]->getCSSProperty(CSS_VERTICAL_ALIGN); | |
| if ($vertical_align == VA_BOTTOM) { | |
| $this->_line[$i]->baseline = $this->_line[$i]->default_baseline + $height - $this->_line[$i]->get_full_height(); | |
| }; | |
| } | |
| // TOP vertical align | |
| // | |
| $bottom = 0; | |
| for ($i=0; $i < $size; $i++) { | |
| $vertical_align = $this->_line[$i]->getCSSProperty(CSS_VERTICAL_ALIGN); | |
| if ($vertical_align == VA_TOP) { | |
| $bottom = max($bottom, $this->_line[$i]->get_full_height()); | |
| }; | |
| }; | |
| if ($bottom > $height) { | |
| $height = $bottom; | |
| }; | |
| for ($i=0; $i < $size; $i++) { | |
| $vertical_align = $this->_line[$i]->getCSSProperty(CSS_VERTICAL_ALIGN); | |
| if ($vertical_align == VA_TOP) { | |
| $this->_line[$i]->baseline = $this->_line[$i]->default_baseline; | |
| }; | |
| } | |
| // Calculate the bottom Y coordinate of last line box | |
| // | |
| $line_bottom = $this->_current_y; | |
| foreach ($this->_line AS $line_element) { | |
| // This line is required; say, we have sequence of text and image inside the container, | |
| // AND image have greater baseline than text; in out case, text will be offset to the bottom | |
| // of the page and we lose the gap between text and container bottom edge, unless we'll re-extend | |
| // containier height | |
| // Note that we're using the colapsed margin value to get the Y coordinate to extend height to, | |
| // as bottom margin may be collapsed with parent | |
| $effective_bottom = | |
| $line_element->get_top() - | |
| $line_element->get_height(); | |
| $this->extend_height($effective_bottom); | |
| $line_bottom = min($effective_bottom, $line_bottom); | |
| } | |
| $this->extend_height($line_bottom); | |
| // Clear the line box | |
| $this->_line = array(); | |
| // Reset current X coordinate to the far left | |
| $this->_current_x = $this->get_left(); | |
| // Extend Y coordinate | |
| $this->_current_y = $line_bottom; | |
| // Render the deferred floats | |
| for ($i = 0, $size = count($this->_deferred_floats); $i < $size; $i++) { | |
| $this->_deferred_floats[$i]->reflow_static_float($this, $context); | |
| }; | |
| // Clear deferred float list | |
| $this->_deferred_floats = array(); | |
| // modify the current-x value, so that next inline box will not intersect any floating boxes | |
| $this->_current_x = $context->float_left_x($this->_current_x, $this->_current_y); | |
| $this->_first_line = false; | |
| } | |
| function append_line(&$item) { | |
| $this->_line[] =& $item; | |
| } | |
| // Line box should be treated as empty in following cases: | |
| // 1. It is really empty (so, it contains 0 boxes) | |
| // 2. It contains only whitespace boxes | |
| function line_box_empty() { | |
| $size = count($this->_line); | |
| if ($size == 0) { return true; } | |
| // Scan line box | |
| for ($i=0; $i<$size; $i++) { | |
| if (!is_whitespace($this->_line[$i]) && | |
| !$this->_line[$i]->is_null()) { return false; }; | |
| } | |
| // No non-whitespace boxes were found | |
| return true; | |
| } | |
| function reflow_anchors(&$viewport, &$anchors) { | |
| GenericFormattedBox::reflow_anchors($viewport, $anchors); | |
| $size = count($this->content); | |
| for ($i=0; $i<$size; $i++) { | |
| $this->content[$i]->reflow_anchors($viewport, $anchors); | |
| } | |
| } | |
| function fitFloats(&$context) { | |
| $float_bottom = $context->float_bottom(); | |
| if (!is_null($float_bottom)) { | |
| $this->extend_height($float_bottom); | |
| }; | |
| $float_right = $context->float_right(); | |
| if (!is_null($float_right)) { | |
| $this->extend_width($float_right); | |
| }; | |
| } | |
| function reflow_content(&$context) { | |
| $text_indent = $this->getCSSProperty(CSS_TEXT_INDENT); | |
| $this->close_line($context); | |
| $this->_first_line = true; | |
| // If first child is inline - apply text-indent | |
| $first = $this->get_first(); | |
| if (!is_null($first)) { | |
| if (is_inline($first)) { | |
| $this->_current_x += $text_indent->calculate($this); | |
| $this->_current_x += $this->_additional_text_indent; | |
| }; | |
| }; | |
| $this->height = 0; | |
| // Reset current Y value | |
| $this->_current_y = $this->get_top(); | |
| $size = count($this->content); | |
| for ($i=0; $i < $size; $i++) { | |
| $child =& $this->content[$i]; | |
| $child->reflow($this, $context); | |
| }; | |
| $this->close_line($context, true); | |
| } | |
| function reflow_inline() { | |
| $size = count($this->content); | |
| for ($i=0; $i<$size; $i++) { | |
| $this->content[$i]->reflow_inline(); | |
| }; | |
| } | |
| function reflow_text(&$viewport) { | |
| $size = count($this->content); | |
| for ($i=0; $i<$size; $i++) { | |
| if (is_null($this->content[$i]->reflow_text($viewport))) { | |
| return null; | |
| }; | |
| } | |
| return true; | |
| } | |
| /** | |
| * Position/size current box as floating one | |
| */ | |
| function reflow_static_float(&$parent, &$context) { | |
| // Defer the float rendering till the next line box | |
| if (!$parent->line_box_empty()) { | |
| $parent->add_deferred_float($this); | |
| return; | |
| }; | |
| // Calculate margin values if they have been set as a percentage | |
| $this->_calc_percentage_margins($parent); | |
| $this->_calc_percentage_padding($parent); | |
| // Calculate width value if it have been set as a percentage | |
| $this->_calc_percentage_width($parent, $context); | |
| // Calculate margins and/or width is 'auto' values have been specified | |
| $this->_calc_auto_width_margins($parent); | |
| // Determine the actual width of the floating box | |
| // Note that get_max_width returns both content and extra width | |
| $this->put_full_width($this->get_max_width_natural($context, $this->parent->get_width())); | |
| // We need to call this function before determining the horizontal coordinate | |
| // as after vertical offset the additional space to the left may apperar | |
| $y = $this->apply_clear($parent->_current_y, $context); | |
| // determine the position of top-left floating box corner | |
| if ($this->getCSSProperty(CSS_FLOAT) === FLOAT_RIGHT) { | |
| $context->float_right_xy($parent, $this->get_full_width(), $x, $y); | |
| $x -= $this->get_full_width(); | |
| } else { | |
| $context->float_left_xy($parent, $this->get_full_width(), $x, $y); | |
| }; | |
| // Note that $x and $y contain just a free space corner coordinate; | |
| // If our float has a margin/padding space, we'll need to offset ot a little; | |
| // Remember that float margins are never collapsed! | |
| $this->moveto($x + $this->get_extra_left(), $y - $this->get_extra_top()); | |
| // Reflow contents. | |
| // Note that floating box creates a new float flow context for it children. | |
| $context->push_floats(); | |
| // Floating box create a separate margin collapsing context | |
| $context->push_collapsed_margin(0); | |
| $this->reflow_content($context); | |
| $context->pop_collapsed_margin(); | |
| // Floats and boxes with overflow: hidden | |
| // should completely enclose its child floats | |
| $this->fitFloats($context); | |
| // restore old float flow context | |
| $context->pop_floats(); | |
| // Add this box to the list of floats in current context | |
| $context->add_float($this); | |
| // Now fix the value of _current_x for the parent box; it is required | |
| // in the following case: | |
| // <body><img align="left">some text | |
| // in such situation floating image is flown immediately, but it the close_line call have been made before, | |
| // so _current_x value of container box will be still equal to ots left content edge; by calling float_left_x again, | |
| // we'll force "some text" to be offset to the right | |
| $parent->_current_x = $context->float_left_x($parent->_current_x, $parent->_current_y); | |
| } | |
| function reflow_whitespace(&$linebox_started, &$previous_whitespace) { | |
| $previous_whitespace = false; | |
| $linebox_started = false; | |
| $size = count($this->content); | |
| for ($i=0; $i<$size; $i++) { | |
| $child =& $this->content[$i]; | |
| $child->reflow_whitespace($linebox_started, $previous_whitespace); | |
| }; | |
| // remove the last whitespace in block box | |
| $this->remove_last_whitespace(); | |
| // Non-inline box have terminated; we may be sure that line box will be closed | |
| // at this moment and new line box after this will be generated | |
| if (!is_inline($this)) { | |
| $linebox_started = false; | |
| }; | |
| return; | |
| } | |
| function remove_last_whitespace() { | |
| if (count($this->content) == 0) { | |
| return; | |
| }; | |
| $i = count($this->content)-1; | |
| $last = $this->content[$i]; | |
| while ($i >= 0 && is_whitespace($this->content[$i])) { | |
| $this->remove($this->content[$i]); | |
| $i --; | |
| if ($i >= 0) { | |
| $last = $this->content[$i]; | |
| }; | |
| }; | |
| if ($i >= 0) { | |
| if (is_container($this->content[$i])) { | |
| $this->content[$i]->remove_last_whitespace(); | |
| }; | |
| }; | |
| } | |
| function remove(&$box) { | |
| $size = count($this->content); | |
| for ($i=0; $i<$size; $i++) { | |
| if ($this->content[$i]->uid === $box->uid) { | |
| $this->content[$i] = NullBox::create(); | |
| }; | |
| }; | |
| return; | |
| } | |
| function is_first(&$box) { | |
| $first =& $this->get_first(); | |
| // Check if there's no first box at all | |
| // | |
| if (is_null($first)) { return false; }; | |
| return $first->uid == $box->uid; | |
| } | |
| function is_null() { | |
| $size = count($this->content); | |
| for ($i=0; $i<$size; $i++) { | |
| if (!$this->content[$i]->is_null()) { return false; }; | |
| }; | |
| return true; | |
| } | |
| // Calculate the available widths - e.g. content width minus space occupied by floats; | |
| // as floats may not fill the whole height of this box, this value depends on Y-coordinate. | |
| // We use current_Y in calculations | |
| // | |
| function get_available_width(&$context) { | |
| $left_float_width = $context->float_left_x($this->get_left(), $this->_current_y) - $this->get_left(); | |
| $right_float_width = $this->get_right() - $context->float_right_x($this->get_right(), $this->_current_y); | |
| return $this->get_width() - $left_float_width - $right_float_width; | |
| } | |
| function pre_reflow_images() { | |
| $size = count($this->content); | |
| for ($i=0; $i<$size; $i++) { | |
| $this->content[$i]->pre_reflow_images(); | |
| }; | |
| } | |
| function _setupClip(&$driver) { | |
| if (!is_null($this->parent)) { | |
| $this->parent->_setupClip($driver); | |
| }; | |
| $overflow = $this->getCSSProperty(CSS_OVERFLOW); | |
| if ($overflow !== OVERFLOW_VISIBLE) { | |
| $driver->moveto( $this->get_left_border() , $this->get_top_border()); | |
| $driver->lineto( $this->get_right_border(), $this->get_top_border()); | |
| $driver->lineto( $this->get_right_border(), $this->get_bottom_border()); | |
| $driver->lineto( $this->get_left_border() , $this->get_bottom_border()); | |
| $driver->closepath(); | |
| $driver->clip(); | |
| }; | |
| } | |
| /** | |
| * DOMish functions | |
| */ | |
| function &get_element_by_id($id) { | |
| if (isset($GLOBALS['__html_box_id_map'])) { | |
| return $GLOBALS['__html_box_id_map'][$id]; | |
| } else { | |
| $dummy = null; | |
| return $dummy; | |
| }; | |
| } | |
| /* | |
| * this is just a fake at the moment | |
| */ | |
| function get_body() { | |
| return $this; | |
| } | |
| function getChildNodes() { | |
| return $this->content; | |
| } | |
| } | |