1<?php 2// $Header: /cvsroot/html2ps/box.php,v 1.46 2007/05/06 18:49:29 Konstantin Exp $ 3 4// This variable is used to track the reccurrent framesets 5// they can be produced by inaccurate or malicious HTML-coder 6// or by some cookie- or referrer- based identification system 7// 8$GLOBALS['g_frame_level'] = 0; 9 10// Called when frame node is to be processed 11function inc_frame_level() { 12 global $g_frame_level; 13 $g_frame_level ++; 14 15 if ($g_frame_level > MAX_FRAME_NESTING_LEVEL) { 16 trigger_error('Frame nesting too deep', 17 E_USER_ERROR); 18 }; 19} 20 21// Called when frame (and all nested frames, of course) processing have been completed 22// 23function dec_frame_level() { 24 global $g_frame_level; 25 $g_frame_level --; 26} 27 28// Calculate 'display' CSS property according to CSS 2.1 paragraph 9.7 29// "Relationships between 'display', 'position', and 'float'" 30// (The last table in that paragraph) 31// 32// @return flag indication of current box need a block box wrapper 33// 34function _fix_display_position_float(&$css_state) { 35 // Specified value -> Computed value 36 // inline-table -> table 37 // inline, run-in, table-row-group, table-column, table-column-group, table-header-group, 38 // table-footer-group, table-row, table-cell, table-caption, inline-block -> block 39 // others-> same as specified 40 41 $display = $css_state->get_property(CSS_DISPLAY); 42 43 switch ($display) { 44 case "inline-table": 45 $css_state->set_property(CSS_DISPLAY, 'table'); 46 return false; 47 case "inline": 48 case "run-in": 49 case "table-row-group": 50 case "table-column": 51 case "table-column-group": 52 case "table-header-group": 53 case "table-footer-group": 54 case "table-row": 55 case "table-cell": 56 case "table-caption": 57 case "inline-block": 58 // Note that as we're using some non-standard display values, we need to add them to translation table 59 $css_state->set_property(CSS_DISPLAY, 'block'); 60 return false; 61 62 // There are display types that cannot be directly converted to block; in this case we need to create a "wrapper" floating 63 // or positioned block box and put our real box into it. 64 case "-button": 65 case "-button-submit": 66 case "-button-reset": 67 case "-button-image": 68 case "-checkbox": 69 case "-iframe": 70 case "-image": 71 case "-legend": 72 case "-password": 73 case "-radio": 74 case "-select": 75 case "-text": 76 case "-textarea": 77 // No change 78 return true; 79 80 // Display values that are not affected by "float" property 81 case "-frame": 82 case "-frameset": 83 // 'block' is assumed here 84 default: 85 // No change 86 return false; 87 } 88} 89 90function &create_pdf_box(&$root, &$pipeline) { 91 switch ($root->node_type()) { 92 case XML_DOCUMENT_NODE: 93 // TODO: some magic from traverse_dom_tree 94 $box =& create_document_box($root, $pipeline); 95 return $box; 96 case XML_ELEMENT_NODE: 97 $box =& create_node_box($root, $pipeline); 98 return $box; 99 case XML_TEXT_NODE: 100 $box =& create_text_box($root, $pipeline); 101 return $box; 102 default: 103 die("Unsupported node type:".$root->node_type()); 104 } 105} 106 107function &create_document_box(&$root, &$pipeline) { 108 return BlockBox::create($root, $pipeline); 109} 110 111function &create_node_box(&$root, &$pipeline) { 112 // Determine CSS proerty value for current child 113 $css_state =& $pipeline->get_current_css_state(); 114 $css_state->pushDefaultState(); 115 116 $default_css = $pipeline->get_default_css(); 117 $default_css->apply($root, $css_state, $pipeline); 118 119 // Store the default 'display' value; we'll need it later when checking for impossible tag/display combination 120 $handler =& CSS::get_handler(CSS_DISPLAY); 121 $default_display = $handler->get($css_state->getState()); 122 123 // Initially generated boxes do not require block wrappers 124 // Block wrappers are required in following cases: 125 // - float property is specified for non-block box which cannot be directly converted to block box 126 // (a button, for example) 127 // - display set to block for such box 128 $need_block_wrapper = false; 129 130 // TODO: some inheritance magic 131 132 // Order is important. Items with most priority should be applied last 133 // Tag attributes 134 execute_attrs_before($root, $pipeline); 135 136 // CSS stylesheet 137 $css =& $pipeline->get_current_css(); 138 $css->apply($root, $css_state, $pipeline); 139 140 // values from 'style' attribute 141 if ($root->has_attribute("style")) { 142 parse_style_attr($root, $css_state, $pipeline); 143 }; 144 145 _fix_tag_display($default_display, $css_state, $pipeline); 146 147 execute_attrs_after_styles($root, $pipeline); 148 149 // CSS 2.1: 150 // 9.7 Relationships between 'display', 'position', and 'float' 151 // The three properties that affect box generation and layout 152 // 'display', 'position', and 'float' interact as follows: 153 // 1. If 'display' has the value 'none', then 'position' and 'float' do not apply. 154 // In this case, the element generates no box. 155 $position_handler =& CSS::get_handler(CSS_POSITION); 156 $float_handler =& CSS::get_handler(CSS_FLOAT); 157 158 // 2. Otherwise, if 'position' has the value 'absolute' or 'fixed', the box is absolutely positioned, 159 // the computed value of 'float' is 'none', and display is set according to the table below. 160 // The position of the box will be determined by the 'top', 'right', 'bottom' and 'left' properties and 161 // the box's containing block. 162 $position = $css_state->get_property(CSS_POSITION); 163 if ($position === CSS_PROPERTY_INHERIT) { 164 $position = $css_state->getInheritedProperty(CSS_POSITION); 165 }; 166 167 if ($position === POSITION_ABSOLUTE || 168 $position === POSITION_FIXED) { 169 $float_handler->replace(FLOAT_NONE, $css_state); 170 $need_block_wrapper |= _fix_display_position_float($css_state); 171 }; 172 173 // 3. Otherwise, if 'float' has a value other than 'none', the box is floated and 'display' is set 174 // according to the table below. 175 $float = $css_state->get_property(CSS_FLOAT); 176 if ($float != FLOAT_NONE) { 177 $need_block_wrapper |= _fix_display_position_float($css_state); 178 }; 179 180 // Process some special nodes, which should not get their 'display' values overwritten (unless 181 // current display value is 'none' 182 $current_display = $css_state->get_property(CSS_DISPLAY); 183 184 if ($current_display != 'none') { 185 switch ($root->tagname()) { 186 case 'body': 187 $handler =& CSS::get_handler(CSS_DISPLAY); 188 $handler->css('-body', $pipeline); 189 break; 190 case 'br': 191 $handler =& CSS::get_handler(CSS_DISPLAY); 192 $handler->css('-break', $pipeline); 193 break; 194 case 'img': 195 $handler =& CSS::get_handler(CSS_DISPLAY); 196 $need_block_wrapper |= ($handler->get($css_state->getState()) == 'block'); 197 $handler->css('-image', $pipeline); 198 break; 199 }; 200 }; 201 202 // 4. Otherwise, if the element is the root element, 'display' is set according to the table below. 203 // 5. Otherwise, the remaining 'display' property values apply as specified. (see _fix_display_position_float) 204 205 switch($css_state->get_property(CSS_DISPLAY)) { 206 case 'block': 207 $box =& BlockBox::create($root, $pipeline); 208 break; 209 case '-break': 210 $box =& BRBox::create($pipeline); 211 break; 212 case '-body': 213 $box =& BodyBox::create($root, $pipeline); 214 break; 215 case '-button': 216 $box =& ButtonBox::create($root, $pipeline); 217 break; 218 case '-button-reset': 219 $box =& ButtonResetBox::create($root, $pipeline); 220 break; 221 case '-button-submit': 222 $box =& ButtonSubmitBox::create($root, $pipeline); 223 break; 224 case '-button-image': 225 $box =& ButtonImageBox::create($root, $pipeline); 226 break; 227 case '-checkbox': 228 $box =& CheckBox::create($root, $pipeline); 229 break; 230 case '-form': 231 $box =& FormBox::create($root, $pipeline); 232 break; 233 case '-frame': 234 inc_frame_level(); 235 $box =& FrameBox::create($root, $pipeline); 236 dec_frame_level(); 237 break; 238 case '-frameset': 239 inc_frame_level(); 240 $box =& FramesetBox::create($root, $pipeline); 241 dec_frame_level(); 242 break; 243 case '-iframe': 244 inc_frame_level(); 245 $box =& IFrameBox::create($root, $pipeline); 246 dec_frame_level(); 247 break; 248 case '-textarea': 249 $box =& TextAreaInputBox::create($root, $pipeline); 250 break; 251 case '-image': 252 $box =& IMGBox::create($root, $pipeline); 253 break; 254 case 'inline': 255 $box =& InlineBox::create($root, $pipeline); 256 break; 257 case 'inline-block': 258 $box =& InlineBlockBox::create($root, $pipeline); 259 break; 260 case '-legend': 261 $box =& LegendBox::create($root, $pipeline); 262 break; 263 case 'list-item': 264 $box =& ListItemBox::create($root, $pipeline); 265 break; 266 case 'none': 267 $box =& NullBox::create(); 268 break; 269 case '-radio': 270 $box =& RadioBox::create($root, $pipeline); 271 break; 272 case '-select': 273 $box =& SelectBox::create($root, $pipeline); 274 break; 275 case 'table': 276 $box =& TableBox::create($root, $pipeline); 277 break; 278 case 'table-cell': 279 $box =& TableCellBox::create($root, $pipeline); 280 break; 281 case 'table-row': 282 $box =& TableRowBox::create($root, $pipeline); 283 break; 284 case 'table-row-group': 285 case 'table-header-group': 286 case 'table-footer-group': 287 $box =& TableSectionBox::create($root, $pipeline); 288 break; 289 case '-text': 290 $box =& TextInputBox::create($root, $pipeline); 291 break; 292 case '-password': 293 $box =& PasswordInputBox::create($root, $pipeline); 294 break; 295 default: 296 /** 297 * If 'display' value is invalid or unsupported, fall back to 'block' mode 298 */ 299 error_log("Unsupported 'display' value: ".$css_state->get_property(CSS_DISPLAY)); 300 $box =& BlockBox::create($root, $pipeline); 301 break; 302 } 303 304 // Now check if pseudoelement should be created; in this case we'll use the "inline wrapper" box 305 // containing both generated box and pseudoelements 306 // 307 $pseudoelements = $box->get_css_property(CSS_HTML2PS_PSEUDOELEMENTS); 308 309 if ($pseudoelements & CSS_HTML2PS_PSEUDOELEMENTS_BEFORE) { 310 // Check if :before preudoelement exists 311 $before =& create_pdf_pseudoelement($root, SELECTOR_PSEUDOELEMENT_BEFORE, $pipeline); 312 if (!is_null($before)) { 313 $box->insert_child(0, $before); 314 }; 315 }; 316 317 if ($pseudoelements & CSS_HTML2PS_PSEUDOELEMENTS_AFTER) { 318 // Check if :after pseudoelement exists 319 $after =& create_pdf_pseudoelement($root, SELECTOR_PSEUDOELEMENT_AFTER, $pipeline); 320 if (!is_null($after)) { 321 $box->add_child($after); 322 }; 323 }; 324 325 // Check if this box needs a block wrapper (for example, floating button) 326 // Note that to keep float/position information, we clear the CSS stack only 327 // AFTER the wrapper box have been created; BUT we should clear the following CSS properties 328 // to avoid the fake wrapper box actually affect the layout: 329 // - margin 330 // - border 331 // - padding 332 // - background 333 // 334 if ($need_block_wrapper) { 335 /** 336 * Clear POSITION/FLOAT properties on wrapped boxes 337 */ 338 $box->setCSSProperty(CSS_POSITION, POSITION_STATIC); 339 $box->setCSSProperty(CSS_POSITION, FLOAT_NONE); 340 341 $wc = $box->get_css_property(CSS_WIDTH); 342 343 // Note that if element width have been set as a percentage constraint and we're adding a block wrapper, 344 // then we need to: 345 // 1. set the same percentage width constraint to the wrapper element (will be done implicilty if we will not 346 // modify the 'width' CSS handler stack 347 // 2. set the wrapped element's width constraint to 100%, otherwise it will be narrower than expected 348 if ($wc->isFraction()) { 349 $box->setCSSProperty(CSS_WIDTH, new WCFraction(1)); 350 } 351 352 $handler =& CSS::get_handler(CSS_MARGIN); 353 $box->setCSSProperty(CSS_MARGIN, $handler->default_value()); 354 355 /** 356 * Note: default border does not contain any fontsize-dependent 357 * values, so we may safely use zero as a base font size 358 */ 359 $border_handler =& CSS::get_handler(CSS_BORDER); 360 $value = $border_handler->default_value(); 361 $value->units2pt(0); 362 $box->setCSSProperty(CSS_BORDER, $value); 363 364 $handler =& CSS::get_handler(CSS_PADDING); 365 $box->setCSSProperty(CSS_PADDING, $handler->default_value()); 366 367 $handler =& CSS::get_handler(CSS_BACKGROUND); 368 $box->setCSSProperty(CSS_BACKGROUND, $handler->default_value()); 369 370 // Create "clean" block box 371 $wrapper =& new BlockBox(); 372 $wrapper->readCSS($pipeline->get_current_css_state()); 373 $wrapper->add_child($box); 374 375 // Remove CSS propery values from stack 376 execute_attrs_after($root, $pipeline); 377 378 $css_state->popState(); 379 380 return $wrapper; 381 } else { 382 // Remove CSS propery values from stack 383 execute_attrs_after($root, $pipeline); 384 $css_state->popState(); 385 386 $box->set_tagname($root->tagname()); 387 return $box; 388 }; 389} 390 391function &create_text_box(&$root, &$pipeline) { 392 // Determine CSS property value for current child 393 $css_state =& $pipeline->get_current_css_state(); 394 $css_state->pushDefaultTextState(); 395 396 /** 397 * No text boxes generated by empty text nodes. 398 * Note that nodes containing spaces only are NOT empty, as they may 399 * correspond, for example, to whitespace between tags. 400 */ 401 if ($root->content !== "") { 402 $box =& InlineBox::create($root, $pipeline); 403 } else { 404 $box = null; 405 } 406 407 // Remove CSS property values from stack 408 $css_state->popState(); 409 410 return $box; 411} 412 413function &create_pdf_pseudoelement($root, $pe_type, &$pipeline) { 414 // Store initial values to CSS stack 415 $css_state =& $pipeline->get_current_css_state(); 416 $css_state->pushDefaultState(); 417 418 // Initially generated boxes do not require block wrappers 419 // Block wrappers are required in following cases: 420 // - float property is specified for non-block box which cannot be directly converted to block box 421 // (a button, for example) 422 // - display set to block for such box 423 $need_block_wrapper = false; 424 425 $css =& $pipeline->get_current_css(); 426 $css->apply_pseudoelement($pe_type, $root, $css_state, $pipeline); 427 428 // Now, if no content found, just return 429 // 430 $content_obj = $css_state->get_property(CSS_CONTENT); 431 if ($content_obj === CSS_PROPERTY_INHERIT) { 432 $content_obj = $css_state->getInheritedProperty(CSS_CONTENT); 433 }; 434 $content = $content_obj->render($pipeline->get_counters()); 435 436 if ($content === '') { 437 $css_state->popState(); 438 439 $dummy = null; 440 return $dummy; 441 }; 442 443 // CSS 2.1: 444 // 9.7 Relationships between 'display', 'position', and 'float' 445 // The three properties that affect box generation and layout 446 // 'display', 'position', and 'float' interact as follows: 447 // 1. If 'display' has the value 'none', then 'position' and 'float' do not apply. 448 // In this case, the element generates no box. 449 450 // 2. Otherwise, if 'position' has the value 'absolute' or 'fixed', the box is absolutely positioned, 451 // the computed value of 'float' is 'none', and display is set according to the table below. 452 // The position of the box will be determined by the 'top', 'right', 'bottom' and 'left' properties and 453 // the box's containing block. 454 $position_handler =& CSS::get_handler(CSS_POSITION); 455 $float_handler =& CSS::get_handler(CSS_FLOAT); 456 457 $position = $position_handler->get($css_state->getState()); 458 if ($position === CSS_PROPERTY_INHERIT) { 459 $position = $css_state->getInheritedProperty(CSS_POSITION); 460 }; 461 462 if ($position === POSITION_ABSOLUTE || $position === POSITION_FIXED) { 463 $float_handler->replace(FLOAT_NONE); 464 $need_block_wrapper |= _fix_display_position_float($css_state); 465 }; 466 467 // 3. Otherwise, if 'float' has a value other than 'none', the box is floated and 'display' is set 468 // according to the table below. 469 $float = $float_handler->get($css_state->getState()); 470 if ($float != FLOAT_NONE) { 471 $need_block_wrapper |= _fix_display_position_float($css_state); 472 }; 473 474 // 4. Otherwise, if the element is the root element, 'display' is set according to the table below. 475 // 5. Otherwise, the remaining 'display' property values apply as specified. (see _fix_display_position_float) 476 477 // Note that pseudoelements may get only standard display values 478 $display_handler =& CSS::get_handler(CSS_DISPLAY); 479 $display = $display_handler->get($css_state->getState()); 480 481 switch ($display) { 482 case 'block': 483 $box =& BlockBox::create_from_text($content, $pipeline); 484 break; 485 case 'inline': 486 $ws_handler =& CSS::get_handler(CSS_WHITE_SPACE); 487 $box =& InlineBox::create_from_text($content, 488 $ws_handler->get($css_state->getState()), 489 $pipeline); 490 break; 491 default: 492 die('Unsupported "display" value: '.$display_handler->get($css_state->getState())); 493 } 494 495 // Check if this box needs a block wrapper (for example, floating button) 496 // Note that to keep float/position information, we clear the CSS stack only 497 // AFTER the wrapper box have been created; BUT we should clear the following CSS properties 498 // to avoid the fake wrapper box actually affect the layout: 499 // - margin 500 // - border 501 // - padding 502 // - background 503 // 504 if ($need_block_wrapper) { 505 $handler =& CSS::get_handler(CSS_MARGIN); 506 $handler->css("0",$pipeline); 507 508 pop_border(); 509 push_border(default_border()); 510 511 pop_padding(); 512 push_padding(default_padding()); 513 514 $handler =& CSS::get_handler(CSS_BACKGROUND); 515 $handler->css('transparent',$pipeline); 516 517 // Create "clean" block box 518 $wrapper =& new BlockBox(); 519 $wrapper->readCSS($pipeline->get_current_css_state()); 520 $wrapper->add_child($box); 521 522 $css_state->popState(); 523 return $wrapper; 524 } else { 525 $css_state->popState(); 526 return $box; 527 }; 528} 529 530function is_inline(&$box) { 531 if (is_a($box, "TextBox")) { return true; }; 532 533 $display = $box->get_css_property(CSS_DISPLAY); 534 535 return 536 $display === '-button' || 537 $display === '-button-reset' || 538 $display === '-button-submit' || 539 $display === '-button-image' || 540 $display === '-checkbox' || 541 $display === '-image' || 542 $display === 'inline' || 543 $display === 'inline-block' || 544 $display === 'none' || 545 $display === '-radio' || 546 $display === '-select' || 547 $display === '-text' || 548 $display === '-password'; 549} 550 551function is_whitespace(&$box) { 552 return 553 is_a($box, "WhitespaceBox") || 554 is_a($box, "NullBox"); 555} 556 557function is_container(&$box) { 558 return is_a($box, "GenericContainerBox") && 559 !is_a($box, "GenericInlineBox") || 560 is_a($box, "InlineBox"); 561} 562 563function is_span(&$box) { 564 return is_a($box, "InlineBox"); 565} 566 567function is_table_cell(&$box) { 568 return is_a($box, "TableCellBox"); 569} 570?>