SimpleInlineBox();
$this->words = array();
$this->encodings = array();
$this->hyphens = array();
$this->_word_widths = array();
$this->_wrappable = array();
$this->wrapped = null;
$this->_widths = array();
$this->font_size = 0;
$this->ascender = 0;
$this->descender = 0;
$this->width = 0;
$this->height = 0;
}
/**
* Check if given subword contains soft hyphens and calculate
*/
function _make_wrappable(&$driver, $base_width, $font_name, $font_size, $subword_index) {
$hyphens = $this->hyphens[$subword_index];
$wrappable = array();
foreach ($hyphens as $hyphen) {
$subword_wrappable_index = $hyphen;
$subword_wrappable_width = $base_width + $driver->stringwidth(substr($this->words[$subword_index], 0, $subword_wrappable_index),
$font_name,
$this->encodings[$subword_index],
$font_size);
$subword_full_width = $subword_wrappable_width + $driver->stringwidth('-',
$font_name,
"iso-8859-1",
$font_size);
$wrappable[] = array($subword_index, $subword_wrappable_index, $subword_wrappable_width, $subword_full_width);
};
return $wrappable;
}
function get_content() {
return join('', array_map(array($this, 'get_content_callback'), $this->words, $this->encodings));
}
function get_content_callback($word, $encoding) {
$manager_encoding =& ManagerEncoding::get();
return $manager_encoding->to_utf8($word, $encoding);
}
function get_height() {
return $this->height;
}
function put_height($value) {
$this->height = $value;
}
// Apply 'line-height' CSS property; modifies the default_baseline value
// (NOT baseline, as it is calculated - and is overwritten - in the close_line
// method of container box
//
// Note that underline position (or 'descender' in terms of PDFLIB) -
// so, simple that space of text box under the baseline - is scaled too
// when 'line-height' is applied
//
function _apply_line_height() {
$height = $this->get_height();
$under = $height - $this->default_baseline;
$line_height = $this->get_css_property(CSS_LINE_HEIGHT);
if ($height > 0) {
$scale = $line_height->apply($this->ascender + $this->descender) / ($this->ascender + $this->descender);
} else {
$scale = 0;
};
// Calculate the height delta of the text box
$delta = $height * ($scale-1);
$this->put_height(($this->ascender + $this->descender)*$scale);
$this->default_baseline = $this->default_baseline + $delta/2;
}
function _get_font_name(&$viewport, $subword_index) {
if (isset($this->_cache[CACHE_TYPEFACE][$subword_index])) {
return $this->_cache[CACHE_TYPEFACE][$subword_index];
};
$font_resolver =& $viewport->get_font_resolver();
$font = $this->get_css_property(CSS_FONT);
$typeface = $font_resolver->get_typeface_name($font->family,
$font->weight,
$font->style,
$this->encodings[$subword_index]);
$this->_cache[CACHE_TYPEFACE][$subword_index] = $typeface;
return $typeface;
}
function add_subword($raw_subword, $encoding, $hyphens) {
$text_transform = $this->get_css_property(CSS_TEXT_TRANSFORM);
switch ($text_transform) {
case CSS_TEXT_TRANSFORM_CAPITALIZE:
$subword = ucwords($raw_subword);
break;
case CSS_TEXT_TRANSFORM_UPPERCASE:
$subword = strtoupper($raw_subword);
break;
case CSS_TEXT_TRANSFORM_LOWERCASE:
$subword = strtolower($raw_subword);
break;
case CSS_TEXT_TRANSFORM_NONE:
$subword = $raw_subword;
break;
}
$this->words[] = $subword;
$this->encodings[] = $encoding;
$this->hyphens[] = $hyphens;
}
function &create($text, $encoding, &$pipeline) {
$box =& TextBox::create_empty($pipeline);
$box->add_subword($text, $encoding, array());
return $box;
}
function &create_empty(&$pipeline) {
$box =& new TextBox();
$css_state = $pipeline->get_current_css_state();
$box->readCSS($css_state);
$css_state = $pipeline->get_current_css_state();
return $box;
}
function readCSS(&$state) {
parent::readCSS($state);
$this->_readCSSLengths($state,
array(CSS_TEXT_INDENT,
CSS_LETTER_SPACING));
}
// Inherited from GenericFormattedBox
function get_descender() {
return $this->descender;
}
function get_ascender() {
return $this->ascender;
}
function get_baseline() {
return $this->baseline;
}
function get_min_width_natural(&$context) {
return $this->get_full_width();
}
function get_min_width(&$context) {
return $this->get_full_width();
}
function get_max_width(&$context) {
return $this->get_full_width();
}
// Checks if current inline box should cause a line break inside the parent box
//
// @param $parent reference to a parent box
// @param $content flow context
// @return true if line break occurred; false otherwise
//
function maybe_line_break(&$parent, &$context) {
if (!$parent->line_break_allowed()) {
return false;
};
$last =& $parent->last_in_line();
if ($last) {
// Check if last box was a note call box. Punctuation marks
// after a note-call box should not be wrapped to new line,
// while "plain" words may be wrapped.
if ($last->is_note_call() && $this->is_punctuation()) {
return false;
};
};
// Calculate the x-coordinate of this box right edge
$right_x = $this->get_full_width() + $parent->_current_x;
$need_break = false;
// Check for right-floating boxes
// If upper-right corner of this inline box is inside of some float, wrap the line
$float = $context->point_in_floats($right_x, $parent->_current_y);
if ($float) {
$need_break = true;
};
// No floats; check if we had run out the right edge of container
// TODO: nobr-before, nobr-after
if (($right_x > $parent->get_right()+EPSILON)) {
// Now check if parent line box contains any other boxes;
// if not, we should draw this box unless we have a floating box to the left
$first = $parent->get_first();
$ti = $this->get_css_property(CSS_TEXT_INDENT);
$indent_offset = $ti->calculate($parent);
if ($parent->_current_x > $parent->get_left() + $indent_offset + EPSILON) {
$need_break = true;
};
}
// As close-line will not change the current-Y parent coordinate if no
// items were in the line box, we need to offset this explicitly in this case
//
if ($parent->line_box_empty() && $need_break) {
$parent->_current_y -= $this->get_height();
};
if ($need_break) {
// Check if current box contains soft hyphens and use them, breaking word into parts
$size = count($this->_wrappable);
if ($size > 0) {
$width_delta = $right_x - $parent->get_right();
if (!is_null($float)) {
$width_delta = $right_x - $float->get_left_margin();
};
$this->_find_soft_hyphen($parent, $width_delta);
};
$parent->close_line($context);
// Check if parent inline boxes have left padding/margins and add them to current_x
$element = $this->parent;
while (!is_null($element) && is_a($element,"GenericInlineBox")) {
$parent->_current_x += $element->get_extra_left();
$element = $element->parent;
};
};
return $need_break;
}
function _find_soft_hyphen(&$parent, $width_delta) {
/**
* Now we search for soft hyphen closest to the right margin
*/
$size = count($this->_wrappable);
for ($i=$size-1; $i>=0; $i--) {
$wrappable = $this->_wrappable[$i];
if ($this->get_width() - $wrappable[3] > $width_delta) {
$this->save_wrapped($wrappable, $parent, $context);
$parent->append_line($this);
return;
};
};
}
function save_wrapped($wrappable, &$parent, &$context) {
$this->wrapped = array($wrappable,
$parent->_current_x + $this->get_extra_left(),
$parent->_current_y - $this->get_extra_top());
}
function reflow(&$parent, &$context) {
// Check if we need a line break here (possilble several times in a row, if we
// have a long word and a floating box intersecting with this word
//
// To prevent infinite loop, we'll use a limit of 100 sequental line feeds
$i=0;
do { $i++; } while ($this->maybe_line_break($parent, $context) && $i < 100);
// Determine the baseline position and height of the text-box using line-height CSS property
$this->_apply_line_height();
// set default baseline
$this->baseline = $this->default_baseline;
// append current box to parent line box
$parent->append_line($this);
// Determine coordinates of upper-left _margin_ corner
$this->guess_corner($parent);
// Offset parent current X coordinate
if (!is_null($this->wrapped)) {
$parent->_current_x += $this->get_full_width() - $this->wrapped[0][2];
} else {
$parent->_current_x += $this->get_full_width();
};
// Extends parents height
$parent->extend_height($this->get_bottom());
// Update the value of current collapsed margin; pure text (non-span)
// boxes always have zero margin
$context->pop_collapsed_margin();
$context->push_collapsed_margin( 0 );
}
function getWrappedWidthAndHyphen() {
return $this->wrapped[0][3];
}
function getWrappedWidth() {
return $this->wrapped[0][2];
}
function reflow_text(&$driver) {
$num_words = count($this->words);
/**
* Empty text box
*/
if ($num_words == 0) {
return true;
};
/**
* A simple assumption is made: fonts used for different encodings
* have equal ascender/descender values (while they have the same
* typeface, style and weight).
*/
$font_name = $this->_get_font_name($driver, 0);
/**
* Get font vertical metrics
*/
$ascender = $driver->font_ascender($font_name, $this->encodings[0]);
if (is_null($ascender)) {
error_log("TextBox::reflow_text: cannot get font ascender");
return null;
};
$descender = $driver->font_descender($font_name, $this->encodings[0]);
if (is_null($descender)) {
error_log("TextBox::reflow_text: cannot get font descender");
return null;
};
/**
* Setup box size
*/
$font = $this->get_css_property(CSS_FONT_SIZE);
$font_size = $font->getPoints();
// Both ascender and descender should make $font_size
// as it is not guaranteed that $ascender + $descender == 1,
// we should normalize the result
$koeff = $font_size / ($ascender + $descender);
$this->ascender = $ascender * $koeff;
$this->descender = $descender * $koeff;
$this->default_baseline = $this->ascender;
$this->height = $this->ascender + $this->descender;
/**
* Determine box width
*/
if ($font_size > 0) {
$width = 0;
for ($i=0; $i<$num_words; $i++) {
$font_name = $this->_get_font_name($driver, $i);
$current_width = $driver->stringwidth($this->words[$i],
$font_name,
$this->encodings[$i],
$font_size);
$this->_word_widths[] = $current_width;
// Add information about soft hyphens
$this->_wrappable = array_merge($this->_wrappable, $this->_make_wrappable($driver, $width, $font_name, $font_size, $i));
$width += $current_width;
};
$this->width = $width;
} else {
$this->width = 0;
};
$letter_spacing = $this->get_css_property(CSS_LETTER_SPACING);
if ($letter_spacing->getPoints() != 0) {
$this->_widths = array();
for ($i=0; $i<$num_words; $i++) {
$num_chars = strlen($this->words[$i]);
for ($j=0; $j<$num_chars; $j++) {
$this->_widths[] = $driver->stringwidth($this->words[$i]{$j},
$font_name,
$this->encodings[$i],
$font_size);
};
$this->width += $letter_spacing->getPoints()*$num_chars;
};
};
return true;
}
function show(&$driver) {
/**
* Check if font-size have been set to 0; in this case we should not draw this box at all
*/
$font_size = $this->get_css_property(CSS_FONT_SIZE);
if ($font_size->getPoints() == 0) {
return true;
}
// Check if current text box will be cut-off by the page edge
// Get Y coordinate of the top edge of the box
$top = $this->get_top_margin();
// Get Y coordinate of the bottom edge of the box
$bottom = $this->get_bottom_margin();
$top_inside = $top >= $driver->getPageBottom()-EPSILON;
$bottom_inside = $bottom >= $driver->getPageBottom()-EPSILON;
if (!$top_inside && !$bottom_inside) {
return true;
}
return $this->_showText($driver);
}
function _showText(&$driver) {
if (!is_null($this->wrapped)) {
return $this->_showTextWrapped($driver);
} else {
return $this->_showTextNormal($driver);
};
}
function _showTextWrapped(&$driver) {
// draw generic box
parent::show($driver);
$font_size = $this->get_css_property(CSS_FONT_SIZE);
$decoration = $this->get_css_property(CSS_TEXT_DECORATION);
// draw text decoration
$driver->decoration($decoration['U'],
$decoration['O'],
$decoration['T']);
$letter_spacing = $this->get_css_property(CSS_LETTER_SPACING);
// Output text with the selected font
// note that we're using $default_baseline;
// the alignment offset - the difference between baseline and default_baseline values
// is taken into account inside the get_top/get_bottom functions
//
$current_char = 0;
$left = $this->wrapped[1];
$top = $this->get_top() - $this->default_baseline;
$num_words = count($this->words);
/**
* First part of wrapped word (before hyphen)
*/
for ($i=0; $i<$this->wrapped[0][0]; $i++) {
// Activate font
$status = $driver->setfont($this->_get_font_name($driver, $i),
$this->encodings[$i],
$font_size->getPoints());
if (is_null($status)) {
error_log("TextBox::show: setfont call failed");
return null;
};
$driver->show_xy($this->words[$i],
$left,
$this->wrapped[2] - $this->default_baseline);
$left += $this->_word_widths[$i];
};
$index = $this->wrapped[0][0];
$status = $driver->setfont($this->_get_font_name($driver, $index),
$this->encodings[$index],
$font_size->getPoints());
if (is_null($status)) {
error_log("TextBox::show: setfont call failed");
return null;
};
$driver->show_xy(substr($this->words[$index],0,$this->wrapped[0][1])."-",
$left,
$this->wrapped[2] - $this->default_baseline);
/**
* Second part of wrapped word (after hyphen)
*/
$left = $this->get_left();
$top = $this->get_top();
$driver->show_xy(substr($this->words[$index],$this->wrapped[0][1]),
$left,
$top - $this->default_baseline);
$size = count($this->words);
for ($i = $this->wrapped[0][0]+1; $i<$size; $i++) {
// Activate font
$status = $driver->setfont($this->_get_font_name($driver, $i),
$this->encodings[$i],
$font_size->getPoints());
if (is_null($status)) {
error_log("TextBox::show: setfont call failed");
return null;
};
$driver->show_xy($this->words[$i],
$left,
$top - $this->default_baseline);
$left += $this->_word_widths[$i];
};
return true;
}
function _showTextNormal(&$driver) {
// draw generic box
parent::show($driver);
$font_size = $this->get_css_property(CSS_FONT_SIZE);
$decoration = $this->get_css_property(CSS_TEXT_DECORATION);
// draw text decoration
$driver->decoration($decoration['U'],
$decoration['O'],
$decoration['T']);
$letter_spacing = $this->get_css_property(CSS_LETTER_SPACING);
if ($letter_spacing->getPoints() == 0) {
// Output text with the selected font
// note that we're using $default_baseline;
// the alignment offset - the difference between baseline and default_baseline values
// is taken into account inside the get_top/get_bottom functions
//
$size = count($this->words);
$left = $this->get_left();
for ($i=0; $i<$size; $i++) {
// Activate font
$status = $driver->setfont($this->_get_font_name($driver, $i),
$this->encodings[$i],
$font_size->getPoints());
if (is_null($status)) {
error_log("TextBox::show: setfont call failed");
return null;
};
$driver->show_xy($this->words[$i],
$left,
$this->get_top() - $this->default_baseline);
$left += $this->_word_widths[$i];
};
} else {
$current_char = 0;
$left = $this->get_left();
$top = $this->get_top() - $this->default_baseline;
$num_words = count($this->words);
for ($i=0; $i<$num_words; $i++) {
$num_chars = strlen($this->words[$i]);
for ($j=0; $j<$num_chars; $j++) {
$status = $driver->setfont($this->_get_font_name($driver, $i),
$this->encodings[$i],
$font_size->getPoints());
$driver->show_xy($this->words[$i]{$j}, $left, $top);
$left += $this->_widths[$current_char] + $letter_spacing->getPoints();
$current_char++;
};
};
};
return true;
}
function show_fixed(&$driver) {
$font_size = $this->get_css_property(CSS_FONT_SIZE);
// Check if font-size have been set to 0; in this case we should not draw this box at all
if ($font_size->getPoints() == 0) {
return true;
}
return $this->_showText($driver);
}
function offset($dx, $dy) {
parent::offset($dx, $dy);
// Note that horizonal offset should be called explicitly from text-align routines
// otherwise wrapped part will be offset twice (as offset is called both for
// wrapped and non-wrapped parts).
if (!is_null($this->wrapped)) {
$this->offset_wrapped($dx, $dy);
};
}
function offset_wrapped($dx, $dy) {
$this->wrapped[1] += $dx;
$this->wrapped[2] += $dy;
}
function reflow_whitespace(&$linebox_started, &$previous_whitespace) {
$linebox_started = true;
$previous_whitespace = false;
return;
}
function is_null() { return false; }
}
?>