function apply(&$box, &$context) { $this->_maxw = 0; // We need to add text indent to the max width $text_indent = $box->getCSSProperty(CSS_TEXT_INDENT); # kornev if (!$text_indent) { $text_indent = CSSTextIndent::default_value(); } $this->_cmaxw = $text_indent->calculate($box); for ($i = 0, $size = count($box->content); $i < $size; $i++) { $child =& $box->content[$i]; if ($child->isLineBreak()) { $this->line_break(); } elseif (!$child->out_of_flow()) { if (is_inline($child) || $child->getCSSProperty(CSS_FLOAT) !== FLOAT_NONE) { $this->add_width($child->get_max_width($context, $this->_limit)); } else { $this->line_break(); $this->add_width($child->get_max_width($context, $this->_limit)); // Process special case with percentage constrained table $item_wc = $child->getCSSProperty(CSS_WIDTH); if (is_a($child, "TableBox") && is_a($item_wc, "WCFraction")) { $this->_cmaxw = max($this->_cmaxw, $item_wc->apply($box->get_width(), $box->parent->get_expandable_width())); } $this->line_break(); } } } // Check if last line have maximal width // $this->line_break(); // Note that max width cannot differ from constrained width, // if any width constraints apply // $wc = $box->getCSSProperty(CSS_WIDTH); # kornev if (!$wc) { $wc = CSSCompositeWidth::default_value(); } if ($wc->applicable($box)) { if ($box->parent) { $this->_maxw = $wc->apply($this->_maxw, $box->parent->get_width()); } else { $this->_maxw = $wc->apply($this->_maxw, $this->_maxw); } } return $this->_maxw + $box->_get_hor_extra(); }
function apply(&$box, &$context) { $this->_maxw = 0; // We need to add text indent to the width $ti = $box->getCSSProperty(CSS_TEXT_INDENT); # kornev if (!$ti) { $ti = CSSTextIndent::default_value(); } $this->add_width($ti->calculate($box)); for ($i = 0, $size = count($box->content); $i < $size; $i++) { $child =& $box->content[$i]; if ($child->isLineBreak()) { $this->line_break(); } elseif (!$child->out_of_flow()) { if (is_inline($child)) { // Inline boxes content will not be wrapped, so we may calculate its max width $this->add_width($child->get_max_width($context)); } else { // Non-inline boxes cause line break $this->line_break(); $this->add_width($child->get_min_width($context)); $this->line_break(); } } } // Check if last line have maximal width $this->line_break(); // Apply width constraint to min width. Return maximal value $wc = $box->getCSSProperty(CSS_WIDTH); # kornev if (!$wc) { $wc = CSSCompositeWidth::default_value(); } return max($this->_maxw, $wc->apply($this->_maxw, $box->parent->get_width())) + $box->_get_hor_extra(); }
/** * See also CSS 2.1, p 10.3.7 Absolutely positioned, non-replaced * elements */ function apply(&$box, &$context) { $containing_block =& $box->_get_containing_block(); $containing_block_width = $containing_block['right'] - $containing_block['left']; $right =& $box->getCSSProperty(CSS_RIGHT); $left =& $box->getCSSProperty(CSS_LEFT); $wc =& $box->getCSSProperty(CSS_WIDTH); // For the purposes of this section and the next, the term "static // position" (of an element) refers, roughly, to the position an // element would have had in the normal flow. More precisely: // // * The static position for 'left' is the distance from the left // edge of the containing block to the left margin edge of a // hypothetical box that would have been the first box of the // element if its 'position' property had been 'static' and // 'float' had been 'none'. The value is negative if the // hypothetical box is to the left of the containing block. // // * The static position for 'right' is the distance from the // right edge of the containing block to the right margin edge // of the same hypothetical box as above. The value is positive // if the hypothetical box is to the left of the containing // block's edge. // // For the purposes of calculating the static position, the // containing block of fixed positioned elements is the initial // containing block instead of the viewport, and all scrollable // boxes should be assumed to be scrolled to their origin. // // @todo: implement $static_left = 0; $static_right = 0; // Calculation of the shrink-to-fit width is similar to // calculating the width of a table cell using the automatic table // layout algorithm. Roughly: calculate the preferred width by // formatting the content without breaking lines other than where // explicit line breaks occur, and also calculate the preferred // minimum width, e.g., by trying all possible line breaks. CSS // 2.1 does not define the exact algorithm. Thirdly, calculate the // available width: this is found by solving for 'width' after // setting 'left' (in case 1) or 'right' (in case 3) to 0. // // Then the shrink-to-fit width is: min(max(preferred minimum // width, available width), preferred width). $preferred_minimum_width = $box->get_preferred_minimum_width($context); $available_width = $containing_block_width - ($left->isAuto() ? 0 : $left->getPoints($containing_block_width)) - ($right->isAuto() ? 0 : $right->getPoints($containing_block_width)) - $box->_get_hor_extra(); $preferred_width = $box->get_preferred_width($context); $shrink_to_fit_width = min(max($preferred_minimum_width, $available_width), $preferred_width); // The constraint that determines the used values for these // elements is: // // 'left' + 'margin-left' + 'border-left-width' + 'padding-left' + // 'width' + 'padding-right' + 'border-right-width' + // 'margin-right' + 'right' + scrollbar width (if any) = width of // containing block // If all three of 'left', 'width', and 'right' are 'auto': First // set any 'auto' values for 'margin-left' and 'margin-right' to // 0. Then, if the 'direction' property of the containing block is // 'ltr' set 'left' to the static position and apply rule number // three below; otherwise, set 'right' to the static position and // apply rule number one below. if ($left->isAuto() && $right->isAuto() && $wc->isNull()) { // @todo: support 'direction' property for the containing block $box->setCSSProperty(CSS_LEFT, ValueLeft::fromString('0')); } // If none of the three is 'auto': If both 'margin-left' and // 'margin-right' are 'auto', solve the equation under the extra // constraint that the two margins get equal values, unless this // would make them negative, in which case when direction of the // containing block is 'ltr' ('rtl'), set 'margin-left' // ('margin-right') to zero and solve for 'margin-right' // ('margin-left'). If one of 'margin-left' or 'margin-right' is // 'auto', solve the equation for that value. If the values are // over-constrained, ignore the value for 'left' (in case the // 'direction' property of the containing block is 'rtl') or // 'right' (in case 'direction' is 'ltr') and solve for that // value. if (!$left->isAuto() && !$right->isAuto() && !$wc->isNull()) { // @todo: implement $box->put_width($wc->apply($box->get_width(), $containing_block_width)); } // Otherwise, set 'auto' values for 'margin-left' and // 'margin-right' to 0, and pick the one of the following six // rules that applies. // Case 1 ('left' and 'width' are 'auto' and 'right' is not // 'auto', then the width is shrink-to-fit. Then solve for 'left') if ($left->isAuto() && !$right->isAuto() && $wc->isNull()) { $box->put_width($shrink_to_fit_width); } // Case 2 ('left' and 'right' are 'auto' and 'width' is not // 'auto', then if the 'direction' property of the containing // block is 'ltr' set 'left' to the static position, otherwise set // 'right' to the static position. Then solve for 'left' (if // 'direction is 'rtl') or 'right' (if 'direction' is 'ltr').) if ($left->isAuto() && $right->isAuto() && !$wc->isNull()) { // @todo: implement 'direction' support $box->put_width($wc->apply($box->get_width(), $containing_block_width)); } // Case 3 ('width' and 'right' are 'auto' and 'left' is not // 'auto', then the width is shrink-to-fit . Then solve for // 'right') if (!$wc) { $wc = CSSCompositeWidth::default_value(); } if (!$left->isAuto() && $right->isAuto() && $wc->isNull()) { $box->put_width($shrink_to_fit_width); } // Case 4 ('left' is 'auto', 'width' and 'right' are not 'auto', // then solve for 'left') if ($left->isAuto() && !$right->isAuto() && !$wc->isNull()) { $box->put_width($wc->apply($box->get_width(), $containing_block_width)); } // Case 5 ('width' is 'auto', 'left' and 'right' are not 'auto', // then solve for 'width') if (!$left->isAuto() && !$right->isAuto() && $wc->isNull()) { $box->put_width($containing_block_width - $left->getPoints($containing_block_width) - $right->getPoints($containing_block_width)); } // Case 6 ('right' is 'auto', 'left' and 'width' are not 'auto', // then solve for 'right') if (!$left->isAuto() && $right->isAuto() && !$wc->isNull()) { $box->put_width($wc->apply($box->get_width(), $containing_block_width)); } /** * After this we should remove width constraints or we may encounter problem * in future when we'll try to call get_..._width functions for this box * * @todo Update the family of get_..._width function so that they would apply constraint * using the containing block width, not "real" parent width */ $box->setCSSProperty(CSS_WIDTH, new WCConstant($box->get_width())); }
/** * Fit table columns to the given width */ function table_columns_fit(&$table, $width, &$context) { $minw = $table->get_table_columns_min_widths($context); $maxw = $table->get_table_columns_max_widths($context); $minw = $this->use_colspans($table, $minw, $context, 'get_min_width', $minw, $maxw); $maxw = $this->use_colspans($table, $maxw, $context, 'get_max_width', $minw, $maxw); // Store number of columns $columns = count($minw); // Apply column width constraints $minwc = array(); $maxwc = array(); $cellpadding = $table->getCSSProperty(CSS_HTML2PS_CELLPADDING); if (!$cellpadding) { $cellpadding = CSSCellPadding::default_value(); } $cellspacing = $table->getCSSProperty(CSS_HTML2PS_CELLSPACING); if (!$cellspacing) { $cellspacing = CSSCellSpacing::default_value(); } for ($i = 0; $i < count($minw); $i++) { $cwc = $table->get_cwc($i); if (!$cwc) { $cwc = new WCNone(); } // Do not allow constrained max width be less than min width // Do not allow constrained min width be less than min width // $table_width = $table->get_width(); $extra = 2 * $cellpadding->getPoints() + $cellspacing->getPoints(); $minwc[$i] = max($minw[$i], $cwc->apply($minw[$i] - $extra, $table_width) + $extra); $maxwc[$i] = max($minw[$i], $cwc->apply($maxw[$i] - $extra, $table_width) + $extra); } $minwc = $table->normalize_min_widths($width, $minw, $minwc); $minwc = $table->_table_apply_colspans($minwc, $context, 'get_min_width', $minwc, $maxwc); // We need to normalize widths for the case of colspans width is too big; for example: // <table><tr><td width="100"> // <table><tr><td width="150">TEXT<td>TEXT<tr><td colspan="2" width="200"> // in this case table SHOULD NOT be expanded over the 100px! // // $minwc = $table->normalize_min_widths($width, $minw, $minwc); $maxwc = $table->_table_apply_colspans($maxwc, $context, 'get_max_width', $minwc, $maxwc); // Calculate actual widths $widths = array(); // Calculate widths for all constrained columns for ($i = 0; $i < $columns; $i++) { if ($table->is_constrained_column($i)) { $widths[$i] = $minwc[$i]; } } // Quick fix for overconstrained tables: if table have width attribute AND its value is less than sum // of constrained columns widths plus minimal widths of uncostrained columns, then we'll expand the width of table // to fit all columns // 1. calculate sum of constrained column widths // 2. calculate sum of unconstrained column minimal widths $sum_cw = 0; $sum_ucw = 0; for ($i = 0; $i < $columns; $i++) { if ($table->is_constrained_column($i)) { $sum_cw += $widths[$i]; } else { $sum_ucw += $minwc[$i]; } } // 3. compare these widths with the table width and choose the maximal value $width = max($width, $sum_cw + $sum_ucw); // Second pass - disctribute the rest of the width // Explanation of the stuff below (I've really had problems with this small piece of code, especially // when I was trying to fix "bugs" inside it) // // First of all, no column can be narrower than it minimal width (determined by its content) // Note that constrained columns have their widths distributed above, so we can exclude them for now // (just throw them out and imagine that table does not contain any width-constrained cols) // // Second, the relative widths of columns will have _appoximately_ the same ratio as // their maximal content widths. (In exception of cases where the first rule will take place - // say for the table containing two columns with the VERY long text in the first and one or two words // in the second) // // In general, this approach can be inoptimal in case of _very_ different font sizes // inside the cells, of, say big images; nevertheless, it will give a good approximate // AND still fast enough (unlike fully correct methods involving evaluation of the content height of the cell) // // Thus, we do the following: // - calculate the ratio of current column MAXIMAL ($current_max) width to the sum of MAXIMAL widths of all columns left // (inluding current) second rule applied. Note that we need remember about column spans and select // maxw or maxwc in order. // - then check if the rest of width will be too small for other columns to fit and decrease current columns // width (see MIN function call) // - then check again if our width will be too small for current column to fit (and expand if nesessary) - // MAX function call for ($i = 0; $i < $columns; $i++) { if (!$table->is_constrained_column($i)) { // Get undistributed width (total table width - width of constrained columns) $rest = $width - array_sum($widths); // get max width of column being processed // If width is equal to zero, use max constrained width, as this column could be covered by colspan; // If not, we lose nothing, because all constrained columns are already processed earlier, and no more // columns except these two types can have different constrained and raw widths $current_max = max($maxw[$i], $maxwc[$i]); // Get sum of maximal constrained widths of unplaced columns $sum_max_cw = 0; $sum_min_cw = 0; for ($j = 0; $j < $columns; $j++) { if (!isset($widths[$j])) { $sum_max_cw += max($maxw[$j], $maxwc[$j]); $sum_min_cw += max($minw[$j], $minwc[$j]); } } // If some unplaced columns have maximal (constrained width) greater zero if ($sum_max_cw > 0) { $current_max = min($current_max * $rest / $sum_max_cw, $rest - $sum_min_cw + max($minwc[$i], $minw[$i])); } // Check for minimal width (either unconstrained or constrained) of current column $current_max = max($current_max, $minw[$i] == 0 ? $minwc[$i] : $minw[$i]); // Store calculated width $widths[$i] = $current_max; } } // Process the case of a lone empty table cell (used, for example, for its background color) // as we're using floating point numbers, we cannot use equals sign if (array_sum($widths) < EPSILON) { for ($i = 0; $i < count($widths); $i++) { $widths[$i] = 0.01; } } // now - the last attempt; if total width is less than box width, then we have a situation when either // all columns AND table are width constrained or the HTML similar to the following: // // <table cellpadding="0" width="100%" bgcolor="green"><tbody><tr> // <td colspan="2" bgcolor="yellow"></td> // <td style="width: 100px;" bgcolor="cyan">TEXT // // e.g. empty column (with zero width) and fixed-width column. // $wc = $table->getCSSProperty(CSS_WIDTH); if (!$wc) { $wc = CSSCompositeWidth::default_value(); } if (!$wc->isNull()) { if (array_sum($widths) < $width) { // Let's make zero-width columns // non-zero width (so that they columd be expanded) and re-try expanding columns // for ($i = 0; $i < count($widths); $i++) { if ($widths[$i] == 0) { $widths[$i] = EPSILON; } } // Now, if there's at least one non-costrained columns, try expanding them again $flags = $table->get_non_constrained_width_flags(); if (!any_flag_set($flags)) { $flags = $table->get_non_constant_constrained_width_flags(); if (!any_flag_set($flags)) { $flags = $table->get_non_image_constrained_width_flags(); if (!any_flag_set($flags)) { for ($i = 0; $i < count($flags); $i++) { $flags[$i] = true; } } } } $widths = expand_to_with_flags($width, $widths, $flags); } // in case of overconstrained table (e.g. two columns with 20% widths), expand them $widths = expand_to($width, $widths); } $table->put_full_width(array_sum($widths)); // Now we need to sort array by key keeping key-value associations in order for array_slice to work correctly ksort($widths, SORT_NUMERIC); return $widths; }