<?php
/**
 * Copyright 2002-2017 Horde LLC (http://www.horde.org/)
 *
 * See the enclosed file LICENSE for license information (LGPL). If you
 * did not receive this file, see http://www.horde.org/licenses/lgpl21.
 *
 * @author    Chuck Hagenbuch <chuck@horde.org>
 * @category  Horde
 * @license   http://www.horde.org/licenses/lgpl21 LGPL-2.1
 * @package   Image
 */

/**
 * This class implements the Horde_Image:: API for SVG.
 *
 * @author    Chuck Hagenbuch <chuck@horde.org>
 * @category  Horde
 * @copyright 2002-2017 Horde LLC
 * @license   http://www.horde.org/licenses/lgpl21 LGPL-2.1
 * @package   Image
 *
 * Requires PEAR XML_SVG
 */
class Horde_Image_Svg extends Horde_Image_Base
{
    /**
     * SVG document handle.
     *
     * @var XML_SVG
     */
    protected $_svg;

    /**
     * Capabilites of this driver.
     *
     * @var array
     */
    protected $_capabilities = array(
        'canvas',
        'circle',
        'dashedLine',
        'line',
        'polygon',
        'polyline',
        'rectangle',
        'roundedRectangle',
        'text',
    );

    /**
     * Constructor.
     *
     * @see Horde_Image_Base::_construct
     */
    public function __construct($params, $context = array())
    {
        parent::__construct($params, $context);
        $this->_svg = new XML_SVG_Document(array(
            'width' => $this->_width,
            'height' => $this->_height,
        ));
        if ($this->_background != 'none') {
            $this->rectangle(
                0, 0,
                $this->_width, $this->_height,
                $this->_background, $this->_background
            );
        }
    }

    /**
     * Returns the MIME type for this image.
     *
     * @return string  The MIME type for this image.
     */
    public function getContentType()
    {
        return 'image/svg+xml';
    }

    /**
     * Displays the current image.
     */
    public function display()
    {
        $this->headers();
        $this->_svg->printElement();
    }

    /**
     * Return raw image data.
     *
     * @param boolean $convert  Unused in SVG driver.
     * @param array $options    Array of options:
     *                          NONE USED in SVG driver.
     *
     * @return string  The raw image data.
     */
    public function raw($convert = false, $options = array())
    {
        return $this->_svg->bufferObject();
    }

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

    private 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);
    }

    /**
     * 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.
     * @param string $fontsize    Size of the font (small, medium, large, giant)
     */
    public function text(
        $string, $x, $y, $font = 'monospace', $color = 'black', $direction = 0
    )
    {
        $height = 12;
        $style = 'font-family:' . $font . ';font-height:' . $height
            . 'px;fill:' . Horde_Image::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
        )));
    }

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

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

    /**
     * Draws 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     The color to fill the polygon.
     */
    public function polygon($verts, $color, $fill = null)
    {
        if (!empty($fill)) {
            $style = 'fill:' . Horde_Image::getHexColor($fill) . '; ';
        } else {
            $style = 'fill:none;';
        }
        $style .= 'stroke:' . Horde_Image::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
        )));
    }

    /**
     * Draws 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     The color to fill the rectangle.
     */
    public function rectangle($x, $y, $width, $height, $color, $fill = null)
    {
        if (!empty($fill) && $fill != 'none') {
            $style = 'fill:' . Horde_Image::getHexColor($fill) . '; ';
        } else {
            $style = 'fill:none;';
        }
        $style .= 'stroke:' . Horde_Image::getHexColor($color) . '; stroke-width:1';

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

    /**
     * Draws a rounded 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 integer $round   The width of the corner rounding.
     * @param string  $color   The line color of the rectangle.
     * @param string  $fill    The color to fill the rounded rectangle with.
     */
    public function roundedRectangle(
        $x, $y, $width, $height, $round, $color, $fill
    )
    {
        if (!empty($fill)) {
            $style = 'fill:' . Horde_Image::getHexColor($fill) . '; ';
        } else {
            $style = 'fill:none;';
        }
        $style .= 'stroke:' . Horde_Image::getHexColor($color) . '; stroke-width:1';

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

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

    /**
     * Draws a dashed line.
     *
     * @param integer $x0           The x coordinate of the start.
     * @param integer $y0           The y coordinate of the start.
     * @param integer $x1           The x coordinate of the end.
     * @param integer $y1           The y coordinate of the end.
     * @param string  $color        The line color.
     * @param string  $width        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
     */
    public function dashedLine(
        $x1, $y1, $x2, $y2, $color = 'black', $width = 1, $dash_length = 2,
        $dash_space = 2
    )
    {
        $style = 'stroke:' . Horde_Image::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
        )));
    }

    /**
     * Draws 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    The width of the line.
     */
    public function polyline($verts, $color, $width = 1)
    {
        $style = 'stroke:' . Horde_Image::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
        )));
    }

    /**
     * Draws an arc.
     *
     * @param integer $x      The x coordinate of the centre.
     * @param integer $y      The y coordinate 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).
     */
    public function arc(
        $x, $y, $r, $start, $end, $color = 'black', $fill = null
    )
    {
        if (!empty($fill)) {
            $style = 'fill:' . Horde_Image::getHexColor($fill) . '; ';
        } else {
            $style = 'fill:none;';
        }
        $style .= 'stroke:' . Horde_Image::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) = Horde_Image::circlePoint($start, $r * 2);
            $path .= 'L ' . round($x + $arcX) . ',' .
                round($y + $arcY) . ' ';
        }

        // Draw arcs.
        list($arcX, $arcY) = Horde_Image::circlePoint($mid, $r * 2);
        $path .= "A $r,$r 0 0 1 " .
            round($x + $arcX) . ',' .
            round($y + $arcY) . ' ';

        list($arcX, $arcY) = Horde_Image::circlePoint($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
        )));
    }

}