<?php

require_once dirname(__FILE__) . '/../Image.php';
require_once dirname(__FILE__) . '/../XML/SVG.php';

/**
 * This class implements the Horde_Image:: API for the XML_SVG
 * extension. It mainly provides some utility functions, such as the
 * ability to make pixels and barcodes.
 *
 * $Horde: horde/lib/Image/svg.php,v 1.21 2003/07/28 20:18:48 chuck Exp $
 *
 * Copyright 2002-2003 Chuck Hagenbuch <chuck@horde.org>
 *
 * See the enclosed file COPYING for license information (LGPL). If you
 * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
 *
 * @author  Chuck Hagenbuch <chuck@horde.org>
 * @version $Revision: 1.21 $
 * @since   Horde 3.0
 * @package Horde_Image
 */
class Horde_Image_svg extends Horde_Image {

    var $_svg;

    /**
     * Capabilites of this driver.
     * @var array $_capabilities
     */
    var $_capabilities = array('canvas');

    function Horde_Image_svg($params)
    {
        $this->_width = $params['width'];
        $this->_height = $params['height'];
        $this->_svg = &new XML_SVG_Document(array('width' => $this->_width,
                                                  'height' => $this->_height));
    }

    function getContentType()
    {
        return 'image/svg+xml';
    }

    function getLink($url, $title = '')
    {
    }

    function display()
    {
        $this->_svg->printElement();
    }

    function raw()
    {
        return $this->_svg->bufferObject();
    }

    function getFont($font)
    {
        return $font;
    }

    function _createSymbol($s, $id)
    {
        $s->setParam('id', $id);
        $defs = &new XML_SVG_Defs();
        $defs->addChild($s);
        $this->_svg->addChild($defs);
    }

    function _createDropShadow($id = 'dropShadow')
    {
        $defs = &new XML_SVG_Defs();
        $filter = &new XML_SVG_Filter(array('id' => $id));
        $filter->addPrimitive('GaussianBlur', array('in' => 'SourceAlpha',
                                                    'stdDeviation' => 2,
                                                    'result' => 'blur'));
        $filter->addPrimitive('Offset', array('in' => 'blur',
                                              'dx' => 4,
                                              'dy' => 4,
                                              'result' => 'offsetBlur'));
        $merge = &new XML_SVG_FilterPrimitive('Merge');
        $merge->addMergeNode('offsetBlur');
        $merge->addMergeNode('SourceGraphic');

        $filter->addChild($merge);
        $defs->addChild($filter);
        $this->_svg->addChild($defs);
    }

    function barcode($text)
    {
        $barcodeheight = 40;
        $barcodethinwidth = 2;
        $barcodethickwidth = $barcodethinwidth * 3;
        $codingmap = array('0' => '000110100', '1' => '100100001',
                           '2' => '001100001', '3' => '101100000', '4' => '000110001',
                           '5' => '100110000', '6' => '001110000', '7' => '000100101',
                           '8' => '100100100', '9' => '001100100', 'A' => '100001001',
                           'B' => '001001001', 'C' => '101001000', 'D' => '000011001',
                           'E' => '100011000', 'F' => '001011000', 'G' => '000001101',
                           'H' => '100001100', 'I' => '001001100', 'J' => '000011100',
                           'K' => '100000011', 'L' => '001000011', 'M' => '101000010',
                           'N' => '000010011', 'O' => '100010010', 'P' => '001010010',
                           'Q' => '000000111', 'R' => '100000110', 'S' => '001000110',
                           'T' => '000010110', 'U' => '110000001', 'V' => '011000001',
                           'W' => '111000000', 'X' => '010010001', 'Y' => '110010000',
                           'Z' => '011010000', ' ' => '011000100', '$' => '010101000',
                           '%' => '000101010', '*' => '010010100', '+' => '010001010',
                           '-' => '010000101', '.' => '110000100', '/' => '010100010');
        $text = String::upper($text, true);

        // Add start/stop chars.
        $text = "*$text*";

        $textlen = strlen($text);
        $barcodewidth = ($textlen) * (7 * $barcodethinwidth
                                      + 3 * $barcodethickwidth) - $barcodethinwidth;

        $this->_svg = &new XML_SVG_Document(array('width' => $barcodewidth,
                                                  'height' => $barcodeheight));

        $this->_svg->addChild(new XML_SVG_Rect(array('x' => 0,
                                                     'y' => 0,
                                                     'width' => $barcodewidth,
                                                     'height' => $barcodeheight,
                                                     'style' => 'fill:white')));

        $xpos = 0;
        for ($idx = 0; $idx < $textlen; $idx++) {
            $char = substr($text, $idx, 1);
            // Make unknown chars a '-'.
            if (!isset($codingmap[$char])) {
                $char = '-';
            }
            for ($baridx = 0; $baridx <= 8; $baridx++) {
                $elementwidth = (substr($codingmap[$char], $baridx, 1)) ?
                    $barcodethickwidth : $barcodethinwidth;
                if (($baridx + 1) % 2) {
                    $this->_svg->addChild(new XML_SVG_Rect(array('x' => $xpos,
                                                                 'y' => 0,
                                                                 'width' => $elementwidth,
                                                                 'height' => $barcodeheight,
                                                                 'style' => 'fill:black')));
                }
                $xpos += $elementwidth;
            }
            $xpos += $barcodethinwidth;
        }

        return $this->_svg->printElement();
    }

