<?php

/* (c) Serge Emond, http://greyworld.net/
 * 
 * Simple test script for my patch to GD & PHP allowing
 * access to more of freetype2's properties
 * 
 * see http://greyworld.net/software/php-gd-freetype2/
 * 
 * $Id: generate.php,v 1.3 2008/01/25 07:06:51 greyl Exp $
 */

if (!extension_loaded('gd'))
    dl('gd.so');

/** Simple object to render text in an image using GD.
 */
class Generate
{
    var $o = array(
                   /** Output filename (always PNG) */
                   'out' => './out.png',
                   /** Text to render (UTF-8) */
                   'text' => false,
                   /** Font file */
                   'font' => false,
                   /** Color (R, G, B): int, 0-255 */
                   'color' => array(0,0,0),
                   /** Background color (R, G, B, Alpha)
                    * R/G/B:  int = 0-255
                    * Alpha:  int = 0-127 (0: opaque, 127: transparent) */
                   'bg' => array(255,255,255,127),
                   
                   /** Size in points */
                   'size' => 10,
                   /** Dots per inch (DPI) */
                   'res' => 96,
                   
                   'angle' => 0,       /**< Angle in degrees */
                   'slant' => 0.0,     /**< Slant in degrees */
                   'wm' => 1.0,        /**< Width Multiplier */
                   'hm' => 1.0,        /**< Height Multiplier */
                   'am' => 1.0,        /**< Advance Multiplier */
                   'uline' => 0,       /**< Bool: show underline or not */
                   );

    /** getopt() */
    var $shortOpts = "t:f:s:r:o:x:SHBaImlAh";
    
    /** extrainfo to getfttext */
    var $ft_opts = array('hdpi' => 96,
                         'vdpi' => 96,);
    /** Bounding box width */
    var $boxW = 0;
    /** Bounding box height */
    var $boxH = 0;
    /** Text X pos */
    var $txtX = 0;
    /** Text Y pos */
    var $txtY = 0;
    
    /** Entry point */
    function main()
    {
        // Parse options
        $this->getOpts();

        // Evaluate image dimensions & calculate bounding box
        $this->evalBitmap();

        // Render text and save to file
        $this->draw();
    } // main()
    
    /** Show usage information */
    function usage()
    {
        $a = $_SERVER['argv'];
        echo "{$a[0]} <options>\n".
            " Options (* means requires)\n".
            "  -h                  This help\n".
            "\n".
            "   -t \"txt\"        *Text to generate\n".
            "   -o name            Out file (png, default: out.png)\n".
            "   -f fnt.ttf        *Font file\n".
            "   -s size            Font size (default = 10)\n".
            "   -r dpi             DPI to use\n".
            "\n".
            "   -S                 load_no_scale\n".
            "   -H                 load_no_hinting\n".
            "   -B                 load_no_bitmap\n".
            "   -a                 load_force_autohint\n".
            "   -I                 load_load_ignore_transform\n".
            "   -m                 load_monochrome\n".
            "   -l                 load_linear_design\n".
            "   -A                 load_no_autohint\n".
            "   -x slant,wm,hm,am  Various transformations:\n".
            "      slant:            Slant Angle, degrees (def: 0.0)\n".
            "      wm:               Width Multiplier (def: 1.0)\n".
            "      hm:               Height Multiplier (def: 1.0)\n".
            "      am:               Advance Multiplier (def: 1.0)\n".
            "";
    } // usage()
    
