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.inline.php,v 1.53 2007/01/24 18:55:44 Konstantin Exp $
3
 
4
require_once(HTML2PS_DIR.'encoding.inc.php');
5
 
6
define('SYMBOL_SHY', code_to_utf8(0xAD));
7
define('BROKEN_SYMBOL', chr(0xC2));
8
 
9
class LineBox {
10
  var $top;
11
  var $right;
12
  var $bottom;
13
  var $left;
14
 
15
  function LineBox() { }
16
 
17
  function &copy() {
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
 
82
class 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
?>