    /**
     * Draws a text string on the image in a specified location, with
     * the specified style information.
     *
     * @param string  $text       The text to draw.
     * @param integer $x          The left x coordinate of the start of the text string.
     * @param integer $y          The top y coordinate of the start of the text string.
     * @param string  $font       The font identifier you want to use for the text.
     * @param string  $color      The color that you want the text displayed in.
     * @param integer $direction  An integer that specifies the orientation of the text.
     */
    function text($string, $x, $y, $font = 'monospace', $color = 'black', $direction = 0)
    {
        $height = 12;
        $style = 'font-family:' . $font . ';font-height:' . $height . 'px;fill:' . $this->getHexColor($color) . ';text-anchor:start;';
        $transform = 'rotate(' . $direction . ',' . $x . ',' . $y . ')';
        $this->_svg->addChild(new XML_SVG_Text(array('text' => $string,
                                                     'x' => (int)$x,
                                                     'y' => (int)$y + $height,
                                                     'transform' => $transform,
                                                     'style' => $style)));
    }

    /**
     * Draw a circle.
     *
     * @param integer $x      The x co-ordinate of the centre.
     * @param integer $y      The y co-ordinate of the centre.
     * @param integer $r      The radius of the circle.
     * @param string  $color  The line color of the circle.
     * @param string  $fill   (optional) The color to fill the circle.
     */
    function circle($x, $y, $r, $color, $fill = null)
    {
        if (!empty($fill)) {
            $style = 'fill:' . $this->getHexColor($fill) . '; ';
        } else {
            $style = 'fill:none;';
        }
        $style .= 'stroke:' . $this->getHexColor($color) . '; stroke-width:1';

        $this->_svg->addChild(new XML_SVG_Circle(array('cx' => $x,
                                                       'cy' => $y,
                                                       'r' => $r,
                                                       'style' => $style)));
    }

    /**
     * Draw a polygon based on a set of vertices.
     *
     * @param array   $vertices  An array of x and y labeled arrays
     *                           (eg. $vertices[0]['x'], $vertices[0]['y'], ...).
     * @param string  $color     The color you want to draw the polygon with.
     * @param string  $fill      (optional) The color to fill the polygon.
     */
    function polygon($verts, $color, $fill = null)
    {
        if (!empty($fill)) {
            $style = 'fill:' . $this->getHexColor($fill) . '; ';
        } else {
            $style = 'fill:none;';
        }
        $style .= 'stroke:' . $this->getHexColor($color) . '; stroke-width:1';

        $points = '';
        foreach ($verts as $v) {
            $points .= $v['x'] . ',' . $v['y'] . ' ';
        }
        $points = trim($points);

        $this->_svg->addChild(new XML_SVG_Polygon(array('points' => $points,
                                                        'style' => $style)));
    }

    /**
     * Draw a rectangle.
     *
     * @param integer $x       The left x-coordinate of the rectangle.
     * @param integer $y       The top y-coordinate of the rectangle.
     * @param integer $width   The width of the rectangle.
     * @param integer $height  The height of the rectangle.
     * @param string  $color   The line color of the rectangle.
     * @param string  $fill    (optional) The color to fill the rectangle.
     */
    function rectangle($x, $y, $width, $height, $color, $fill = null)
    {
        if (!empty($fill)) {
            $style = 'fill:' . $this->getHexColor($fill) . '; ';
        } else {
            $style = 'fill:none;';
        }
        $style .= 'stroke:' . $this->getHexColor($color) . '; stroke-width:1';

        $this->_svg->addChild(new XML_SVG_Rect(array('x' => $x,
                                                     'y' => $y,
                                                     'width' => $width,
                                                     'height' => $height,
                                                     'style' => $style)));
    }

