Subversion-Projekte lars-tiefland.php_share

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

Revision Autor Zeilennr. Zeile
1 lars 1
<?php
2
 
3
/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
 
5
/**
6
 * Image_Text.
7
 *
8
 * This is the main file of the Image_Text package. This file has to be
9
 * included for usage of Image_Text.
10
 *
11
 * This is a simple example script, showing Image_Text's facilities.
12
 *
13
 * -------- Start example --------
14
 *
15
 * require_once 'Image/Text.php';
16
 *
17
 * $colors = array(
18
 *     0 => '#0d54e2',
19
 *     1 => '#e8ce7a',
20
 *     2 => '#7ae8ad'
21
 * );
22
 *
23
 * $text = "EXTERIOR: DAGOBAH -- DAY\nWith Yoda\nstrapped to\n\nhis back, Luke climbs up one of the many thick vines that grow in the swamp until he reaches the Dagobah statistics lab. Panting heavily, he continues his exercises -- grepping, installing new packages, logging in as root, and writing replacements for two-year-old shell scripts in PHP.\nYODA: Code! Yes. A programmer's strength flows from code maintainability. But beware of Perl. Terse syntax... more than one way to do it... default variables. The dark side of code maintainability are they. Easily they flow, quick to join you when code you write. If once you start down the dark path, forever will it dominate your destiny, consume you it will.\nLUKE: Is Perl better than PHP?\nYODA: No... no... no. Orderless, dirtier, more seductive.\nLUKE: But how will I know why PHP is better than Perl?\nYODA: You will know. When your code you try to read six months from now...";
24
 *
25
 * $options = array(
26
 *             'canvas'        => array('width'=> 600,'height'=> 600), // Generate a new image 600x600 pixel
27
 *             'cx'            => 300,     // Set center to the middle of the canvas
28
 *             'cy'            => 300,
29
 *             'width'         => 300,     // Set text box size
30
 *             'height'        => 300,
31
 *             'line_spacing'  => 1,       // Normal linespacing
32
 *             'angle'         => 45,      // Text rotated by 45
33
 *             'color'         => $colors, // Predefined colors
34
 *             'background_color' => '#FF0000', //red background
35
 *             'max_lines'     => 100,     // Maximum lines to render
36
 *             'min_font_size' => 2,       // Minimal/Maximal font size (for automeasurize)
37
 *             'max_font_size' => 50,
38
 *             'font_path'     => './',    // Settings for the font file
39
 *             'font_file'     => 'Vera.ttf',
40
 *             'antialias'     => true,    // Antialiase font rendering
41
 *             'halign'        => IMAGE_TEXT_ALIGN_RIGHT,  // Alignment to the right and middle
42
 *             'valign'        => IMAGE_TEXT_ALIGN_MIDDLE
43
 *         );
44
 *
45
 * // Generate a new Image_Text object
46
 * $itext = new Image_Text($text, $options);
47
 *
48
 * // Initialize and check the settings
49
 * $itext->init();
50
 
51
 * // Automatically determine optimal font size
52
 * $itext->autoMeasurize();
53
 *
54
 * // Render the image
55
 * $itext->render();
56
 *
57
 * // Display it
58
 * $itext->display();
59
 *
60
 * -------- End example --------
61
 *
62
 * PHP versions 4 and 5
63
 *
64
 * LICENSE: This source file is subject to version 3.0 of the PHP license
65
 * that is available through the world-wide-web at the following URI:
66
 * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
67
 * the PHP License and are unable to obtain it through the web, please
68
 * send a note to license@php.net so we can mail you a copy immediately.
69
 *
70
 * @category   Image
71
 * @package    Text
72
 * @author     Tobias Schlitt <toby@php.net>
73
 * @copyright  1997-2005 The PHP Group
74
 * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
75
 * @version    CVS: $Id: Text.php,v 1.32 2007/04/16 09:52:34 cweiske Exp $
76
 * @link       http://pear.php.net/package/Net_FTP2
77
 * @since      File available since Release 0.0.1
78
 */
79
 
80
/**
81
 * Require PEAR file for error handling.
82
 */
83
require_once 'PEAR.php';
84
 
85
/**
86
 * Regex to match HTML style hex triples.
87
 */
88
define("IMAGE_TEXT_REGEX_HTMLCOLOR", "/^[#|]([a-f0-9]{2})?([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})$/i", true);
89
 
90
/**
91
 * Defines horizontal alignment to the left of the text box. (This is standard.)
92
 */
93
define("IMAGE_TEXT_ALIGN_LEFT", "left", true);
94
 
95
/**
96
 * Defines horizontal alignment to the center of the text box.
97
 */
98
define("IMAGE_TEXT_ALIGN_RIGHT", "right", true);
99
 
100
/**
101
 * Defines horizontal alignment to the center of the text box.
102
 */
103
define("IMAGE_TEXT_ALIGN_CENTER", "center", true);
104
 
105
/**
106
 * Defines vertical alignment to the to the top of the text box. (This is standard.)
107
 */
108
define("IMAGE_TEXT_ALIGN_TOP", "top", true);
109
 
110
/**
111
 * Defines vertical alignment to the to the middle of the text box.
112
 */
113
define("IMAGE_TEXT_ALIGN_MIDDLE", "middle", true);
114
 
115
/**
116
 * Defines vertical alignment to the to the bottom of the text box.
117
 */
118
define("IMAGE_TEXT_ALIGN_BOTTOM", "bottom", true);
119
 
120
/**
121
 * TODO: This constant is useless until now, since justified alignment does not work yet
122
 */
123
define("IMAGE_TEXT_ALIGN_JUSTIFY", "justify", true);
124
 
