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.text.php,v 1.56 2007/05/07 12:15:53 Konstantin Exp $
3
 
4
require_once(HTML2PS_DIR.'box.inline.simple.php');
5
 
6
// TODO: from my POV, it wll be better to pass the font- or CSS-controlling object to the constructor
7
// instead of using globally visible functions in 'show'.
8
 
9
class TextBox extends SimpleInlineBox {
10
  var $words;
11
  var $encodings;
12
  var $hyphens;
13
  var $_widths;
14
  var $_word_widths;
15
  var $_wrappable;
16
  var $wrapped;
17
 
18
  function TextBox() {
19
    $this->SimpleInlineBox();
20
 
21
    $this->words        = array();
22
    $this->encodings    = array();
23
    $this->hyphens      = array();
24
    $this->_word_widths = array();
25
    $this->_wrappable   = array();
26
    $this->wrapped      = null;
27
    $this->_widths      = array();
28
 
29
    $this->font_size = 0;
30
    $this->ascender  = 0;
31
    $this->descender = 0;
32
    $this->width     = 0;
33
    $this->height    = 0;
34
  }
35
 
36
  /**
37
   * Check if given subword contains soft hyphens and calculate
38
   */
39
  function _make_wrappable(&$driver, $base_width, $font_name, $font_size, $subword_index) {
40
    $hyphens = $this->hyphens[$subword_index];
41
    $wrappable = array();
42
 
43
    foreach ($hyphens as $hyphen) {
44
      $subword_wrappable_index = $hyphen;
45
      $subword_wrappable_width = $base_width + $driver->stringwidth(substr($this->words[$subword_index], 0, $subword_wrappable_index),
46
                                                                    $font_name,
47
                                                                    $this->encodings[$subword_index],
48
                                                                    $font_size);
49
      $subword_full_width = $subword_wrappable_width + $driver->stringwidth('-',
50
                                                                            $font_name,
51
                                                                            "iso-8859-1",
52
                                                                            $font_size);
53
 
54
      $wrappable[] = array($subword_index, $subword_wrappable_index, $subword_wrappable_width, $subword_full_width);
55
    };
56
    return $wrappable;
57
  }
58
 
59
  function get_content() {
60
    return join('', array_map(array($this, 'get_content_callback'), $this->words, $this->encodings));
61
  }
62
 
63
  function get_content_callback($word, $encoding) {
64
    $manager_encoding =& ManagerEncoding::get();
65
    return $manager_encoding->to_utf8($word, $encoding);
66
  }
67
 
68
  function get_height() {
69
    return $this->height;
70
  }
71
 
72
  function put_height($value) {
73
    $this->height = $value;
74
  }
75
 
76
  // Apply 'line-height' CSS property; modifies the default_baseline value
77
  // (NOT baseline, as it is calculated - and is overwritten - in the close_line
78
  // method of container box
79
  //
80
  // Note that underline position (or 'descender' in terms of PDFLIB) -
81
  // so, simple that space of text box under the baseline - is scaled too
82
  // when 'line-height' is applied
83
  //
84
  function _apply_line_height() {
85
    $height     = $this->get_height();
86
    $under      = $height - $this->default_baseline;
87
 
88
    $line_height = $this->get_css_property(CSS_LINE_HEIGHT);
89
 
90
    if ($height > 0) {
91
      $scale = $line_height->apply($this->ascender + $this->descender) / ($this->ascender + $this->descender);
92
    } else {
93
      $scale = 0;
94
    };
95
 
96
    // Calculate the height delta of the text box
97
 
98
    $delta = $height * ($scale-1);
99
    $this->put_height(($this->ascender + $this->descender)*$scale);
100
    $this->default_baseline = $this->default_baseline + $delta/2;
101
  }
102
 
103
  function _get_font_name(&$viewport, $subword_index) {
104
    if (isset($this->_cache[CACHE_TYPEFACE][$subword_index])) {
105
      return $this->_cache[CACHE_TYPEFACE][$subword_index];
106
    };
107
 
108
    $font_resolver =& $viewport->get_font_resolver();
109
 
110
    $font = $this->get_css_property(CSS_FONT);
111
 
112
    $typeface = $font_resolver->get_typeface_name($font->family,
113
                                                $font->weight,
114
                                                $font->style,
115
                                                $this->encodings[$subword_index]);
116
 
117
    $this->_cache[CACHE_TYPEFACE][$subword_index] = $typeface;
118
 
119
    return $typeface;
120
  }
121
 
122
  function add_subword($raw_subword, $encoding, $hyphens) {
123
    $text_transform = $this->get_css_property(CSS_TEXT_TRANSFORM);
124
    switch ($text_transform) {
125
    case CSS_TEXT_TRANSFORM_CAPITALIZE:
126
      $subword = ucwords($raw_subword);
127
      break;
128
    case CSS_TEXT_TRANSFORM_UPPERCASE:
129
      $subword = strtoupper($raw_subword);
130
      break;
131
    case CSS_TEXT_TRANSFORM_LOWERCASE:
132
      $subword = strtolower($raw_subword);
133
      break;
134
    case CSS_TEXT_TRANSFORM_NONE:
135
      $subword = $raw_subword;
136
      break;
137
    }
138
 
139
    $this->words[]     = $subword;
140
    $this->encodings[] = $encoding;
141
    $this->hyphens[]   = $hyphens;
142
  }
143
 
144
  function &create($text, $encoding, &$pipeline) {
145
    $box =& TextBox::create_empty($pipeline);
146
    $box->add_subword($text, $encoding, array());
147
    return $box;
148
  }
149
 
150
  function &create_empty(&$pipeline) {
151
    $box =& new TextBox();
152
    $css_state = $pipeline->get_current_css_state();
153
 
154
    $box->readCSS($css_state);
155
    $css_state = $pipeline->get_current_css_state();
156
 
157
    return $box;
158
  }
159
 
160
  function readCSS(&$state) {
161
    parent::readCSS($state);
162
 
163
    $this->_readCSSLengths($state,
164
                           array(CSS_TEXT_INDENT,
165
                                 CSS_LETTER_SPACING));
166
  }
167
 
168
  // Inherited from GenericFormattedBox
169
  function get_descender() {
170
    return $this->descender;
171
  }
172
 
173
  function get_ascender() {
174
    return $this->ascender;
175
  }
176
 
177
  function get_baseline() {
178
    return $this->baseline;
179
  }
180
 
181
  function get_min_width_natural(&$context) {
182
    return $this->get_full_width();
183
  }
184
 
185
  function get_min_width(&$context) {
186
    return $this->get_full_width();
187
  }
188
 
189
  function get_max_width(&$context) {
190
    return $this->get_full_width();
191
  }
192
 
193
  // Checks if current inline box should cause a line break inside the parent box
194
  //
195
  // @param $parent reference to a parent box
196
  // @param $content flow context
197
  // @return true if line break occurred; false otherwise
198
  //
199
  function maybe_line_break(&$parent, &$context) {
200
    if (!$parent->line_break_allowed()) {
201
      return false;
202
    };
203
 
204
    $last =& $parent->last_in_line();
205
    if ($last) {
206
      // Check  if last  box was  a note  call box.  Punctuation marks
207
      // after  a note-call  box should  not be  wrapped to  new line,
208
      // while "plain" words may be wrapped.
209
      if ($last->is_note_call() && $this->is_punctuation()) {
210
        return false;
211
      };
212
    };
213
 
214
    // Calculate the x-coordinate of this box right edge
215
    $right_x = $this->get_full_width() + $parent->_current_x;
216
 
217
    $need_break = false;
218
 
219
    // Check for right-floating boxes
220
    // If upper-right corner of this inline box is inside of some float, wrap the line
221
    $float = $context->point_in_floats($right_x, $parent->_current_y);
222
    if ($float) {
223
      $need_break = true;
224
    };
225
 
226
    // No floats; check if we had run out the right edge of container
227
    // TODO: nobr-before, nobr-after
228
    if (($right_x > $parent->get_right()+EPSILON)) {
229
      // Now check if parent line box contains any other boxes;
230
      // if not, we should draw this box unless we have a floating box to the left
231
 
232
      $first = $parent->get_first();
233
 
234
      $ti = $this->get_css_property(CSS_TEXT_INDENT);
235
      $indent_offset = $ti->calculate($parent);
236
 
237
      if ($parent->_current_x > $parent->get_left() + $indent_offset + EPSILON) {
238
        $need_break = true;
239
      };
240
    }
241
 
242
    // As close-line will not change the current-Y parent coordinate if no
243
    // items were in the line box, we need to offset this explicitly in this case
244
    //
245
    if ($parent->line_box_empty() && $need_break) {
246
      $parent->_current_y -= $this->get_height();
247
    };
248
 
249
    if ($need_break) {
250
      // Check if current box contains soft hyphens and use them, breaking word into parts
251
      $size = count($this->_wrappable);
252
      if ($size > 0) {
253
        $width_delta = $right_x - $parent->get_right();
254
        if (!is_null($float)) {
255
          $width_delta = $right_x - $float->get_left_margin();
256
        };
257
 
258
        $this->_find_soft_hyphen($parent, $width_delta);
259
      };
260
 
261
      $parent->close_line($context);
262
 
263
      // Check if parent inline boxes have left padding/margins and add them to current_x
264
      $element = $this->parent;
265
      while (!is_null($element) && is_a($element,"GenericInlineBox")) {
266
        $parent->_current_x += $element->get_extra_left();
267
        $element = $element->parent;
268
      };
269
    };
270
 
271
    return $need_break;
272
  }
273
 
274
  function _find_soft_hyphen(&$parent, $width_delta) {
275
    /**
276
     * Now we search for soft hyphen closest to the right margin
277
     */
278
    $size = count($this->_wrappable);
279
    for ($i=$size-1; $i>=0; $i--) {
280
      $wrappable = $this->_wrappable[$i];
281
      if ($this->get_width() - $wrappable[3] > $width_delta) {
282
        $this->save_wrapped($wrappable, $parent, $context);
283
        $parent->append_line($this);
284
        return;
285
      };
286
    };
287
  }
288
 
289
  function save_wrapped($wrappable, &$parent, &$context) {
290
    $this->wrapped = array($wrappable,
291
                           $parent->_current_x + $this->get_extra_left(),
292
                           $parent->_current_y - $this->get_extra_top());
293
  }
294
 
295
  function reflow(&$parent, &$context) {
296
    // Check if we need a line break here (possilble several times in a row, if we
297
    // have a long word and a floating box intersecting with this word
298
    //
299
    // To prevent infinite loop, we'll use a limit of 100 sequental line feeds
300
    $i=0;
301
 
302
    do { $i++; } while ($this->maybe_line_break($parent, $context) && $i < 100);
303
 
304
    // Determine the baseline position and height of the text-box using line-height CSS property
305
    $this->_apply_line_height();
306
 
307
    // set default baseline
308
    $this->baseline = $this->default_baseline;
309
 
310
    // append current box to parent line box
311
    $parent->append_line($this);
312
 
313
    // Determine coordinates of upper-left _margin_ corner
314
    $this->guess_corner($parent);
315
 
316
    // Offset parent current X coordinate
317
    if (!is_null($this->wrapped)) {
318
      $parent->_current_x += $this->get_full_width() - $this->wrapped[0][2];
319
    } else {
320
      $parent->_current_x += $this->get_full_width();
321
    };
322
 
323
    // Extends parents height
324
    $parent->extend_height($this->get_bottom());
325
 
326
    // Update the value of current collapsed margin; pure text (non-span)
327
    // boxes always have zero margin
328
 
329
    $context->pop_collapsed_margin();
330
    $context->push_collapsed_margin( 0 );
331
  }
332
 
333
  function getWrappedWidthAndHyphen() {
334
    return $this->wrapped[0][3];
335
  }
336
 
337
  function getWrappedWidth() {
338
    return $this->wrapped[0][2];
339
  }
340
 
341
  function reflow_text(&$driver) {
342
    $num_words = count($this->words);
343
 
344
    /**
345
     * Empty text box
346
     */
347
    if ($num_words == 0) {
348
      return true;
349
    };
350
 
351
    /**
352
     * A simple assumption is made: fonts used for different encodings
353
     * have equal ascender/descender values  (while they have the same
354
     * typeface, style and weight).
355
     */
356
    $font_name = $this->_get_font_name($driver, 0);
357
 
358
    /**
359
     * Get font vertical metrics
360
     */
361
    $ascender  = $driver->font_ascender($font_name, $this->encodings[0]);
362
    if (is_null($ascender)) {
363
      error_log("TextBox::reflow_text: cannot get font ascender");
364
      return null;
365
    };
366
 
367
    $descender = $driver->font_descender($font_name, $this->encodings[0]);
368
    if (is_null($descender)) {
369
      error_log("TextBox::reflow_text: cannot get font descender");
370
      return null;
371
    };
372
 
373
    /**
374
     * Setup box size
375
     */
376
    $font = $this->get_css_property(CSS_FONT_SIZE);
377
    $font_size = $font->getPoints();
378
 
379
    // Both ascender and descender should make $font_size
380
    // as it is not guaranteed that $ascender + $descender == 1,
381
    // we should normalize the result
382
    $koeff = $font_size / ($ascender + $descender);
383
    $this->ascender         = $ascender  * $koeff;
384
    $this->descender        = $descender * $koeff;
385
 
386
    $this->default_baseline = $this->ascender;
387
    $this->height           = $this->ascender + $this->descender;
388
 
389
    /**
390
     * Determine box width
391
     */
392
    if ($font_size > 0) {
393
      $width = 0;
394
 
395
      for ($i=0; $i<$num_words; $i++) {
396
        $font_name = $this->_get_font_name($driver, $i);
397
 
398
        $current_width = $driver->stringwidth($this->words[$i],
399
                                                $font_name,
400
                                                $this->encodings[$i],
401
                                                $font_size);
402
        $this->_word_widths[] = $current_width;
403
 
404
        // Add information about soft hyphens
405
        $this->_wrappable = array_merge($this->_wrappable, $this->_make_wrappable($driver, $width, $font_name, $font_size, $i));
406
 
407
        $width += $current_width;
408
      };
409
 
410
      $this->width = $width;
411
    } else {
412
      $this->width = 0;
413
    };
414
 
415
    $letter_spacing = $this->get_css_property(CSS_LETTER_SPACING);
416
 
417
    if ($letter_spacing->getPoints() != 0) {
418
      $this->_widths = array();
419
 
420
      for ($i=0; $i<$num_words; $i++) {
421
        $num_chars = strlen($this->words[$i]);
422
 
423
        for ($j=0; $j<$num_chars; $j++) {
424
          $this->_widths[] = $driver->stringwidth($this->words[$i]{$j},
425
                                                    $font_name,
426
                                                    $this->encodings[$i],
427
                                                    $font_size);
428
        };
429
 
430
        $this->width += $letter_spacing->getPoints()*$num_chars;
431
      };
432
    };
433
 
434
    return true;
435
  }
436
 
437
  function show(&$driver) {
438
    /**
439
     * Check if font-size have been set to 0; in this case we should not draw this box at all
440
     */
441
    $font_size = $this->get_css_property(CSS_FONT_SIZE);
442
    if ($font_size->getPoints() == 0) {
443
      return true;
444
    }
445
 
446
    // Check if current text box will be cut-off by the page edge
447
    // Get Y coordinate of the top edge of the box
448
    $top    = $this->get_top_margin();
449
    // Get Y coordinate of the bottom edge of the box
450
    $bottom = $this->get_bottom_margin();
451
 
452
    $top_inside    = $top    >= $driver->getPageBottom()-EPSILON;
453
    $bottom_inside = $bottom >= $driver->getPageBottom()-EPSILON;
454
 
455
    if (!$top_inside && !$bottom_inside) {
456
      return true;
457
    }
458
 
459
    return $this->_showText($driver);
460
  }
461
 
462
  function _showText(&$driver) {
463
    if (!is_null($this->wrapped)) {
464
      return $this->_showTextWrapped($driver);
465
    } else {
466
      return $this->_showTextNormal($driver);
467
    };
468
  }
469
 
470
  function _showTextWrapped(&$driver) {
471
    // draw generic box
472
    parent::show($driver);
473
 
474
    $font_size = $this->get_css_property(CSS_FONT_SIZE);
475
 
476
    $decoration = $this->get_css_property(CSS_TEXT_DECORATION);
477
 
478
    // draw text decoration
479
    $driver->decoration($decoration['U'],
480
                        $decoration['O'],
481
                        $decoration['T']);
482
 
483
    $letter_spacing = $this->get_css_property(CSS_LETTER_SPACING);
484
 
485
    // Output text with the selected font
486
    // note that we're using $default_baseline;
487
    // the alignment offset - the difference between baseline and default_baseline values
488
    // is taken into account inside the get_top/get_bottom functions
489
    //
490
    $current_char = 0;
491
 
492
    $left = $this->wrapped[1];
493
    $top  = $this->get_top() - $this->default_baseline;
494
    $num_words = count($this->words);
495
 
496
    /**
497
     * First part of wrapped word (before hyphen)
498
     */
499
    for ($i=0; $i<$this->wrapped[0][0]; $i++) {
500
      // Activate font
501
      $status = $driver->setfont($this->_get_font_name($driver, $i),
502
                                 $this->encodings[$i],
503
                                 $font_size->getPoints());
504
      if (is_null($status)) {
505
        error_log("TextBox::show: setfont call failed");
506
        return null;
507
      };
508
 
509
      $driver->show_xy($this->words[$i],
510
                       $left,
511
                       $this->wrapped[2] - $this->default_baseline);
512
      $left += $this->_word_widths[$i];
513
    };
514
 
515
    $index = $this->wrapped[0][0];
516
 
517
    $status = $driver->setfont($this->_get_font_name($driver, $index),
518
                               $this->encodings[$index],
519
                               $font_size->getPoints());
520
    if (is_null($status)) {
521
      error_log("TextBox::show: setfont call failed");
522
      return null;
523
    };
524
 
525
    $driver->show_xy(substr($this->words[$index],0,$this->wrapped[0][1])."-",
526
                     $left,
527
                     $this->wrapped[2] - $this->default_baseline);
528
 
529
    /**
530
     * Second part of wrapped word (after hyphen)
531
     */
532
 
533
    $left = $this->get_left();
534
    $top  = $this->get_top();
535
    $driver->show_xy(substr($this->words[$index],$this->wrapped[0][1]),
536
                     $left,
537
                     $top - $this->default_baseline);
538
 
539
    $size = count($this->words);
540
    for ($i = $this->wrapped[0][0]+1; $i<$size; $i++) {
541
      // Activate font
542
      $status = $driver->setfont($this->_get_font_name($driver, $i),
543
                                 $this->encodings[$i],
544
                                 $font_size->getPoints());
545
      if (is_null($status)) {
546
        error_log("TextBox::show: setfont call failed");
547
        return null;
548
      };
549
 
550
      $driver->show_xy($this->words[$i],
551
                       $left,
552
                       $top - $this->default_baseline);
553
 
554
      $left += $this->_word_widths[$i];
555
    };
556
 
557
    return true;
558
  }
559
 
560
  function _showTextNormal(&$driver) {
561
    // draw generic box
562
    parent::show($driver);
563
 
564
    $font_size = $this->get_css_property(CSS_FONT_SIZE);
565
 
566
    $decoration = $this->get_css_property(CSS_TEXT_DECORATION);
567
 
568
    // draw text decoration
569
    $driver->decoration($decoration['U'],
570
                        $decoration['O'],
571
                        $decoration['T']);
572
 
573
    $letter_spacing = $this->get_css_property(CSS_LETTER_SPACING);
574
 
575
    if ($letter_spacing->getPoints() == 0) {
576
      // Output text with the selected font
577
      // note that we're using $default_baseline;
578
      // the alignment offset - the difference between baseline and default_baseline values
579
      // is taken into account inside the get_top/get_bottom functions
580
      //
581
      $size = count($this->words);
582
      $left = $this->get_left();
583
 
584
      for ($i=0; $i<$size; $i++) {
585
        // Activate font
586
        $status = $driver->setfont($this->_get_font_name($driver, $i),
587
                                   $this->encodings[$i],
588
                                   $font_size->getPoints());
589
        if (is_null($status)) {
590
          error_log("TextBox::show: setfont call failed");
591
          return null;
592
        };
593
 
594
        $driver->show_xy($this->words[$i],
595
                         $left,
596
                         $this->get_top() - $this->default_baseline);
597
 
598
        $left += $this->_word_widths[$i];
599
      };
600
    } else {
601
      $current_char = 0;
602
 
603
      $left = $this->get_left();
604
      $top  = $this->get_top() - $this->default_baseline;
605
      $num_words = count($this->words);
606
 
607
      for ($i=0; $i<$num_words; $i++) {
608
        $num_chars = strlen($this->words[$i]);
609
 
610
        for ($j=0; $j<$num_chars; $j++) {
611
          $status = $driver->setfont($this->_get_font_name($driver, $i),
612
                                     $this->encodings[$i],
613
                                     $font_size->getPoints());
614
 
615
          $driver->show_xy($this->words[$i]{$j}, $left, $top);
616
          $left += $this->_widths[$current_char] + $letter_spacing->getPoints();
617
          $current_char++;
618
        };
619
      };
620
    };
621
 
622
    return true;
623
  }
624
 
625
  function show_fixed(&$driver) {
626
    $font_size = $this->get_css_property(CSS_FONT_SIZE);
627
 
628
    // Check if font-size have been set to 0; in this case we should not draw this box at all
629
    if ($font_size->getPoints() == 0) {
630
      return true;
631
    }
632
 
633
    return $this->_showText($driver);
634
  }
635
 
636
  function offset($dx, $dy) {
637
    parent::offset($dx, $dy);
638
 
639
    // Note that horizonal offset should be called explicitly from text-align routines
640
    // otherwise wrapped part will be offset twice (as offset is called both for
641
    // wrapped and non-wrapped parts).
642
    if (!is_null($this->wrapped)) {
643
      $this->offset_wrapped($dx, $dy);
644
    };
645
  }
646
 
647
  function offset_wrapped($dx, $dy) {
648
    $this->wrapped[1] += $dx;
649
    $this->wrapped[2] += $dy;
650
  }
651
 
652
  function reflow_whitespace(&$linebox_started, &$previous_whitespace) {
653
    $linebox_started = true;
654
    $previous_whitespace = false;
655
    return;
656
  }
657
 
658
  function is_null() { return false; }
659
}
660
?>