    /** Parse options */
    function getOpts()
    {
        $options = getopt($this->shortOpts);

        $missing = array('t' => 'Text to render required',
                         'f' => 'Font file required',
                         );

        $fto = array('S' => 'load_no_scale',
                     'H' => 'load_no_hinting',
                     'B' => 'load_no_bitmap',
                     'a' => 'load_force_autohint',
                     'I' => 'load_load_ignore_transform',
                     'm' => 'load_monochrome',
                     'l' => 'load_linear_design',
                     'A' => 'load_no_autohint',
                     );

        foreach ($options as $k => $v) {
            switch ($k) {
             case 'h':
                $this->usage();
                exit(0);
             case 't':
                $this->o['text'] = $v;
                unset($missing['t']);
                break;
             case 'f':
                $this->o['font'] = $v;
                unset($missing['f']);
                break;
             case 'o':
                $this->o['out'] = $v;
                break;
             case 's':
                $this->o['size'] = (int)$v;
                break;
             case 'r':
                $this->o['res'] = (int)$v;
                $this->ft_opts['hdpi'] = (int)$v;
                $this->ft_opts['vdpi'] = (int)$v;
                break;

             case 'S':
             case 'H':
             case 'B':
             case 'a':
             case 'I':
             case 'm':
             case 'l':
             case 'A':
                $this->ft_opts[$fto[$k]] = 1;
                break;

             case 'x':
                $tmp = explode(',', $v);
                if (isset($tmp[0]) && $tmp[0] != '') {
                    $this->o['slant'] = $tmp[0];
                    $this->ft_opts['slant_angle'] = $tmp[0] * M_PI / 180.0;
                }
                if (isset($tmp[1]) && $tmp[1] != '') {
                    $this->o['wm'] = $tmp[1];
                    $this->ft_opts['width_mult'] = $tmp[1];
                }
                if (isset($tmp[2]) && $tmp[2] != '') {
                    $this->o['hm'] = $tmp[2];
                    $this->ft_opts['height_mult'] = $tmp[2];
                }
                if (isset($tmp[3]) && $tmp[3] != '') {
                    $this->o['am'] = $tmp[3];
                    $this->ft_opts['advance_mult'] = $tmp[3];
                }

                break;
            }
        }

        if (count($missing) > 0) {
            foreach ($missing as $k => $v) {
                echo "Missing argument '$k':  $v\n";
            }
            $this->usage();
            exit(-1);
        }
    } // getOpts()
    
    /** Calculate bounding boxes and stuff */
    function evalBitmap()
    {
        /* Notes on bounding boxes
         *
         * TL                                                         TR
         * +----------------------------------------------------------+
         * |     |                                                    |
         * |     |                                                    |
         * |     | (0,0)                                              |
         * |     |/                                                   |
         * |-----+----------------------------------------------------|
         * |     |                                                    |
         * |     |                                                    |
         * +----------------------------------------------------------+
         * BL                                                         BR
         *
         *                                  a=0     Descrition si a != 0
         * 0: xMin cos(a) - yMin sin(a)     xMin    x du coin BL
         * 1: xMin sin(a) - yMin cos(a)    -yMin    y min (y de BL)
         * 2: xMax cos(a) - yMin sin(a)     xMax    x max (x de BR)
         * 3: xMax sin(a) - yMin cos(a)    -yMin    y de BR
         * 4: xMax cos(a) - yMax sin(a)     xMax    x de TR
         * 5: xMax sin(a) - yMax cos(a)    -yMax    y max (y de TR)
         * 6: xMin cos(a) - yMax sin(a)     xMin    x min (x de TL)
         * 7: xMin sin(a) - yMax cos(a)    -yMax    y de TL
         *
         * C'est tout fucké.. alors utilisons nos propres calculs.
         */
        $s = Imageftbbox($this->o['size'], 0, $this->o['font'], $this->o['text'], $this->ft_opts);

        $width = abs($s[2] - $s[6]);
        $height = abs($s[1] - $s[5]);

        // Convert size of fonts from points to pixels
        $pixelSize = (float)($this->o['size'] * $this->ft_opts['vdpi']) / 72.0;

        // x: vers la droite
        // y: vers le bas
        // (0,0) = baseline, coin gauche
        $dims = array('tl' => array($s[0], $s[5]),
                      'tr' => array($s[2], $s[5]),
                      'br' => array($s[2], $s[1]),
                      'bl' => array($s[0], $s[1])
                      );

        // Underline stuff..
        #if ($this->o['uline']) {
        #  $ulineThickness = (int)abs( $this->o['hm'] * $this->fnt['fntUlineThick'] * $pixelSize / (float)$this->fnt['fntEmSize']);
        #  $ulinePosition = (int)(-1 * ( $this->o['hm'] * $this->fnt['fntUlinePos'] * $pixelSize / (float)$this->fnt['fntEmSize'] + $ulineThickness/2) + .5);
        #  if ($ulineThickness < 1) $ulineThickness = 1;
        #  $ulineLeft = $s[0] - 1.5*abs($ulineThickness);
        #  $ulineRight = $s[2] + 1.5*abs($ulineThickness);
        #  $dims['uposl'] = array($ulineLeft, $ulinePosition);
        #  $dims['uposr'] = array($ulineRight, $ulinePosition);
        #}

        // On applique une rotation de "a"
        $a = $this->o['angle'] * M_PI / 180.0;
        $cosa = cos($a);
        $sina = sin($a);

        $dimsp = array();
        $xMin = $xMax = $yMin = $yMax = false;
        $all_x = $all_y = array();
        foreach ($dims as $tag => $c) {
            $dimsp[$tag] = array($c[1]*$sina + $this->o['am']*$this->o['wm']*$c[0]*$cosa,
                                 $c[1]*$cosa - $this->o['am']*$this->o['wm']*$c[0]*$sina);
            $all_x[] = $dimsp[$tag][0];
            $all_y[] = $dimsp[$tag][1];
        }
        $bbox = array(min($all_x), max($all_x), min($all_y), max($all_y));

        #$bbox = array(min($s[0], $s[2], $s[4], $s[6]),
        #              max($s[0], $s[2], $s[4], $s[6]),
        #              min($s[1], $s[3], $s[5], $s[7]),
        #              max($s[1], $s[3], $s[5], $s[7])
        #              );

        $width = abs($bbox[1] - $bbox[0]);
        $height = abs($bbox[3] - $bbox[2]);

        $base = array(-$bbox[0], -$bbox[2]);

        // Padding
        #$width += 6; $height += 6;
        #$base[0] += 3; $base[1] += 3;

        #echo "bbox:<br><pre>";print_r($bbox);exit;
        #echo "Orig: <br><pre>"; print_r($dims) ; echo "</pre><br>Rot of $a rads:<br><pre>";print_r($dimsp);echo "</pre>";exit;

        // Padding: pour l'instant, on fixe à 3 pixels
        $this->boxW = 10 + $width;
        $this->boxH = 10 + $height;
        $this->txtX = 5 + $base[0];
        $this->txtY = 5 + $base[1];

        #if ($this->o['uline']) {
        #  $this->ulinePos = array($dimsp['uposl'], $dimsp['uposr']);
        #  $this->ulineThick = $ulineThickness;
        #}

        #$this->txtX = 3;
        #$this->txtY = 3 + $height;

        #echo "({$this->boxW} x {$this->boxH}) :: ({$this->txtX},{$this->txtY})<br>\n";
        #exit;

        return true;
    } // evalBitmap()
    
