Image_Text
[ class tree: Image_Text ] [ index: Image_Text ] [ all elements ]

Source for file Text.php

Documentation is available at Text.php

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

Documentation generated on Wed, 07 Aug 2013 13:00:04 +0000 by phpDocumentor 1.4.3. PEAR Logo Copyright © PHP Group 2004.