125
/**
126
 * Image_Text - Advanced text maipulations in images
127
 *
128
 * Image_Text provides advanced text manipulation facilities for GD2
129
 * image generation with PHP. Simply add text clippings to your images,
130
 * let the class automatically determine lines, rotate text boxes around
131
 * their center or top left corner. These are only a couple of features
132
 * Image_Text provides.
133
 *
134
 * @license    http://www.php.net/license/3_0.txt  PHP License 3.0
135
 * @category   Image
136
 * @package    Text
137
 * @author     Tobias Schlitt <toby@php.net>
138
 * @copyright  1997-2005 The PHP Group
139
 * @version    Release: @package_version@
140
 * @link       http://pear.php.net/package/Net_FTP
141
 * @since      0.0.1
142
 * @access     public
143
 */
144
class Image_Text {
145
 
146
    /**
147
     * Options array. these options can be set through the constructor or the set() method.
148
     *
149
     * Possible options to set are:
150
     * <pre>
151
     *
152
     *      'x'                 | This sets the top left coordinates (using x/y) or the center point
153
     *      'y'                 | coordinates (using cx/cy) for your text box. The values from
154
     *      'cx'                | cx/cy will overwrite x/y.
155
     *      'cy'                |
156
     *
157
     *      'canvas'            | You can set different values as a canvas:
158
     *                          |   - A gd image resource.
159
     *                          |   - An array with 'width' and 'height'.
160
     *                          |   - Nothing (the canvas will be measured after the real text size).
161
     *
162
     *      'antialias'         | This is usually true. Set it to false to switch antialiasing off.
163
     *
164
     *      'width'             | The width and height for your text box.
165
     *      'height'            |
166
     *
167
     *      'halign'            | Alignment of your text inside the textbox. Use alignmnet constants to define
168
     *      'valign'            | vertical and horizontal alignment.
169
     *
170
     *      'angle'             | The angle to rotate your text box.
171
     *
172
     *      'color'             | An array of color values. Colors will be rotated in the mode you choose (linewise
173
     *                          | or paragraphwise). Can be in the following formats:
174
     *                          |   - String representing HTML style hex couples (+ unusual alpha couple in the first place, optional).
175
     *                          |   - Array of int values using 'r', 'g', 'b' and optionally 'a' as keys.
176
     *
177
     *      'color_mode'        | The color rotation mode for your color sets. Does only apply if you
178
     *                          | defined multiple colors. Use 'line' or 'paragraph'.
179
     *
180
     *      'background_color'  | defines the background color. NULL sets it transparent
181
     *      'enable_alpha'      | if alpha channel should be enabled. Automatically
182
     *                          | enabled when background_color is set to NULL
183
     *
184
     *      'font_path'         | Location of the font to use. The path only gives the directory path (ending with a /).
185
     *      'font_file'         | The fontfile is given in the 'font_file' option.
186
     *
187
     *      'font_size'         | The font size to render text in (will be overwriten, if you use automeasurize).
188
     *
189
     *      'line_spacing'      | Measure for the line spacing to use. Default is 0.5.
190
     *
191
     *      'min_font_size'     | Automeasurize settings. Try to keep this area as small as possible to get better
192
     *      'max_font_size'     | performance.
193
     *
194
     *      'image_type'        | The type of image (use image type constants). Is default set to PNG.
195
     *
196
     *      'dest_file'         | The destination to (optionally) save your file.
197
     * </pre>
198
     *
199
     * @access public
200
     * @var array
201
     * @see Image_Text::Image_Text(), Image_Text::set()
202
     */
203
 
204
    var $options = array(
205
            // orientation
206
            'x'                 => 0,
207
            'y'                 => 0,
208
 
209
            // surface
210
            'canvas'            => null,
211
            'antialias'         => true,
212
 
213
            // text clipping
214
            'width'             => 0,
215
            'height'            => 0,
216
 
217
            // text alignment inside the clipping
218
            'halign'             => IMAGE_TEXT_ALIGN_LEFT,
219
            'valign'             => IMAGE_TEXT_ALIGN_TOP,
220
 
221
            // angle to rotate the text clipping
222
            'angle'             => 0,
223
 
224
            // color settings
225
            'color'             => array( '#000000' ),
226
 
227
            'color_mode'        => 'line',
228
 
229
            'background_color'  => '#000000',
230
            'enable_alpha'      => false,
231
 
232
            // font settings
233
            'font_path'         => "./",
234
            'font_file'         => null,
235
            'font_size'         => 2,
236
            'line_spacing'      => 0.5,
237
 
238
            // automasurizing settings
239
            'min_font_size'     => 1,
240
            'max_font_size'     => 100,
241
 
242
            //max. lines to render
243
            'max_lines'         => 100,
244
 
245
            // misc settings
246
            'image_type'        => IMAGETYPE_PNG,
247
            'dest_file'         => ''
248
        );
249
 
250
    /**
251
     * Contains option names, which can cause re-initialization force.
252
     *
253
     * @var array
254
     * @access private
255
     */
256
 
257
    var $_reInits = array('width', 'height', 'canvas', 'angle', 'font_file', 'font_path', 'font_size');
258
 
259
    /**
260
     * The text you want to render.
261
     *
262
     * @access private
263
     * @var string
264
     */
265
 
266
    var $_text;
267
 
268
    /**
269
     * Resource ID of the image canvas.
270
     *
271
     * @access private
272
     * @var ressource
273
     */
274
 
275
    var $_img;
276
 
277
    /**
278
     * Tokens (each word).
279
     *
280
     * @access private
281
     * @var array
282
     */
283
 
284
    var $_tokens = array();
285
 
286
    /**
287
     * Fullpath to the font.
288
     *
289
     * @access private
290
     * @var string
291
     */
292
 
293
    var $_font;
294
 
295
    /**
296
     * Contains the bbox of each rendered lines.
297
     *
298
     * @access private
299
     * @var array
300
     */
301
 
302
    var $bbox = array();
303
 
304
    /**
305
     * Defines in which mode the canvas has be set.
306
     *
307
     * @access private
308
     * @var array
309
     */
310
 
311
    var $_mode = '';
312
 
313
    /**
314
     * Color indeces returned by imagecolorallocatealpha.
315
     *
316
     * @access public
317
     * @var array
318
     */
319
 
320
    var $colors = array();
321
 
322
    /**
323
     * Width and height of the (rendered) text.
324
     *
325
     * @access private
326
     * @var array
327
     */
328
 
329
    var $_realTextSize = array('width' => false, 'height' => false);
330
 
331
    /**
332
     * Measurized lines.
333
     *
334
     * @access private
335
     * @var array
336
     */
337
 
338
    var $_lines = false;
339
 
340
    /**
341
     * Fontsize for which the last measuring process was done.
342
     *
343
     * @access private
344
     * @var array
345
     */
346
 
347
    var $_measurizedSize = false;
348
 
349
    /**
350
     * Is the text object initialized?
351
     *
352
     * @access private
353
     * @var bool
354
     */
355
 
356
    var $_init = false;
357
 
358
    /**
359
     * Constructor
360
     *
361
     * Set the text and options. This initializes a new Image_Text object. You must set your text
362
     * here. Optinally you can set all options here using the $options parameter. If you finished switching
363
     * all options you have to call the init() method first befor doing anything further! See Image_Text::set()
364
     * for further information.
365
     *
366
     * @param   string  $text       Text to print.
367
     * @param   array   $options    Options.
368
     * @access public
369
     * @see Image_Text::set(), Image_Text::construct(), Image_Text::init()
370
     */
371
 
372
    function Image_Text($text, $options = null)
373
    {
374
        $this->set('text', $text);
375
        if (!empty($options)) {
376
            $this->options = array_merge($this->options, $options);
377
        }
378
    }
379
 
380
    /**
381
     * Construct and initialize an Image_Text in one step.
382
     * This method is called statically and creates plus initializes an Image_Text object.
383
     * Beware: You will have to recall init() if you set an option afterwards manually.
384
     *
385
     * @param   string  $text       Text to print.
386
     * @param   array   $options    Options.
387
     * @access public
388
     * @static
389
     * @see Image_Text::set(), Image_Text::Image_Text(), Image_Text::init()
390
     */
391
 
392
    function &construct ( $text, $options ) {
393
        $itext = new Image_Text($text, $options);
394
        $res = $itext->init();
395
        if (PEAR::isError($res)) {
396
            return $res;
397
        }
398
        return $itext;
399
    }
400
 
401
    /**
402
     * Set options
403
     *
404
     * Set a single or multiple options. It may happen that you have to reinitialize the Image_Text
405
     * object after changing options. For possible options, please take a look at the class options
406
     * array!
407
     *
408
     *
409
     * @param   mixed   $option     A single option name or the options array.
410
     * @param   mixed   $value      Option value if $option is string.
411
     * @return  bool                True on success, otherwise PEAR::Error.
412
     * @access public
413
     * @see Image_Text::Image_Text()
414
     */
415
 
416
    function set($option, $value=null)
417
    {
418
        $reInits = array_flip($this->_reInits);
419
        if (!is_array($option)) {
420
            if (!isset($value)) {
421
                return PEAR::raiseError('No value given.');
422
            }
423
            $option = array($option => $value);
424
        }
425
        foreach ($option as $opt => $val) {
426
            switch ($opt) {
427
             case 'color':
428
                $this->setColors($val);
429
                break;
430
             case 'text':
431
                if (is_array($val)) {
432
                    $this->_text = implode('\n', $val);
433
                } else {
434
                    $this->_text = $val;
435
                }
436
                break;
437
             default:
438
                $this->options[$opt] = $val;
439
                break;
440
            }
441
            if (isset($reInits[$opt])) {
442
                $this->_init = false;
443
            }
444
        }
445
        return true;
446
    }
447
 
448
    /**
449
     * Set the color-set
450
     *
451
     * Using this method you can set multiple colors for your text.
452
     * Use a simple numeric array to determine their order and give
453
     * it to this function. Multiple colors will be
454
     * cycled by the options specified 'color_mode' option. The given array
455
     * will overwrite the existing color settings!
456
     *
457
     * The following colors syntaxes are understood by this method:
458
     * - "#ffff00" hexadecimal format (HTML style), with and without #.
459
     * - "#08ffff00" hexadecimal format (HTML style) with alpha channel (08), with and without #.
460
     * - array with 'r','g','b' and (optionally) 'a' keys, using int values.
461
     * - a GD color special color (tiled,...).
462
     *
463
     * A single color or an array of colors are allowed here.
464
     *
465
     * @param   mixed  $colors       Single color or array of colors.
466
     * @return  bool                 True on success, otherwise PEAR::Error.
467
     * @access  public
468
     * @see Image_Text::setColor(), Image_Text::set()
469
     */
470
 
471
    function setColors($colors)
472
    {
473
        $i = 0;
474
        if (is_array($colors) &&
475
            (is_string($colors[0]) || is_array($colors[0]))
476
           ) {
477
            foreach ($colors as $color) {
478
                $res = $this->setColor($color,$i++);
479
                if (PEAR::isError($res)) {
480
                    return $res;
481
                }
482
            }
483
        } else {
484
            return $this->setColor($colors, $i);
485
        }
486
        return true;
487
    }
488
 
489
    /**
490
     * Set a color
491
     *
492
     * This method is used to set a color at a specific color ID inside the
493
     * color cycle.
494
     *
495
     * The following colors syntaxes are understood by this method:
496
     * - "#ffff00" hexadecimal format (HTML style), with and without #.
497
     * - "#08ffff00" hexadecimal format (HTML style) with alpha channel (08), with and without #.
498
     * - array with 'r','g','b' and (optionally) 'a' keys, using int values.
499
     *
500
     * @param   mixed    $color        Color value.
501
     * @param   mixed    $id           ID (in the color array) to set color to.
502
     * @return  bool                True on success, otherwise PEAR::Error.
503
     * @access  public
504
     * @see Image_Text::setColors(), Image_Text::set()
505
     */
506
 
507
    function setColor($color, $id=0)
508
    {
509
        if(is_array($color)) {
510
            if (isset($color['r']) && isset($color['g']) && isset($color['b'])) {
511
                $color['a'] = isset($color['a']) ? $color['a'] : 0;
512
                $this->options['colors'][$id] = $color;
513
            } else if (isset($color[0]) && isset($color[1]) && isset($color[2])) {
514
                $color['r'] = $color[0];
515
                $color['g'] = $color[1];
516
                $color['b'] = $color[2];
517
                $color['a'] = isset($color[3]) ? $color[3] : 0;
518
                $this->options['colors'][$id] = $color;
519
            } else {
520
                return PEAR::raiseError('Use keys 1,2,3 (optionally) 4 or r,g,b and (optionally) a.');
521
            }
522
        } elseif (is_string($color)) {
523
            $color = $this->_convertString2RGB($color);
524
            if ($color) {
525
                $this->options['color'][$id] = $color;
526
            } else {
527
                return PEAR::raiseError('Invalid color.');
528
            }
529
        }
530
        if ($this->_img) {
531
            $aaFactor = ($this->options['antialias']) ? 1 : -1;
532
            if (function_exists('imagecolorallocatealpha') && isset($color['a'])) {
533
                $this->colors[$id] = $aaFactor * imagecolorallocatealpha($this->_img,
534
                                $color['r'],$color['g'],$color['b'],$color['a']);
535
            } else {
536
                $this->colors[$id] = $aaFactor * imagecolorallocate($this->_img,
537
                                $color['r'],$color['g'],$color['b']);
538
            }
539
        }
540
        return true;
541
    }
542
 
543
    /**
544
     * Initialiaze the Image_Text object.
545
     *
546
     * This method has to be called after setting the options for your Image_Text object.
547
     * It initializes the canvas, normalizes some data and checks important options.
548
     * Be shure to check the initialization after you switched some options. The
549
     * set() method may force you to reinitialize the object.
550
     *
551
     * @access  public
552
     * @return  bool  True on success, otherwise PEAR::Error.
553
     * @see Image_Text::set()
554
     */
555
 
556
    function init()
557
    {
558
        // Does the fontfile exist and is readable?
559
        // todo: with some versions of the GD-library it's also possible to leave font_path empty, add strip ".ttf" from
560
        //        the fontname; the fontfile will then be automatically searched for in library-defined directories
561
        //        however this does not yet work if at this point we check for the existance of the fontfile
562
        $font_file = rtrim($this->options['font_path'], '/\\');
563
        $font_file.= (OS_WINDOWS) ? '\\' : '/';
564
        $font_file.= $this->options['font_file'];
565
        $font_file = realpath($font_file);
566
        if (empty($font_file) || !is_file($font_file) || !is_readable($font_file)) {
567
            return PEAR::raiseError('Fontfile not found or not readable.');
568
        } else {
569
            $this->_font = $font_file;
570
        }
571
 
572
        // Is the font size to small?
573
        if ($this->options['width'] < 1) {
574
            return PEAR::raiseError('Width too small. Has to be > 1.');
575
        }
576
 
577
        // Check and create canvas
578
 
579
        switch (true) {
580
            case (empty($this->options['canvas'])):
581
 
582
                // Create new image from width && height of the clipping
583
                $this->_img = imagecreatetruecolor(
584
                            $this->options['width'], $this->options['height']);
585
                if (!$this->_img) {
586
                    return PEAR::raiseError('Could not create image canvas.');
587
                }
588
                break;
589
 
590
            case (is_resource($this->options['canvas']) &&
591
                    get_resource_type($this->options['canvas'])=='gd'):
592
                // The canvas is an image resource
593
                $this->_img = $this->options['canvas'];
594
                break;
595
 
596
            case (is_array($this->options['canvas']) &&
597
                    isset($this->options['canvas']['width']) &&
598
                    isset($this->options['canvas']['height'])):
599
 
600
                // Canvas must be a width and height measure
601
                $this->_img = imagecreatetruecolor(
602
                    $this->options['canvas']['width'],
603
                    $this->options['canvas']['height']
604
                );
605
                break;
606
 
607
 
608
            case (is_array($this->options['canvas']) &&
609
                    isset($this->options['canvas']['size']) &&
610
                    ($this->options['canvas']['size'] = 'auto')):
611
 
612
            case (is_string($this->options['canvas']) &&
613
                     ($this->options['canvas'] = 'auto')):
614
                $this->_mode = 'auto';
615
                break;
616
 
617
            default:
618
                return PEAR::raiseError('Could not create image canvas.');
619
 
620
        }
621
 
622
 
623
 
624
        if ($this->_img) {
625
            $this->options['canvas'] = array();
626
            $this->options['canvas']['width']  = imagesx($this->_img);
627
            $this->options['canvas']['height'] = imagesy($this->_img);
628
        }
629
 
630
        if ($this->options['enable_alpha']) {
631
            imagesavealpha($this->_img, true);
632
            imagealphablending($this->_img, false);
633
        }
634
 
635
        if ($this->options['background_color'] === null) {
636
            $this->options['enable_alpha'] = true;
637
            imagesavealpha($this->_img, true);
638
            imagealphablending($this->_img, false);
639
            $colBg = imagecolorallocatealpha($this->_img, 255, 255, 255, 127);
640
        } else {
641
            $arBg  = $this->_convertString2RGB($this->options['background_color']);
642
            if ($arBg === false) {
643
                return PEAR::raiseError('Background color is invalid.');
644
            }
645
            $colBg = imagecolorallocatealpha($this->_img, $arBg['r'], $arBg['g'], $arBg['b'], $arBg['a']);
646
        }
647
        imagefilledrectangle(
648
            $this->_img,
649
            0, 0,
650
            $this->options['canvas']['width'] - 1, $this->options['canvas']['height'] - 1,
651
            $colBg
652
        );
653
 
654
 
655
        // Save and repair angle
656
        $angle = $this->options['angle'];
657
        while ($angle < 0) {
658
            $angle += 360;
659
        }
660
        if ($angle > 359) {
661
            $angle = $angle % 360;
662
        }
663
        $this->options['angle'] = $angle;
664
 
665
        // Set the color values
666
        $res = $this->setColors($this->options['color']);
667
        if (PEAR::isError($res)) {
668
            return $res;
669
        }
670
 
671
        $this->_lines = null;
672
 
673
        // Initialization is complete
674
        $this->_init = true;
675
        return true;
676
    }
677
 
678
    /**
679
     * Auto measurize text
680
     *
681
     * Automatically determines the greatest possible font size to
682
     * fit the text into the text box. This method may be very resource
683
     * intensive on your webserver. A good tweaking point are the $start
684
     * and $end parameters, which specify the range of font sizes to search
685
     * through. Anyway, the results should be cached if possible. You can
686
     * optionally set $start and $end here as a parameter or the settings of
687
     * the options array are used.
688
     *
689
     * @access public
690
     * @param  int      $start  Fontsize to start testing with.
691
     * @param  int      $end    Fontsize to end testing with.
692
     * @return int              Fontsize measured or PEAR::Error.
693
     * @see Image_Text::measurize()
694
     */
695
 
696
    function autoMeasurize($start=false, $end=false)
697
    {
698
        if (!$this->_init) {
699
            return PEAR::raiseError('Not initialized. Call ->init() first!');
700
        }
701
 
702
        $start = (empty($start)) ? $this->options['min_font_size'] : $start;
703
        $end = (empty($end)) ? $this->options['max_font_size'] : $end;
704
 
705
        $res = false;
706
        // Run through all possible font sizes until a measurize fails
707
        // Not the optimal way. This can be tweaked!
708
        for ($i = $start; $i <= $end; $i++) {
709
            $this->options['font_size'] = $i;
710
            $res = $this->measurize();
711
 
712
            if ($res === false) {
713
                if ($start == $i) {
714
                    $this->options['font_size'] = -1;
715
                    return PEAR::raiseError("No possible font size found");
716
                }
717
                $this->options['font_size'] -= 1;
718
                $this->_measurizedSize = $this->options['font_size'];
719
                break;
720
            }
721
            // Always the last couple of lines is stored here.
722
            $this->_lines = $res;
723
        }
724
        return $this->options['font_size'];
725
    }
726
 
727
    /**
728
     * Measurize text into the text box
729
     *
730
     * This method makes your text fit into the defined textbox by measurizing the
731
     * lines for your given font-size. You can do this manually before rendering (or use
732
     * even Image_Text::autoMeasurize()) or the renderer will do measurizing
733
     * automatically.
734
     *
735
     * @access public
736
     * @param  bool  $force  Optionally, default is false, set true to force measurizing.
737
     * @return array         Array of measured lines or PEAR::Error.
738
     * @see Image_Text::autoMeasurize()
739
     */
740
 
741
    function measurize($force=false)
742
    {
743
        if (!$this->_init) {
744
            return PEAR::raiseError('Not initialized. Call ->init() first!');
745
        }
746
        $this->_processText();
747
 
748
        // Precaching options
749
        $font = $this->_font;
750
        $size = $this->options['font_size'];
751
 
752
        $line_spacing = $this->options['line_spacing'];
753
        $space = (1 + $this->options['line_spacing']) * $this->options['font_size'];
754
 
755
        $max_lines = (int)$this->options['max_lines'];
756
 
757
        if (($max_lines<1) && !$force) {
758
            return false;
759
        }
760
 
761
        $block_width = $this->options['width'];
762
        $block_height = $this->options['height'];
763
 
764
        $colors_cnt = sizeof($this->colors);
765
        $c = $this->colors[0];
766
 
767
        $text_line = '';
768
 
769
        $lines_cnt = 0;
770
        $tokens_cnt = sizeof($this->_tokens);
771
 
772
        $lines = array();
773
 
774
        $text_height = 0;
775
        $text_width = 0;
776
 
777
        $i = 0;
778
        $para_cnt = 0;
779
 
780
        $beginning_of_line = true;
781
 
782
        // Run through tokens and order them in lines
783
        foreach($this->_tokens as $token) {
784
            // Handle new paragraphs
785
            if ($token=="\n") {
786
                $bounds = imagettfbbox($size, 0, $font, $text_line);
787
                if ((++$lines_cnt>=$max_lines) && !$force) {
788
                    return false;
789
                }
790
                if ($this->options['color_mode']=='paragraph') {
791
                    $c = $this->colors[$para_cnt%$colors_cnt];
792
                    $i++;
793
                } else {
794
                    $c = $this->colors[$i++%$colors_cnt];
795
                }
796
                $lines[]  = array(
797
                                'string'        => $text_line,
798
                                'width'         => $bounds[2]-$bounds[0],
799
                                'height'        => $bounds[1]-$bounds[7],
800
                                'bottom_margin' => $bounds[1],
801
                                'left_margin'   => $bounds[0],
802
                                'color'         => $c
803
                            );
804
                $text_width = max($text_width, ($bounds[2]-$bounds[0]));
805
                $text_height += (int)$space;
806
                if (($text_height > $block_height) && !$force) {
807
                    return false;
808
                }
809
                $para_cnt++;
810
                $text_line = '';
811
                $beginning_of_line = true;
812
                continue;
813
            }
814
 
815
            // Usual lining up
816
 
817
            if ($beginning_of_line) {
818
                $text_line = '';
819
                $text_line_next = $token;
820
                $beginning_of_line = false;
821
            } else {
822
                $text_line_next = $text_line.' '.$token;
823
            }
824
            $bounds = imagettfbbox($size, 0, $font, $text_line_next);
825
            $prev_width = isset($prev_width)?$width:0;
826
            $width = $bounds[2]-$bounds[0];
827
 
828
            // Handling of automatic new lines
829
            if ($width>$block_width) {
830
                if ((++$lines_cnt>=$max_lines) && !$force) {
831
                    return false;
832
                }
833
                if ($this->options['color_mode']=='line') {
834
                    $c = $this->colors[$i++%$colors_cnt];
835
                } else {
836
                    $c = $this->colors[$para_cnt%$colors_cnt];
837
                    $i++;
838
                }
839
 
840
                $lines[]  = array(
841
                                'string'    => $text_line,
842
                                'width'     => $prev_width,
843
                                'height'    => $bounds[1]-$bounds[7],
844
                                'bottom_margin' => $bounds[1],
845
                                'left_margin'   => $bounds[0],
846
                                'color'         => $c
847
                            );
848
                $text_width = max($text_width, ($bounds[2]-$bounds[0]));
849
                $text_height += (int)$space;
850
                if (($text_height > $block_height) && !$force) {
851
                    return false;
852
                }
853
 
854
                $text_line      = $token;
855
                $bounds = imagettfbbox($size, 0, $font, $text_line);
856
                $width = $bounds[2]-$bounds[0];
857
                $beginning_of_line = false;
858
            } else {
859
                $text_line = $text_line_next;
860
            }
861
        }
862
        // Store remaining line
863
        $bounds = imagettfbbox($size, 0, $font,$text_line);
864
        if ($this->options['color_mode']=='line') {
865
            $c = $this->colors[$i++%$colors_cnt];
866
        } else {
867
            $c = $this->colors[$para_cnt%$colors_cnt];
868
            $i++;
869
        }
870
        $lines[]  = array(
871
                        'string'=> $text_line,
872
                        'width' => $bounds[2]-$bounds[0],
873
                        'height'=> $bounds[1]-$bounds[7],
874
                        'bottom_margin' => $bounds[1],
875
                        'left_margin'   => $bounds[0],
876
                        'color'         => $c
877
                    );
878
 
879
        // add last line height, but without the line-spacing
880
        $text_height += $this->options['font_size'];
881
 
882
        $text_width = max($text_width, ($bounds[2]-$bounds[0]));
883
 
884
        if (($text_height > $block_height) && !$force) {
885
            return false;
886
        }
887
 
888
        $this->_realTextSize = array('width' => $text_width, 'height' => $text_height);
889
        $this->_measurizedSize = $this->options['font_size'];
890
 
891
        return $lines;
892
    }
893
 
894
    /**
895
     * Render the text in the canvas using the given options.
896
     *
897
     * This renders the measurized text or automatically measures it first. The $force parameter
898
     * can be used to switch of measurizing problems (this may cause your text being rendered
899
     * outside a given text box or destroy your image completely).
900
     *
901
     * @access public
902
     * @param  bool     $force  Optional, initially false, set true to silence measurize errors.
903
     * @return bool             True on success, otherwise PEAR::Error.
904
     */
905
 
906
    function render($force=false)
907
    {
908
        if (!$this->_init) {
909
            return PEAR::raiseError('Not initialized. Call ->init() first!');
910
        }
911
 
912
        if (!$this->_tokens) {
913
            $this->_processText();
914
        }
915
 
916
        if (empty($this->_lines) || ($this->_measurizedSize != $this->options['font_size'])) {
917
            $this->_lines = $this->measurize( $force );
918
        }
919
        $lines = $this->_lines;
920
 
921
        if (PEAR::isError($this->_lines)) {
922
            return $this->_lines;
923
        }
924
 
925
        if ($this->_mode === 'auto') {
926
            $this->_img = imagecreatetruecolor(
927
                        $this->_realTextSize['width'],
928
                        $this->_realTextSize['height']
929
                    );
930
            if (!$this->_img) {
931
                return PEAR::raiseError('Could not create image cabvas.');
932
            }
933
            $this->_mode = '';
934
            $this->setColors($this->_options['color']);
935
        }
936
 
937
        $block_width = $this->options['width'];
938
        $block_height = $this->options['height'];
939
 
940
        $max_lines = $this->options['max_lines'];
941
 
942
        $angle = $this->options['angle'];
943
        $radians = round(deg2rad($angle), 3);
944
 
945
        $font = $this->_font;
946
        $size = $this->options['font_size'];
947
 
948
        $line_spacing = $this->options['line_spacing'];
949
 
950
        $align = $this->options['halign'];
951
 
952
        $im = $this->_img;
953
 
954
        $offset = $this->_getOffset();
955
 
956
        $start_x = $offset['x'];
957
        $start_y = $offset['y'];
958
 
959
        $end_x = $start_x + $block_width;
960
        $end_y = $start_y + $block_height;
961
 
962
        $sinR = sin($radians);
963
        $cosR = cos($radians);
964
 
965
        switch ($this->options['valign']) {
966
            case IMAGE_TEXT_ALIGN_TOP:
967
                $valign_space = 0;
968
                break;
969
            case IMAGE_TEXT_ALIGN_MIDDLE:
970
                $valign_space = ($this->options['height'] - $this->_realTextSize['height']) / 2;
971
                break;
972
            case IMAGE_TEXT_ALIGN_BOTTOM:
973
                $valign_space = $this->options['height'] - $this->_realTextSize['height'];
974
                break;
975
            default:
976
                $valign_space = 0;
977
        }
978
 
979
        $space = (1 + $line_spacing) * $size;
980
 
981
        // Adjustment of align + translation of top-left-corner to bottom-left-corner of first line
982
        $new_posx = $start_x + ($sinR * ($valign_space + $size));
983
        $new_posy = $start_y + ($cosR * ($valign_space + $size));
984
 
985
        $lines_cnt = min($max_lines,sizeof($lines));
986
 
987
        // Go thorugh lines for rendering
988
        for($i=0; $i<$lines_cnt; $i++){
989
 
990
            // Calc the new start X and Y (only for line>0)
991
            // the distance between the line above is used
992
            if($i > 0){
993
                $new_posx += $sinR * $space;
994
                $new_posy += $cosR * $space;
995
            }
996
 
997
            // Calc the position of the 1st letter. We can then get the left and bottom margins
998
            // 'i' is really not the same than 'j' or 'g'.
999
            $bottom_margin  = $lines[$i]['bottom_margin'];
1000
            $left_margin    = $lines[$i]['left_margin'];
1001
            $line_width     = $lines[$i]['width'];
1002
 
1003
            // Calc the position using the block width, the current line width and obviously
1004
            // the angle. That gives us the offset to slide the line.
1005
            switch($align) {
1006
                case IMAGE_TEXT_ALIGN_LEFT:
1007
                    $hyp = 0;
1008
                    break;
1009
                case IMAGE_TEXT_ALIGN_RIGHT:
1010
                    $hyp = $block_width - $line_width - $left_margin;
1011
                    break;
1012
                case IMAGE_TEXT_ALIGN_CENTER:
1013
                    $hyp = ($block_width-$line_width)/2 - $left_margin;
1014
                    break;
1015
                default:
1016
                    $hyp = 0;
1017
                    break;
1018
            }
1019
 
1020
            $posx = $new_posx + $cosR * $hyp;
1021
            $posy = $new_posy - $sinR * $hyp;
1022
 
1023
            $c = $lines[$i]['color'];
1024
 
1025
            // Render textline
1026
            $bboxes[] = imagettftext ($im, $size, $angle, $posx, $posy, $c, $font, $lines[$i]['string']);
1027
        }
1028
        $this->bbox = $bboxes;
1029
        return true;
1030
    }
1031
 
1032
    /**
1033
     * Return the image ressource.
1034
     *
1035
     * Get the image canvas.
1036
     *
1037
     * @access public
1038
     * @return resource Used image resource
1039
     */
1040
 
1041
    function &getImg()
1042
    {
1043
        return $this->_img;
1044
    }
1045
 
1046
    /**
1047
     * Display the image (send it to the browser).
1048
     *
1049
     * This will output the image to the users browser. You can use the standard IMAGETYPE_*
1050
     * constants to determine which image type will be generated. Optionally you can save your
1051
     * image to a destination you set in the options.
1052
     *
1053
     * @param   bool  $save  Save or not the image on printout.
1054
     * @param   bool  $free  Free the image on exit.
1055
     * @return  bool         True on success, otherwise PEAR::Error.
1056
     * @access public
1057
     * @see Image_Text::save()
1058
     */
1059
 
1060
    function display($save=false, $free=false)
1061
    {
1062
        if (!headers_sent()) {
1063
            header("Content-type: " .image_type_to_mime_type($this->options['image_type']));
1064
        } else {
1065
            PEAR::raiseError('Header already sent.');
1066
        }
1067
        switch ($this->options['image_type']) {
1068
            case IMAGETYPE_PNG:
1069
                $imgout = 'imagepng';
1070
                break;
1071
            case IMAGETYPE_JPEG:
1072
                $imgout = 'imagejpeg';
1073
                break;
1074
            case IMAGETYPE_BMP:
1075
                $imgout = 'imagebmp';
1076
                break;
1077
            default:
1078
                return PEAR::raiseError('Unsupported image type.');
1079
                break;
1080
        }
1081
        if ($save) {
1082
            $imgout($this->_img);
1083
            $res = $this->save();
1084
            if (PEAR::isError($res)) {
1085
                return $res;
1086
            }
1087
        } else {
1088
           $imgout($this->_img);
1089
        }
1090
 
1091
        if ($free) {
1092
            $res = imagedestroy($this->image);
1093
            if (!$res) {
1094
                PEAR::raiseError('Destroying image failed.');
1095
            }
1096
        }
1097
        return true;
1098
    }
1099
 
1100
    /**
1101
     * Save image canvas.
1102
     *
1103
     * Saves the image to a given destination. You can leave out the destination file path,
1104
     * if you have the option for that set correctly. Saving is possible with the save()
1105
     * method, too.
1106
     *
1107
     * @param   string  $destFile   The destination to save to (optional, uses options value else).
1108
     * @return  bool                True on success, otherwise PEAR::Error.
1109
     * @see Image_Text::display()
1110
     */
1111
 
1112
    function save($dest_file=false)
1113
    {
1114
        if (!$dest_file) {
1115
            $dest_file = $this->options['dest_file'];
1116
        }
1117
        if (!$dest_file) {
1118
            return PEAR::raiseError("Invalid desitination file.");
1119
        }
1120
 
1121
        switch ($this->options['image_type']) {
1122
            case IMAGETYPE_PNG:
1123
                $imgout = 'imagepng';
1124
                break;
1125
            case IMAGETYPE_JPEG:
1126
                $imgout = 'imagejpeg';
1127
                break;
1128
            case IMAGETYPE_BMP:
1129
                $imgout = 'imagebmp';
1130
                break;
1131
            default:
1132
                return PEAR::raiseError('Unsupported image type.');
1133
                break;
1134
        }
1135
 
1136
        $res = $imgout($this->_img, $dest_file);
1137
        if (!$res) {
1138
            PEAR::raiseError('Saving file failed.');
1139
        }
1140
        return true;
1141
    }
1142
 
1143
    /**
1144
     * Get completely translated offset for text rendering.
1145
     *
1146
     * Get completely translated offset for text rendering. Important
1147
     * for usage of center coords and angles
1148
     *
1149
     * @access private
1150
     * @return array    Array of x/y coordinates.
1151
     */
1152
 
1153
    function _getOffset()
1154
    {
1155
        // Presaving data
1156
        $width = $this->options['width'];
1157
        $height = $this->options['height'];
1158
        $angle = $this->options['angle'];
1159
        $x = $this->options['x'];
1160
        $y = $this->options['y'];
1161
        // Using center coordinates
1162
        if (!empty($this->options['cx']) && !empty($this->options['cy'])) {
1163
            $cx = $this->options['cx'];
1164
            $cy = $this->options['cy'];
1165
            // Calculation top left corner
1166
            $x = $cx - ($width / 2);
1167
            $y = $cy - ($height / 2);
1168
            // Calculating movement to keep the center point on himslf after rotation
1169
            if ($angle) {
1170
                $ang = deg2rad($angle);
1171
                // Vector from the top left cornern ponting to the middle point
1172
                $vA = array( ($cx - $x), ($cy - $y) );
1173
                // Matrix to rotate vector
1174
                // sinus and cosinus
1175
                $sin = round(sin($ang), 14);
1176
                $cos = round(cos($ang), 14);
1177
                // matrix
1178
                $mRot = array(
1179
                    $cos, (-$sin),
1180
                    $sin, $cos
1181
                );
1182
                // Multiply vector with matrix to get the rotated vector
1183
                // This results in the location of the center point after rotation
1184
                $vB = array (
1185
                    ($mRot[0] * $vA[0] + $mRot[2] * $vA[0]),
1186
                    ($mRot[1] * $vA[1] + $mRot[3] * $vA[1])
1187
                );
1188
                // To get the movement vector, we subtract the original middle
1189
                $vC = array (
1190
                    ($vA[0] - $vB[0]),
1191
                    ($vA[1] - $vB[1])
1192
                );
1193
                // Finally we move the top left corner coords there
1194
                $x += $vC[0];
1195
                $y += $vC[1];
1196
            }
1197
        }
1198
        return array ('x' => (int)round($x, 0), 'y' => (int)round($y, 0));
1199
    }
1200
 
1201
    /**
1202
     * Convert a color to an array.
1203
     *
1204
     * The following colors syntax must be used:
1205
     * "#08ffff00" hexadecimal format with alpha channel (08)
1206
     * array with 'r','g','b','a'(optionnal) keys
1207
     * A GD color special color (tiled,...)
1208
     * Only one color is allowed
1209
     * If $id is given, the color index $id is used
1210
     *
1211
     * @param   mixed  $colors       Array of colors.
1212
     * @param   mixed  $id           Array of colors.
1213
     * @access private
1214
     */
1215
    function _convertString2RGB($scolor)
1216
    {
1217
        if (preg_match(IMAGE_TEXT_REGEX_HTMLCOLOR, $scolor, $matches)) {
1218
            return array(
1219
                           'r' => hexdec($matches[2]),
1220
                           'g' => hexdec($matches[3]),
1221
                           'b' => hexdec($matches[4]),
1222
                           'a' => hexdec(!empty($matches[1])?$matches[1]:0),
1223
                           );
1224
        }
1225
        return false;
1226
    }
1227
 
1228
    /**
1229
     * Extract the tokens from the text.
1230
     *
1231
     * @access private
1232
     */
1233
    function _processText()
1234
    {
1235
        if (!isset($this->_text)) {
1236
            return false;
1237
        }
1238
        $this->_tokens = array();
1239
 
1240
        // Normalize linebreak to "\n"
1241
        $this->_text = preg_replace("[\r\n]", "\n", $this->_text);
1242
 
1243
        // Get each paragraph
1244
        $paras = explode("\n",$this->_text);
1245
 
1246
        // loop though the paragraphs
1247
        // and get each word (token)
1248
        foreach($paras as $para) {
1249
            $words = explode(' ',$para);
1250
            foreach($words as $word) {
1251
                $this->_tokens[] = $word;
1252
            }
1253
            // add a "\n" to mark the end of a paragraph
1254
            $this->_tokens[] = "\n";
1255
        }
1256
        // we do not need an end paragraph as the last token
1257
        array_pop($this->_tokens);
1258
    }
1259
}
1260
 
1261