    /** Render text and save file */
    function draw()
    {
        $this->img = imagecreatetruecolor($this->boxW, $this->boxH);
        imagealphablending($this->img, false);

        $bgcolor = imagecolorallocatealpha($this->img,
                                           $this->o['bg'][0],
                                           $this->o['bg'][1],
                                           $this->o['bg'][2],
                                           $this->o['bg'][3]);
        $fgcolor = imagecolorallocatealpha($this->img,
                                           $this->o['color'][0],
                                           $this->o['color'][1],
                                           $this->o['color'][2],
                                           0); // 100% opaque

        imagefilledrectangle($this->img, 0, 0, $this->boxW-1, $this->boxH-1, $bgcolor);
        imagealphablending($this->img, true);

        // Underline
        #if ($this->o['uline']) {
        #    // Force 1 px de thickness :|
        #    //imageantialias($this->img, !isset($this->ft_opts['monochrome']));
        #    imagesetthickness($this->img, $this->ulineThick);
        #    imageline($this->img,
        #            $this->ulinePos[0][0] + $this->txtX, $this->ulinePos[0][1] + $this->txtY,
        #            $this->ulinePos[1][0] + $this->txtX, $this->ulinePos[1][1] + $this->txtY,
        #            $fgcolor);
        #}

        #echo "<pre>";print_r($this->ulinePos);echo "\n\n</pre>thick: {$this->ulineThick}<br>\n";exit;

        ImageFtText($this->img, $this->o['size'], $this->o['angle'],
                    $this->txtX, $this->txtY,
                    $fgcolor,
                    $this->o['font'],
                    $this->o['text'],
                    $this->ft_opts);

        #$xcolor = imagecolorallocate($this->img, 255, 0, 0);
        #imagesetpixel($this->img, $this->txtX, $this->txtY, $xcolor);

        imagesavealpha($this->img, true);

        imagepng($this->img, $this->o['out']);

        return true;
    } // draw()

} // Generate

$g = new Generate();
$g->main();
