1<?php 2// $Header: /cvsroot/html2ps/box.inline.php,v 1.53 2007/01/24 18:55:44 Konstantin Exp $ 3 4require_once(HTML2PS_DIR.'encoding.inc.php'); 5 6define('SYMBOL_SHY', code_to_utf8(0xAD)); 7define('BROKEN_SYMBOL', chr(0xC2)); 8 9class LineBox { 10 var $top; 11 var $right; 12 var $bottom; 13 var $left; 14 15 function LineBox() { } 16 17 function ©() { 18 $box =& new LineBox; 19 $box->top = $this->top; 20 $box->right = $this->right; 21 $box->bottom = $this->bottom; 22 $box->left = $this->left; 23 return $box; 24 } 25 26 function offset($dx, $dy) { 27 $this->top += $dy; 28 $this->bottom += $dy; 29 $this->left += $dx; 30 $this->right += $dx; 31 } 32 33 function create(&$box) { 34 $lbox = new LineBox; 35 $lbox->top = $box->get_top(); 36 $lbox->right = $box->get_right(); 37 $lbox->bottom = $box->get_bottom(); 38 $lbox->left = $box->get_left(); 39 40 // $lbox->bottom = $box->get_top() - $box->get_baseline() - $box->get_descender(); 41 // $lbox->top = $box->get_top() - $box->get_baseline() + $box->get_ascender(); 42 return $lbox; 43 } 44 45 function extend(&$box) { 46 $base = $box->get_top() - $box->get_baseline(); 47 48 $this->top = max($this->top, $base + $box->get_ascender()); 49 $this->right = max($this->right, $box->get_right()); 50 $this->bottom = min($this->bottom, $base - $box->get_descender()); 51 52 // Left edge of the line box should never be modified 53 } 54 55 function fake_box(&$box) { 56 // Create the fake box object 57 58 $fake_state = new CSSState(CSS::get()); 59 $fake_state->pushState(); 60 61 $fake = null; 62 $fake_box = new BlockBox($fake); 63 $fake_box->readCSS($fake_state); 64 65 // Setup fake box size 66 $fake_box->put_left($this->left); 67 $fake_box->put_width($this->right - $this->left); 68 $fake_box->put_top($this->top - $box->baseline); 69 $fake_box->put_height($this->top - $this->bottom); 70 71 // Setup padding value 72 $fake_box->setCSSProperty(CSS_PADDING, $box->get_css_property(CSS_PADDING)); 73 74 // Setup fake box border and background 75 $fake_box->setCSSProperty(CSS_BACKGROUND, $box->get_css_property(CSS_BACKGROUND)); 76 $fake_box->setCSSProperty(CSS_BORDER, $box->get_css_property(CSS_BORDER)); 77 78 return $fake_box; 79 } 80} 81 82class InlineBox extends GenericInlineBox { 83 var $_lines; 84 85 function InlineBox() { 86 // Call parent's constructor 87 $this->GenericInlineBox(); 88 89 // Clear the list of line boxes inside this box 90 $this->_lines = array(); 91 } 92 93 function &create(&$root, &$pipeline) { 94 // Create contents of this inline box 95 if ($root->node_type() == XML_TEXT_NODE) { 96 $css_state =& $pipeline->get_current_css_state(); 97 $box = InlineBox::create_from_text($root->content, 98 $css_state->get_property(CSS_WHITE_SPACE), 99 $pipeline); 100 return $box; 101 } else { 102 $box =& new InlineBox(); 103 104 $css_state =& $pipeline->get_current_css_state(); 105 106 $box->readCSS($css_state); 107 108 // Initialize content 109 $child = $root->first_child(); 110 while ($child) { 111 $child_box =& create_pdf_box($child, $pipeline); 112 $box->add_child($child_box); 113 $child = $child->next_sibling(); 114 }; 115 116 // Add fake whitespace box with zero size for the anchor spans 117 // We need this, as "reflow" functions will automatically remove empty inline boxes from the 118 // document tree 119 // 120 if ($box->is_null()) { 121 $css_state->pushState(); 122 $css_state->set_property(CSS_FONT_SIZE, Value::fromData(0.01, UNIT_PT)); 123 124 $whitespace = WhitespaceBox::create($pipeline); 125 $whitespace->readCSS($css_state); 126 127 $box->add_child($whitespace); 128 129 $css_state->popState(); 130 }; 131 } 132 133 return $box; 134 } 135 136 function &create_from_text($text, $white_space, &$pipeline) { 137 $box =& new InlineBox(); 138 $box->readCSS($pipeline->get_current_css_state()); 139 140 // Apply/inherit text-related CSS properties 141 $css_state =& $pipeline->get_current_css_state(); 142 $css_state->pushDefaultTextState(); 143 144 require_once(HTML2PS_DIR.'inline.content.builder.factory.php'); 145 $inline_content_builder =& InlineContentBuilderFactory::get($white_space); 146 $inline_content_builder->build($box, $text, $pipeline); 147 148 // Clear the CSS stack 149 $css_state->popState(); 150 151 return $box; 152 } 153 154 function &get_line_box($index) { 155 $line_box =& $this->_lines[$index]; 156 return $line_box; 157 } 158 159 function get_line_box_count() { 160 return count($this->_lines); 161 } 162 163 // Inherited from GenericFormattedBox 164 165 function process_word($raw_content, &$pipeline) { 166 if ($raw_content === '') { 167 return false; 168 } 169 170 $ptr = 0; 171 $word = ''; 172 $hyphens = array(); 173 $encoding = 'iso-8859-1'; 174 175 $manager_encoding =& ManagerEncoding::get(); 176 $text_box =& TextBox::create_empty($pipeline); 177 178 $len = strlen($raw_content); 179 while ($ptr < $len) { 180 $char = $manager_encoding->get_next_utf8_char($raw_content, $ptr); 181 182 // Check if current char is a soft hyphen character. It it is, 183 // remove it from the word (as it should not be drawn normally) 184 // and store its location 185 if ($char == SYMBOL_SHY) { 186 $hyphens[] = strlen($word); 187 } else { 188 $mapping = $manager_encoding->get_mapping($char); 189 190 /** 191 * If this character is not found in predefined encoding vectors, 192 * we'll use "Custom" encoding and add single-character TextBox 193 * 194 * @TODO: handle characters without known glyph names 195 */ 196 if (is_null($mapping)) { 197 /** 198 * No mapping to default encoding vectors found for this character 199 */ 200 201 /** 202 * Add last word 203 */ 204 if ($word !== '') { 205 $text_box->add_subword($word, $encoding, $hyphens); 206 }; 207 208 /** 209 * Add current symbol 210 */ 211 $custom_char = $manager_encoding->add_custom_char(utf8_to_code($char)); 212 $text_box->add_subword($custom_char, $manager_encoding->get_current_custom_encoding_name(), $hyphens); 213 214 $word = ''; 215 } else { 216 if (isset($mapping[$encoding])) { 217 $word .= $mapping[$encoding]; 218 } else { 219 // This condition prevents empty text boxes from appearing; say, if word starts with a national 220 // character, an () - text box with no letters will be generated, in rare case causing a random line 221 // wraps, if container is narrow 222 if ($word !== '') { 223 $text_box->add_subword($word, $encoding, $hyphens); 224 }; 225 226 reset($mapping); 227 list($encoding, $add) = each($mapping); 228 229 $word = $mapping[$encoding]; 230 $hyphens = array(); 231 }; 232 }; 233 }; 234 }; 235 236 if ($word !== '') { 237 $text_box->add_subword($word, $encoding, $hyphens); 238 }; 239 240 $this->add_child($text_box); 241 return true; 242 } 243 244 function show(&$driver) { 245 if ($this->get_css_property(CSS_POSITION) == POSITION_RELATIVE) { 246 // Postpone 247 return true; 248 }; 249 250 return $this->_show($driver); 251 } 252 253 function show_postponed(&$driver) { 254 return $this->_show($driver); 255 } 256 257 function _show(&$driver) { 258 // Show line boxes background and borders 259 $size = $this->get_line_box_count(); 260 for ($i=0; $i<$size; $i++) { 261 $line_box = $this->get_line_box($i); 262 $fake_box = $line_box->fake_box($this); 263 264 $background = $this->get_css_property(CSS_BACKGROUND); 265 $border = $this->get_css_property(CSS_BORDER); 266 267 $background->show($driver, $fake_box); 268 $border->show($driver, $fake_box); 269 }; 270 271 // Show content 272 $size = count($this->content); 273 for ($i=0; $i < $size; $i++) { 274 if (is_null($this->content[$i]->show($driver))) { 275 return null; 276 }; 277 } 278 279 return true; 280 } 281 282 // Initialize next line box inside this inline 283 // 284 // Adds the next element to _lines array inside the current object and initializes it with the 285 // $box parameters 286 // 287 // @param $box child box which will be first in this line box 288 // @param $line_no number of line box 289 // 290 function init_line(&$box, &$line_no) { 291 $line_box = LineBox::create($box); 292 $this->_lines[$line_no] = $line_box; 293 } 294 295 // Extends the existing line box to include the given child 296 // OR starts new line box, if current child is to the left of the box right edge 297 // (which should not happen white the line box is filled) 298 // 299 // @param $box child box which will be first in this line box 300 // @param $line_no number of line box 301 // 302 function extend_line(&$box, $line_no) { 303 if (!isset($this->_lines[$line_no])) { 304 // New line box started 305 $this->init_line($box, $line_no); 306 307 return $line_no; 308 }; 309 310 // Check if this box starts a new line 311 if ($box->get_left() < $this->_lines[$line_no]->right) { 312 $line_no++; 313 $this->init_line($box, $line_no); 314 return $line_no; 315 }; 316 317 $this->_lines[$line_no]->extend($box); 318 319 return $line_no; 320 } 321 322 function merge_line(&$box, $line_no) { 323 $start_line = 0; 324 325 if ($line_no > 0 && count($box->_lines) > 0) { 326 if ($this->_lines[$line_no-1]->right + EPSILON > $box->_lines[0]->left) { 327 $this->_lines[$line_no-1]->right = max($box->_lines[0]->right, $this->_lines[$line_no-1]->right); 328 $this->_lines[$line_no-1]->top = max($box->_lines[0]->top, $this->_lines[$line_no-1]->top); 329 $this->_lines[$line_no-1]->bottom = min($box->_lines[0]->bottom, $this->_lines[$line_no-1]->bottom); 330 $start_line = 1; 331 }; 332 }; 333 334 $size = count($box->_lines); 335 for ($i=$start_line; $i<$size; $i++) { 336 $this->_lines[] = $box->_lines[$i]->copy(); 337 }; 338 339 return count($this->_lines); 340 } 341 342 function reflow_static(&$parent, &$context) { 343 GenericFormattedBox::reflow($parent, $context); 344 345 // Note that inline boxes (actually SPANS) 346 // are never added to the parent's line boxes 347 348 // Move current box to the parent's current coordinates 349 // Note that span box will start at the far left of the parent, NOT on its current X! 350 // Also, note that inline box can have margins, padding and borders! 351 352 $this->put_left($parent->get_left()); 353 $this->put_top($parent->get_top() - $this->get_extra_top()); 354 355 // first line of the SPAN will be offset to its parent current-x 356 // PLUS the left padding of current span! 357 $parent->_current_x += $this->get_extra_left(); 358 $this->_current_x = $parent->_current_x; 359 360 // Note that the same operation IS NOT applied to parent current-y! 361 // The padding space is just extended to the top possibly OVERLAPPING the above boxes. 362 363 $this->width = 0; 364 365 // Reflow contents 366 $size = count($this->content); 367 for ($i=0; $i<$size; $i++) { 368 $child =& $this->content[$i]; 369 370 // Add current element into _parent_ line box and reflow it 371 $child->reflow($parent, $context); 372 373 // In general, if inline box centained whitespace box only, 374 // it could be removed during reflow function call; 375 // let's check it and skip to next child 376 // 377 // if no children left AT ALL (so this box is empty), just exit 378 379 // Track the real height of the inline box; it will be used by other functions 380 // (say, functions calculating content height) 381 382 $this->extend_height($child->get_bottom_margin()); 383 }; 384 385 // Apply right extra space value (padding + border + margin) 386 $parent->_current_x += $this->get_extra_right(); 387 388 // Margins of inline boxes are not collapsed 389 390 if ($this->get_first_data()) { 391 $context->pop_collapsed_margin(); 392 $context->push_collapsed_margin( 0 ); 393 }; 394 } 395 396 function reflow_inline() { 397 $line_no = 0; 398 399 $size = count($this->content); 400 for ($i=0; $i<$size; $i++) { 401 $child =& $this->content[$i]; 402 $child->reflow_inline(); 403 404 if (!$child->is_null()) { 405 if (is_a($child,'InlineBox')) { 406 $line_no = $this->merge_line($child, $line_no); 407 } else { 408 $line_no = $this->extend_line($child, $line_no); 409 }; 410 }; 411 }; 412 } 413 414 function reflow_whitespace(&$linebox_started, &$previous_whitespace) { 415 /** 416 * Anchors could have no content at all (like <a name="test"></a>). 417 * We should not remove such anchors, as this will break internal links 418 * in the document. 419 */ 420 $dest = $this->get_css_property(CSS_HTML2PS_LINK_DESTINATION); 421 if (!is_null($dest)) { 422 return; 423 }; 424 425 $size = count($this->content); 426 for ($i=0; $i<$size; $i++) { 427 $child =& $this->content[$i]; 428 $child->reflow_whitespace($linebox_started, $previous_whitespace); 429 }; 430 431 if ($this->is_null()) { 432 $this->parent->remove($this); 433 }; 434 } 435 436 function get_extra_line_left() { 437 return $this->get_extra_left() + ($this->parent ? $this->parent->get_extra_line_left() : 0); 438 } 439 440 function get_extra_line_right() { 441 return $this->get_extra_right() + ($this->parent ? $this->parent->get_extra_line_right() : 0); 442 } 443 444 /** 445 * As "nowrap" properties applied to block-level boxes only, we may use simplified version of 446 * 'get_min_width' here 447 */ 448 function get_min_width(&$context) { 449 if (isset($this->_cache[CACHE_MIN_WIDTH])) { 450 return $this->_cache[CACHE_MIN_WIDTH]; 451 } 452 453 $content_size = count($this->content); 454 455 /** 456 * If box does not have any content, its minimal width is determined by extra horizontal space 457 */ 458 if ($content_size == 0) { 459 return $this->_get_hor_extra(); 460 }; 461 462 $minw = $this->content[0]->get_min_width($context); 463 464 for ($i=1; $i<$content_size; $i++) { 465 $item = $this->content[$i]; 466 if (!$item->out_of_flow()) { 467 $minw = max($minw, $item->get_min_width($context)); 468 }; 469 } 470 471 // Apply width constraint to min width. Return maximal value 472 $wc = $this->get_css_property(CSS_WIDTH); 473 $min_width = max($minw, $wc->apply($minw, $this->parent->get_width())) + $this->_get_hor_extra(); 474 475 $this->_cache[CACHE_MIN_WIDTH] = $min_width; 476 return $min_width; 477 } 478 479 // Restore default behaviour, as this class is a ContainerBox descendant 480 function get_max_width_natural(&$context, $limit=10E6) { 481 return $this->get_max_width($context, $limit); 482 } 483 484 function offset($dx, $dy) { 485 $size = count($this->_lines); 486 for ($i=0; $i<$size; $i++) { 487 $this->_lines[$i]->offset($dx, $dy); 488 }; 489 GenericInlineBox::offset($dx, $dy); 490 } 491 492 /** 493 * Deprecated 494 */ 495 function getLineBoxCount() { 496 return $this->get_line_box_count(); 497 } 498 499 function &getLineBox($index) { 500 return $this->get_line_box($index); 501 } 502}; 503 504?>