Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 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
11
function 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
//
23
function 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
//
34
function _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
 
90
function &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
 
107
function &create_document_box(&$root, &$pipeline) {
108
  return BlockBox::create($root, $pipeline);
109
}
110
 
111
function &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
 
391
function &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
 
413
function &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
 
530
function 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
 
551
function is_whitespace(&$box) {
552
  return
553
    is_a($box, "WhitespaceBox") ||
554
    is_a($box, "NullBox");
555
}
556
 
557
function is_container(&$box) {
558
  return is_a($box, "GenericContainerBox") &&
559
    !is_a($box, "GenericInlineBox") ||
560
    is_a($box, "InlineBox");
561
}
562
 
563
function is_span(&$box) {
564
  return is_a($box, "InlineBox");
565
}
566
 
567
function is_table_cell(&$box) {
568
  return is_a($box, "TableCellBox");
569
}
570
?>