Spaces:
Sleeping
Sleeping
| // $Header: /cvsroot/html2ps/box.inline.php,v 1.53 2007/01/24 18:55:44 Konstantin Exp $ | |
| require_once(HTML2PS_DIR.'encoding.inc.php'); | |
| define('SYMBOL_SHY', code_to_utf8(0xAD)); | |
| define('BROKEN_SYMBOL', chr(0xC2)); | |
| class LineBox { | |
| var $top; | |
| var $right; | |
| var $bottom; | |
| var $left; | |
| function LineBox() { } | |
| function ©() { | |
| $box =& new LineBox; | |
| $box->top = $this->top; | |
| $box->right = $this->right; | |
| $box->bottom = $this->bottom; | |
| $box->left = $this->left; | |
| return $box; | |
| } | |
| function offset($dx, $dy) { | |
| $this->top += $dy; | |
| $this->bottom += $dy; | |
| $this->left += $dx; | |
| $this->right += $dx; | |
| } | |
| function create(&$box) { | |
| $lbox = new LineBox; | |
| $lbox->top = $box->get_top(); | |
| $lbox->right = $box->get_right(); | |
| $lbox->bottom = $box->get_bottom(); | |
| $lbox->left = $box->get_left(); | |
| // $lbox->bottom = $box->get_top() - $box->get_baseline() - $box->get_descender(); | |
| // $lbox->top = $box->get_top() - $box->get_baseline() + $box->get_ascender(); | |
| return $lbox; | |
| } | |
| function extend(&$box) { | |
| $base = $box->get_top() - $box->get_baseline(); | |
| $this->top = max($this->top, $base + $box->get_ascender()); | |
| $this->right = max($this->right, $box->get_right()); | |
| $this->bottom = min($this->bottom, $base - $box->get_descender()); | |
| // Left edge of the line box should never be modified | |
| } | |
| function fake_box(&$box) { | |
| // Create the fake box object | |
| $fake_state = new CSSState(CSS::get()); | |
| $fake_state->pushState(); | |
| $fake = null; | |
| $fake_box = new BlockBox($fake); | |
| $fake_box->readCSS($fake_state); | |
| // Setup fake box size | |
| $fake_box->put_left($this->left); | |
| $fake_box->put_width($this->right - $this->left); | |
| $fake_box->put_top($this->top - $box->baseline); | |
| $fake_box->put_height($this->top - $this->bottom); | |
| // Setup padding value | |
| $fake_box->setCSSProperty(CSS_PADDING, $box->getCSSProperty(CSS_PADDING)); | |
| // Setup fake box border and background | |
| $fake_box->setCSSProperty(CSS_BACKGROUND, $box->getCSSProperty(CSS_BACKGROUND)); | |
| $fake_box->setCSSProperty(CSS_BORDER, $box->getCSSProperty(CSS_BORDER)); | |
| return $fake_box; | |
| } | |
| } | |
| class InlineBox extends GenericInlineBox { | |
| var $_lines; | |
| function InlineBox() { | |
| // Call parent's constructor | |
| $this->GenericInlineBox(); | |
| // Clear the list of line boxes inside this box | |
| $this->_lines = array(); | |
| } | |
| function &create(&$root, &$pipeline) { | |
| // Create contents of this inline box | |
| if ($root->node_type() == XML_TEXT_NODE) { | |
| $css_state =& $pipeline->getCurrentCSSState(); | |
| return InlineBox::create_from_text($root->content, | |
| $css_state->getProperty(CSS_WHITE_SPACE), | |
| $pipeline); | |
| } else { | |
| $box =& new InlineBox(); | |
| $css_state =& $pipeline->getCurrentCSSState(); | |
| $box->readCSS($css_state); | |
| // Initialize content | |
| $child = $root->first_child(); | |
| while ($child) { | |
| $child_box =& create_pdf_box($child, $pipeline); | |
| $box->add_child($child_box); | |
| $child = $child->next_sibling(); | |
| }; | |
| // Add fake whitespace box with zero size for the anchor spans | |
| // We need this, as "reflow" functions will automatically remove empty inline boxes from the | |
| // document tree | |
| // | |
| if ($box->is_null()) { | |
| $css_state->pushState(); | |
| $css_state->setProperty(CSS_FONT_SIZE, Value::fromData(0.01, UNIT_PT)); | |
| $whitespace = WhitespaceBox::create($pipeline); | |
| $whitespace->readCSS($css_state); | |
| $box->add_child($whitespace); | |
| $css_state->popState(); | |
| }; | |
| } | |
| return $box; | |
| } | |
| function &create_from_text($text, $white_space, &$pipeline) { | |
| $box =& new InlineBox(); | |
| $box->readCSS($pipeline->getCurrentCSSState()); | |
| // Apply/inherit text-related CSS properties | |
| $css_state =& $pipeline->getCurrentCSSState(); | |
| $css_state->pushDefaultTextState(); | |
| require_once(HTML2PS_DIR.'inline.content.builder.factory.php'); | |
| $inline_content_builder =& InlineContentBuilderFactory::get($white_space); | |
| $inline_content_builder->build($box, $text, $pipeline); | |
| // Clear the CSS stack | |
| $css_state->popState(); | |
| return $box; | |
| } | |
| function get_line_box_count() { | |
| return count($this->_lines); | |
| } | |
| // Inherited from GenericFormattedBox | |
| function process_word($raw_content, &$pipeline) { | |
| if ($raw_content === '') { | |
| return false; | |
| } | |
| $ptr = 0; | |
| $word = ''; | |
| $hyphens = array(); | |
| $encoding = 'iso-8859-1'; | |
| $manager_encoding =& ManagerEncoding::get(); | |
| $text_box =& TextBox::create_empty($pipeline); | |
| $len = strlen($raw_content); | |
| while ($ptr < $len) { | |
| $char = $manager_encoding->getNextUTF8Char($raw_content, $ptr); | |
| // Check if current char is a soft hyphen character. It it is, | |
| // remove it from the word (as it should not be drawn normally) | |
| // and store its location | |
| if ($char == SYMBOL_SHY) { | |
| $hyphens[] = strlen($word); | |
| } else { | |
| $mapping = $manager_encoding->getMapping($char); | |
| /** | |
| * If this character is not found in predefined encoding vectors, | |
| * we'll use "Custom" encoding and add single-character TextBox | |
| * | |
| * @TODO: handle characters without known glyph names | |
| */ | |
| if (is_null($mapping)) { | |
| /** | |
| * No mapping to default encoding vectors found for this character | |
| */ | |
| /** | |
| * Add last word | |
| */ | |
| if ($word !== '') { | |
| $text_box->add_subword($word, $encoding, $hyphens); | |
| }; | |
| /** | |
| * Add current symbol | |
| */ | |
| $custom_char = $manager_encoding->addCustomChar(utf8_to_code($char)); | |
| $text_box->add_subword($custom_char, $manager_encoding->getCustomEncodingName(), $hyphens); | |
| $word = ''; | |
| } else { | |
| if (isset($mapping[$encoding])) { | |
| $word .= $mapping[$encoding]; | |
| } else { | |
| // This condition prevents empty text boxes from appearing; say, if word starts with a national | |
| // character, an () - text box with no letters will be generated, in rare case causing a random line | |
| // wraps, if container is narrow | |
| if ($word !== '') { | |
| $text_box->add_subword($word, $encoding, $hyphens); | |
| }; | |
| reset($mapping); | |
| list($encoding, $add) = each($mapping); | |
| $word = $mapping[$encoding]; | |
| $hyphens = array(); | |
| }; | |
| }; | |
| }; | |
| }; | |
| if ($word !== '') { | |
| $text_box->add_subword($word, $encoding, $hyphens); | |
| }; | |
| $this->add_child($text_box); | |
| return true; | |
| } | |
| function show(&$driver) { | |
| if ($this->getCSSProperty(CSS_POSITION) == POSITION_RELATIVE) { | |
| // Postpone | |
| return true; | |
| }; | |
| return $this->_show($driver); | |
| } | |
| function show_postponed(&$driver) { | |
| return $this->_show($driver); | |
| } | |
| function _show(&$driver) { | |
| // Show line boxes background and borders | |
| $size = $this->getLineBoxCount(); | |
| for ($i=0; $i<$size; $i++) { | |
| $line_box = $this->getLineBox($i); | |
| $fake_box = $line_box->fake_box($this); | |
| $background = $this->getCSSProperty(CSS_BACKGROUND); | |
| $border = $this->getCSSProperty(CSS_BORDER); | |
| $background->show($driver, $fake_box); | |
| $border->show($driver, $fake_box); | |
| }; | |
| // Show content | |
| $size = count($this->content); | |
| for ($i=0; $i < $size; $i++) { | |
| if (is_null($this->content[$i]->show($driver))) { | |
| return null; | |
| }; | |
| } | |
| return true; | |
| } | |
| // Initialize next line box inside this inline | |
| // | |
| // Adds the next element to _lines array inside the current object and initializes it with the | |
| // $box parameters | |
| // | |
| // @param $box child box which will be first in this line box | |
| // @param $line_no number of line box | |
| // | |
| function init_line(&$box, &$line_no) { | |
| $line_box = LineBox::create($box); | |
| $this->_lines[$line_no] = $line_box; | |
| } | |
| // Extends the existing line box to include the given child | |
| // OR starts new line box, if current child is to the left of the box right edge | |
| // (which should not happen white the line box is filled) | |
| // | |
| // @param $box child box which will be first in this line box | |
| // @param $line_no number of line box | |
| // | |
| function extend_line(&$box, $line_no) { | |
| if (!isset($this->_lines[$line_no])) { | |
| // New line box started | |
| $this->init_line($box, $line_no); | |
| return $line_no; | |
| }; | |
| // Check if this box starts a new line | |
| if ($box->get_left() < $this->_lines[$line_no]->right) { | |
| $line_no++; | |
| $this->init_line($box, $line_no); | |
| return $line_no; | |
| }; | |
| $this->_lines[$line_no]->extend($box); | |
| return $line_no; | |
| } | |
| function merge_line(&$box, $line_no) { | |
| $start_line = 0; | |
| if ($line_no > 0 && count($box->_lines) > 0) { | |
| if ($this->_lines[$line_no-1]->right + EPSILON > $box->_lines[0]->left) { | |
| $this->_lines[$line_no-1]->right = max($box->_lines[0]->right, $this->_lines[$line_no-1]->right); | |
| $this->_lines[$line_no-1]->top = max($box->_lines[0]->top, $this->_lines[$line_no-1]->top); | |
| $this->_lines[$line_no-1]->bottom = min($box->_lines[0]->bottom, $this->_lines[$line_no-1]->bottom); | |
| $start_line = 1; | |
| }; | |
| }; | |
| $size = count($box->_lines); | |
| for ($i=$start_line; $i<$size; $i++) { | |
| $this->_lines[] = $box->_lines[$i]->copy(); | |
| }; | |
| return count($this->_lines); | |
| } | |
| function reflow_static(&$parent, &$context) { | |
| GenericFormattedBox::reflow($parent, $context); | |
| // Note that inline boxes (actually SPANS) | |
| // are never added to the parent's line boxes | |
| // Move current box to the parent's current coordinates | |
| // Note that span box will start at the far left of the parent, NOT on its current X! | |
| // Also, note that inline box can have margins, padding and borders! | |
| $this->put_left($parent->get_left()); | |
| $this->put_top($parent->get_top() - $this->get_extra_top()); | |
| // first line of the SPAN will be offset to its parent current-x | |
| // PLUS the left padding of current span! | |
| $parent->_current_x += $this->get_extra_left(); | |
| $this->_current_x = $parent->_current_x; | |
| // Note that the same operation IS NOT applied to parent current-y! | |
| // The padding space is just extended to the top possibly OVERLAPPING the above boxes. | |
| $this->width = 0; | |
| // Reflow contents | |
| $size = count($this->content); | |
| for ($i=0; $i<$size; $i++) { | |
| $child =& $this->content[$i]; | |
| // Add current element into _parent_ line box and reflow it | |
| $child->reflow($parent, $context); | |
| // In general, if inline box centained whitespace box only, | |
| // it could be removed during reflow function call; | |
| // let's check it and skip to next child | |
| // | |
| // if no children left AT ALL (so this box is empty), just exit | |
| // Track the real height of the inline box; it will be used by other functions | |
| // (say, functions calculating content height) | |
| $this->extend_height($child->get_bottom_margin()); | |
| }; | |
| // Apply right extra space value (padding + border + margin) | |
| $parent->_current_x += $this->get_extra_right(); | |
| // Margins of inline boxes are not collapsed | |
| if ($this->get_first_data()) { | |
| $context->pop_collapsed_margin(); | |
| $context->push_collapsed_margin( 0 ); | |
| }; | |
| } | |
| function reflow_inline() { | |
| $line_no = 0; | |
| $size = count($this->content); | |
| for ($i=0; $i<$size; $i++) { | |
| $child =& $this->content[$i]; | |
| $child->reflow_inline(); | |
| if (!$child->is_null()) { | |
| if (is_a($child,'InlineBox')) { | |
| $line_no = $this->merge_line($child, $line_no); | |
| } else { | |
| $line_no = $this->extend_line($child, $line_no); | |
| }; | |
| }; | |
| }; | |
| } | |
| function reflow_whitespace(&$linebox_started, &$previous_whitespace) { | |
| /** | |
| * Anchors could have no content at all (like <a name="test"></a>). | |
| * We should not remove such anchors, as this will break internal links | |
| * in the document. | |
| */ | |
| $dest = $this->getCSSProperty(CSS_HTML2PS_LINK_DESTINATION); | |
| if ($dest != '') { return; }; | |
| $size = count($this->content); | |
| for ($i=0; $i<$size; $i++) { | |
| $child =& $this->content[$i]; | |
| $child->reflow_whitespace($linebox_started, $previous_whitespace); | |
| }; | |
| if ($this->is_null()) { | |
| $this->parent->remove($this); | |
| }; | |
| } | |
| function get_extra_line_left() { | |
| return $this->get_extra_left() + ($this->parent ? $this->parent->get_extra_line_left() : 0); | |
| } | |
| function get_extra_line_right() { | |
| return $this->get_extra_right() + ($this->parent ? $this->parent->get_extra_line_right() : 0); | |
| } | |
| /** | |
| * As "nowrap" properties applied to block-level boxes only, we may use simplified version of | |
| * 'get_min_width' here | |
| */ | |
| function get_min_width(&$context) { | |
| if (isset($this->_cache[CACHE_MIN_WIDTH])) { | |
| return $this->_cache[CACHE_MIN_WIDTH]; | |
| } | |
| $content_size = count($this->content); | |
| /** | |
| * If box does not have any content, its minimal width is determined by extra horizontal space | |
| */ | |
| if ($content_size == 0) { | |
| return $this->_get_hor_extra(); | |
| }; | |
| $minw = $this->content[0]->get_min_width($context); | |
| for ($i=1; $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); | |
| $min_width = max($minw, $wc->apply($minw, $this->parent->get_width())) + $this->_get_hor_extra(); | |
| $this->_cache[CACHE_MIN_WIDTH] = $min_width; | |
| return $min_width; | |
| } | |
| // Restore default behaviour, as this class is a ContainerBox descendant | |
| function get_max_width_natural(&$context, $limit=10E6) { | |
| return $this->get_max_width($context, $limit); | |
| } | |
| function offset($dx, $dy) { | |
| $size = count($this->_lines); | |
| for ($i=0; $i<$size; $i++) { | |
| $this->_lines[$i]->offset($dx, $dy); | |
| }; | |
| GenericInlineBox::offset($dx, $dy); | |
| } | |
| /** | |
| * Deprecated | |
| */ | |
| function getLineBoxCount() { | |
| return $this->get_line_box_count(); | |
| } | |
| function &getLineBox($index) { | |
| $line_box =& $this->_lines[$index]; | |
| return $line_box; | |
| } | |
| }; | |