    /**
     * Draw a line.
     *
     * @param integer $x0     The x co-ordinate of the start.
     * @param integer $y0     The y co-ordinate of the start.
     * @param integer $x1     The x co-ordinate of the end.
     * @param integer $y1     The y co-ordinate of the end.
     * @param string  $color  (optional) The line color.
     * @param string  $width  (optional) The width of the line.
     */
    function line($x1, $y1, $x2, $y2, $color = 'black', $width = 1)
    {
        $style = 'stroke:' . $this->getHexColor($color) . '; stroke-width:' . (int)$width;
        $this->_svg->addChild(new XML_SVG_Line(array('x1' => $x1,
                                                     'y1' => $y1,
                                                     'x2' => $x2,
                                                     'y2' => $y2,
                                                     'style' => $style)));
    }

    /**
     * Draw a dashed line.
     *
     * @param integer $x0           The x co-ordinate of the start.
     * @param integer $y0           The y co-ordinate of the start.
     * @param integer $x1           The x co-ordinate of the end.
     * @param integer $y1           The y co-ordinate of the end.
     * @param string  $color        (optional) The line color.
     * @param string  $width        (optional) The width of the line.
     * @param integer $dash_length  The length of a dash on the dashed line
     * @param integer $dash_space   The length of a space in the dashed line
     */
    function dashedLine($x1, $y1, $x2, $y2, $color = 'black', $width = 1, $dash_length = 2, $dash_space = 2)
    {
        $style = 'stroke:' . $this->getHexColor($color) . '; stroke-width:' . (int)$width . '; stroke-dasharray:' . $dash_length . ',' . $dash_space . ';';
        $this->_svg->addChild(new XML_SVG_Line(array('x1' => $x1,
                                                     'y1' => $y1,
                                                     'x2' => $x2,
                                                     'y2' => $y2,
                                                     'style' => $style)));
    }

    /**
     * Draw a polyline (a non-closed, non-filled polygon) based on a
     * set of vertices.
     *
     * @param array   $vertices  An array of x and y labeled arrays
     *                           (eg. $vertices[0]['x'], $vertices[0]['y'], ...).
     * @param string  $color     The color you want to draw the line with.
     * @param string  $width     (optional) The width of the line.
     */
    function polyline($verts, $color, $width = 1)
    {
        $style = 'stroke:' . $this->getHexColor($color) . '; stroke-width:' . $width . ';fill:none;';

        // Calculate the path entry.
        $path = '';

        $first = true;
        foreach ($verts as $vert) {
            if ($first) {
                $first = false;
                $path .= 'M ' . $vert['x'] . ',' . $vert['y'];
            } else {
                $path .= ' L ' . $vert['x'] . ',' . $vert['y'];
            }
        }

        $this->_svg->addChild(new XML_SVG_Path(array('d' => $path,
                                                     'style' => $style)));
    }

    /**
     * Draw an arc.
     *
     * @param integer $x      The x co-ordinate of the centre.
     * @param integer $y      The y co-ordinate of the centre.
     * @param integer $r      The radius of the arc.
     * @param integer $start  The start angle of the arc.
     * @param integer $end    The end angle of the arc.
     * @param string  $color  The line color of the arc.
     * @param string  $fill   The fill color of the arc (defaults to none).
     */
    function arc($x, $y, $r, $start, $end, $color = 'black', $fill = null)
    {
        if (!empty($fill)) {
            $style = 'fill:' . $this->getHexColor($fill) . '; ';
        } else {
            $style = 'fill:none;';
        }
        $style .= 'stroke:' . $this->getHexColor($color) . '; stroke-width:1';

        $mid = round(($start + $end) / 2);

        // Calculate the path entry.
        $path = '';

        // If filled, draw the outline.
        if (!empty($fill)) {
            // Start at the center of the ellipse the arc is on.
            $path .= "M $x,$y ";

            // Draw out to ellipse edge
            list($arcX, $arcY) = $this->getCirclePoint($start, $r * 2);
            $path .= 'L ' . round($x + $arcX) . ',' .
                round($y + $arcY) . ' ';
        }

        // Draw arcs.
        list($arcX, $arcY) = $this->getCirclePoint($mid, $r * 2);
        $path .= "A $r,$r 0 0 1 " .
            round($x + $arcX) . ',' .
            round($y + $arcY) . ' ';

        list($arcX, $arcY) = $this->getCirclePoint($end, $r * 2);
        $path .= "A $r,$r 0 0 1 " .
            round($x + $arcX) . ',' .
            round($y + $arcY) . ' ';

        // If filled, close the outline.
        if (!empty($fill)) {
            $path .= 'Z';
        }

        $path = trim($path);

        $this->_svg->addChild(new XML_SVG_Path(array('d' => $path,
                                                     'style' => $style)));
    }

}
