xref: /plugin/captcha/EasySVG.php (revision 186227361d0efe9f0e20bd52c87dfbf939efb53a)
108f248e4SAndreas Gohr<?php
2*18622736SAndreas Gohr
308f248e4SAndreas Gohr/**
408f248e4SAndreas Gohr * EasySVG - Generate SVG from PHP
508f248e4SAndreas Gohr * @author Simon Tarchichi <kartsims@gmail.com>
608f248e4SAndreas Gohr * @version 0.1b
708f248e4SAndreas Gohr *
808f248e4SAndreas Gohr * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform
908f248e4SAndreas Gohr * @see http://stackoverflow.com/questions/14684846/flattening-svg-matrix-transforms-in-inkscape
1008f248e4SAndreas Gohr * @see http://stackoverflow.com/questions/7742148/how-to-convert-text-to-svg-paths
1108f248e4SAndreas Gohr */
12*18622736SAndreas Gohrclass EasySVG
13*18622736SAndreas Gohr{
1408f248e4SAndreas Gohr
1508f248e4SAndreas Gohr    protected $font;
1608f248e4SAndreas Gohr    protected $svg;
1708f248e4SAndreas Gohr
18*18622736SAndreas Gohr    public function __construct()
19*18622736SAndreas Gohr    {
2008f248e4SAndreas Gohr        // default font data
2108f248e4SAndreas Gohr        $this->font = new stdClass;
2208f248e4SAndreas Gohr        $this->font->id = '';
2308f248e4SAndreas Gohr        $this->font->horizAdvX = 0;
2408f248e4SAndreas Gohr        $this->font->unitsPerEm = 0;
2508f248e4SAndreas Gohr        $this->font->ascent = 0;
2608f248e4SAndreas Gohr        $this->font->descent = 0;
2708f248e4SAndreas Gohr        $this->font->glyphs = array();
2808f248e4SAndreas Gohr        $this->font->size = 20;
2908f248e4SAndreas Gohr        $this->font->color = '';
3008f248e4SAndreas Gohr        $this->font->lineHeight = 1;
3108f248e4SAndreas Gohr        $this->font->letterSpacing = 0;
3208f248e4SAndreas Gohr
3308f248e4SAndreas Gohr        $this->clearSVG();
3408f248e4SAndreas Gohr    }
3508f248e4SAndreas Gohr
36*18622736SAndreas Gohr    public function clearSVG()
37*18622736SAndreas Gohr    {
3808f248e4SAndreas Gohr        $this->svg = new SimpleXMLElement('<svg></svg>');
3908f248e4SAndreas Gohr    }
4008f248e4SAndreas Gohr
4108f248e4SAndreas Gohr    /**
4208f248e4SAndreas Gohr     * Function takes UTF-8 encoded string and returns unicode number for every character.
4308f248e4SAndreas Gohr     * @param string $str
4408f248e4SAndreas Gohr     * @return string
4508f248e4SAndreas Gohr     */
46*18622736SAndreas Gohr    private function _utf8ToUnicode($str)
47*18622736SAndreas Gohr    {
4808f248e4SAndreas Gohr        $unicode = array();
4908f248e4SAndreas Gohr        $values = array();
5008f248e4SAndreas Gohr        $lookingFor = 1;
5108f248e4SAndreas Gohr
5208f248e4SAndreas Gohr        for ($i = 0; $i < strlen($str); $i++) {
5308f248e4SAndreas Gohr            $thisValue = ord($str[$i]);
54*18622736SAndreas Gohr            if ($thisValue < 128) {
55*18622736SAndreas Gohr                $unicode[] = $thisValue;
56*18622736SAndreas Gohr            } else {
5708f248e4SAndreas Gohr                if (count($values) == 0) $lookingFor = ($thisValue < 224) ? 2 : 3;
5808f248e4SAndreas Gohr                $values[] = $thisValue;
5908f248e4SAndreas Gohr                if (count($values) == $lookingFor) {
6008f248e4SAndreas Gohr                    $number = ($lookingFor == 3) ?
6108f248e4SAndreas Gohr                        (($values[0] % 16) * 4096) + (($values[1] % 64) * 64) + ($values[2] % 64) :
6208f248e4SAndreas Gohr                        (($values[0] % 32) * 64) + ($values[1] % 64);
6308f248e4SAndreas Gohr
6408f248e4SAndreas Gohr                    $unicode[] = $number;
6508f248e4SAndreas Gohr                    $values = array();
6608f248e4SAndreas Gohr                    $lookingFor = 1;
6708f248e4SAndreas Gohr                }
6808f248e4SAndreas Gohr            }
6908f248e4SAndreas Gohr        }
7008f248e4SAndreas Gohr
7108f248e4SAndreas Gohr        return $unicode;
7208f248e4SAndreas Gohr    }
7308f248e4SAndreas Gohr
7408f248e4SAndreas Gohr    /**
7508f248e4SAndreas Gohr     * Set font params (short-hand method)
7608f248e4SAndreas Gohr     * @param string $filepath
7708f248e4SAndreas Gohr     * @param integer $size
7808f248e4SAndreas Gohr     * @param string $color
7908f248e4SAndreas Gohr     */
80*18622736SAndreas Gohr    public function setFont($filepath, $size, $color)
81*18622736SAndreas Gohr    {
8208f248e4SAndreas Gohr        $this->setFontSVG($filepath);
8308f248e4SAndreas Gohr        $this->setFontSize($size);
8408f248e4SAndreas Gohr        $this->setFontColor($color);
8508f248e4SAndreas Gohr    }
8608f248e4SAndreas Gohr
8708f248e4SAndreas Gohr    /**
8808f248e4SAndreas Gohr     * Set font size for display
8908f248e4SAndreas Gohr     * @param int $size
9008f248e4SAndreas Gohr     * @return void
9108f248e4SAndreas Gohr     */
92*18622736SAndreas Gohr    public function setFontSize($size)
93*18622736SAndreas Gohr    {
9408f248e4SAndreas Gohr        $this->font->size = $size;
9508f248e4SAndreas Gohr    }
9608f248e4SAndreas Gohr
9708f248e4SAndreas Gohr    /**
9808f248e4SAndreas Gohr     * Set font color
9908f248e4SAndreas Gohr     * @param string $color
10008f248e4SAndreas Gohr     * @return void
10108f248e4SAndreas Gohr     */
102*18622736SAndreas Gohr    public function setFontColor($color)
103*18622736SAndreas Gohr    {
10408f248e4SAndreas Gohr        $this->font->color = $color;
10508f248e4SAndreas Gohr    }
10608f248e4SAndreas Gohr
10708f248e4SAndreas Gohr    /**
10808f248e4SAndreas Gohr     * Set the line height from default (1) to custom value
10908f248e4SAndreas Gohr     * @param float $value
11008f248e4SAndreas Gohr     * @return void
11108f248e4SAndreas Gohr     */
112*18622736SAndreas Gohr    public function setLineHeight($value)
113*18622736SAndreas Gohr    {
11408f248e4SAndreas Gohr        $this->font->lineHeight = $value;
11508f248e4SAndreas Gohr    }
11608f248e4SAndreas Gohr
11708f248e4SAndreas Gohr    /**
11808f248e4SAndreas Gohr     * Set the letter spacing from default (0) to custom value
11908f248e4SAndreas Gohr     * @param float $value
12008f248e4SAndreas Gohr     * @return void
12108f248e4SAndreas Gohr     */
122*18622736SAndreas Gohr    public function setLetterSpacing($value)
123*18622736SAndreas Gohr    {
12408f248e4SAndreas Gohr        $this->font->letterSpacing = $value;
12508f248e4SAndreas Gohr    }
12608f248e4SAndreas Gohr
12708f248e4SAndreas Gohr    /**
12808f248e4SAndreas Gohr     * Function takes path to SVG font (local path) and processes its xml
12908f248e4SAndreas Gohr     * to get path representation of every character and additional
13008f248e4SAndreas Gohr     * font parameters
13108f248e4SAndreas Gohr     * @param string $filepath
13208f248e4SAndreas Gohr     * @return void
13308f248e4SAndreas Gohr     */
134*18622736SAndreas Gohr    public function setFontSVG($filepath)
135*18622736SAndreas Gohr    {
13608f248e4SAndreas Gohr        $this->font->glyphs = array();
13708f248e4SAndreas Gohr        $z = new XMLReader;
13808f248e4SAndreas Gohr        $z->open($filepath);
13908f248e4SAndreas Gohr
14008f248e4SAndreas Gohr        // move to the first <product /> node
14108f248e4SAndreas Gohr        while ($z->read()) {
14208f248e4SAndreas Gohr            $name = $z->name;
14308f248e4SAndreas Gohr
14408f248e4SAndreas Gohr            if ($z->nodeType == XMLReader::ELEMENT) {
14508f248e4SAndreas Gohr                if ($name == 'font') {
14608f248e4SAndreas Gohr                    $this->font->id = $z->getAttribute('id');
14708f248e4SAndreas Gohr                    $this->font->horizAdvX = $z->getAttribute('horiz-adv-x');
14808f248e4SAndreas Gohr                }
14908f248e4SAndreas Gohr
15008f248e4SAndreas Gohr                if ($name == 'font-face') {
15108f248e4SAndreas Gohr                    $this->font->unitsPerEm = $z->getAttribute('units-per-em');
15208f248e4SAndreas Gohr                    $this->font->ascent = $z->getAttribute('ascent');
15308f248e4SAndreas Gohr                    $this->font->descent = $z->getAttribute('descent');
15408f248e4SAndreas Gohr                }
15508f248e4SAndreas Gohr
15608f248e4SAndreas Gohr                if ($name == 'glyph') {
15708f248e4SAndreas Gohr                    $unicode = $z->getAttribute('unicode');
15808f248e4SAndreas Gohr                    $unicode = $this->_utf8ToUnicode($unicode);
15908f248e4SAndreas Gohr
16008f248e4SAndreas Gohr                    if (isset($unicode[0])) {
16108f248e4SAndreas Gohr                        $unicode = $unicode[0];
16208f248e4SAndreas Gohr
16308f248e4SAndreas Gohr                        $this->font->glyphs[$unicode] = new stdClass();
16408f248e4SAndreas Gohr                        $this->font->glyphs[$unicode]->horizAdvX = $z->getAttribute('horiz-adv-x');
16508f248e4SAndreas Gohr                        if (empty($this->font->glyphs[$unicode]->horizAdvX)) {
16608f248e4SAndreas Gohr                            $this->font->glyphs[$unicode]->horizAdvX = $this->font->horizAdvX;
16708f248e4SAndreas Gohr                        }
16808f248e4SAndreas Gohr                        $this->font->glyphs[$unicode]->d = $z->getAttribute('d');
16908f248e4SAndreas Gohr
17008f248e4SAndreas Gohr                        // save em value for letter spacing (109 is unicode for the letter 'm')
17108f248e4SAndreas Gohr                        if ($unicode == '109') {
17208f248e4SAndreas Gohr                            $this->font->em = $this->font->glyphs[$unicode]->horizAdvX;
17308f248e4SAndreas Gohr                        }
17408f248e4SAndreas Gohr                    }
17508f248e4SAndreas Gohr                }
17608f248e4SAndreas Gohr            }
17708f248e4SAndreas Gohr        }
17808f248e4SAndreas Gohr    }
17908f248e4SAndreas Gohr
18008f248e4SAndreas Gohr    /**
18108f248e4SAndreas Gohr     * Add a path to the SVG
18208f248e4SAndreas Gohr     * @param string $def
18308f248e4SAndreas Gohr     * @param array $attributes
18408f248e4SAndreas Gohr     * @return SimpleXMLElement
18508f248e4SAndreas Gohr     */
186*18622736SAndreas Gohr    public function addPath($def, $attributes = array())
187*18622736SAndreas Gohr    {
18808f248e4SAndreas Gohr        $path = $this->svg->addChild('path');
18908f248e4SAndreas Gohr        foreach ($attributes as $key => $value) {
19008f248e4SAndreas Gohr            $path->addAttribute($key, $value);
19108f248e4SAndreas Gohr        }
19208f248e4SAndreas Gohr        $path->addAttribute('d', $def);
19308f248e4SAndreas Gohr        return $path;
19408f248e4SAndreas Gohr    }
19508f248e4SAndreas Gohr
19608f248e4SAndreas Gohr    /**
19708f248e4SAndreas Gohr     * Add a text to the SVG
19808f248e4SAndreas Gohr     * @param string $def
19908f248e4SAndreas Gohr     * @param float $x
20008f248e4SAndreas Gohr     * @param float $y
20108f248e4SAndreas Gohr     * @param array $attributes
20208f248e4SAndreas Gohr     * @return SimpleXMLElement
20308f248e4SAndreas Gohr     */
204*18622736SAndreas Gohr    public function addText($text, $x = 0, $y = 0, $attributes = array())
205*18622736SAndreas Gohr    {
20608f248e4SAndreas Gohr        $def = $this->textDef($text);
20708f248e4SAndreas Gohr
20808f248e4SAndreas Gohr        if ($x != 0 || $y != 0) {
20908f248e4SAndreas Gohr            $def = $this->defTranslate($def, $x, $y);
21008f248e4SAndreas Gohr        }
21108f248e4SAndreas Gohr
21208f248e4SAndreas Gohr        if ($this->font->color) {
21308f248e4SAndreas Gohr            $attributes['fill'] = $this->font->color;
21408f248e4SAndreas Gohr        }
21508f248e4SAndreas Gohr
21608f248e4SAndreas Gohr        return $this->addPath($def, $attributes);
21708f248e4SAndreas Gohr    }
21808f248e4SAndreas Gohr
21908f248e4SAndreas Gohr    /**
22008f248e4SAndreas Gohr     * Function takes UTF-8 encoded string and size, returns xml for SVG paths representing this string.
22108f248e4SAndreas Gohr     * @param string $text UTF-8 encoded text
22208f248e4SAndreas Gohr     * @return string xml for text converted into SVG paths
22308f248e4SAndreas Gohr     */
224*18622736SAndreas Gohr    public function textDef($text)
225*18622736SAndreas Gohr    {
22608f248e4SAndreas Gohr        $def = array();
22708f248e4SAndreas Gohr
22808f248e4SAndreas Gohr        $horizAdvX = 0;
22908f248e4SAndreas Gohr        $horizAdvY = $this->font->ascent + $this->font->descent;
23008f248e4SAndreas Gohr        $fontSize = floatval($this->font->size) / $this->font->unitsPerEm;
23108f248e4SAndreas Gohr        $text = $this->_utf8ToUnicode($text);
23208f248e4SAndreas Gohr
23308f248e4SAndreas Gohr        for ($i = 0; $i < count($text); $i++) {
23408f248e4SAndreas Gohr
23508f248e4SAndreas Gohr            $letter = $text[$i];
23608f248e4SAndreas Gohr
23708f248e4SAndreas Gohr            // line break support (10 is unicode for linebreak)
23808f248e4SAndreas Gohr            if ($letter == 10) {
23908f248e4SAndreas Gohr                $horizAdvX = 0;
24008f248e4SAndreas Gohr                $horizAdvY += $this->font->lineHeight * ($this->font->ascent + $this->font->descent);
24108f248e4SAndreas Gohr                continue;
24208f248e4SAndreas Gohr            }
24308f248e4SAndreas Gohr
24408f248e4SAndreas Gohr            // extract character definition
24508f248e4SAndreas Gohr            $d = $this->font->glyphs[$letter]->d;
24608f248e4SAndreas Gohr
24708f248e4SAndreas Gohr            // transform typo from original SVG format to straight display
24808f248e4SAndreas Gohr            $d = $this->defScale($d, $fontSize, -$fontSize);
24908f248e4SAndreas Gohr            $d = $this->defTranslate($d, $horizAdvX, $horizAdvY * $fontSize * 2);
25008f248e4SAndreas Gohr
25108f248e4SAndreas Gohr            $def[] = $d;
25208f248e4SAndreas Gohr
25308f248e4SAndreas Gohr            // next letter's position
25408f248e4SAndreas Gohr            $horizAdvX += $this->font->glyphs[$letter]->horizAdvX * $fontSize + $this->font->em * $this->font->letterSpacing * $fontSize;
25508f248e4SAndreas Gohr        }
25608f248e4SAndreas Gohr        return implode(' ', $def);
25708f248e4SAndreas Gohr    }
25808f248e4SAndreas Gohr
25908f248e4SAndreas Gohr    /**
26008f248e4SAndreas Gohr     * Function takes UTF-8 encoded string and size, returns width and height of the whole text
26108f248e4SAndreas Gohr     * @param string $text UTF-8 encoded text
26208f248e4SAndreas Gohr     * @return array ($width, $height)
26308f248e4SAndreas Gohr     */
264*18622736SAndreas Gohr    public function textDimensions($text)
265*18622736SAndreas Gohr    {
26608f248e4SAndreas Gohr        $def = array();
26708f248e4SAndreas Gohr
26808f248e4SAndreas Gohr        $fontSize = floatval($this->font->size) / $this->font->unitsPerEm;
26908f248e4SAndreas Gohr        $text = $this->_utf8ToUnicode($text);
27008f248e4SAndreas Gohr
27108f248e4SAndreas Gohr        $lineWidth = 0;
27208f248e4SAndreas Gohr        $lineHeight = ($this->font->ascent + $this->font->descent) * $fontSize * 2;
27308f248e4SAndreas Gohr
27408f248e4SAndreas Gohr        $width = 0;
27508f248e4SAndreas Gohr        $height = $lineHeight;
27608f248e4SAndreas Gohr
27708f248e4SAndreas Gohr        for ($i = 0; $i < count($text); $i++) {
27808f248e4SAndreas Gohr
27908f248e4SAndreas Gohr            $letter = $text[$i];
28008f248e4SAndreas Gohr
28108f248e4SAndreas Gohr            // line break support (10 is unicode for linebreak)
28208f248e4SAndreas Gohr            if ($letter == 10) {
28308f248e4SAndreas Gohr                $width = $lineWidth > $width ? $lineWidth : $width;
28408f248e4SAndreas Gohr                $height += $lineHeight * $this->font->lineHeight;
28508f248e4SAndreas Gohr                $lineWidth = 0;
28608f248e4SAndreas Gohr                continue;
28708f248e4SAndreas Gohr            }
28808f248e4SAndreas Gohr
28908f248e4SAndreas Gohr            $lineWidth += $this->font->glyphs[$letter]->horizAdvX * $fontSize + $this->font->em * $this->font->letterSpacing * $fontSize;
29008f248e4SAndreas Gohr        }
29108f248e4SAndreas Gohr
29208f248e4SAndreas Gohr        // only keep the widest line's width
29308f248e4SAndreas Gohr        $width = $lineWidth > $width ? $lineWidth : $width;
29408f248e4SAndreas Gohr
29508f248e4SAndreas Gohr        return array($width, $height);
29608f248e4SAndreas Gohr    }
29708f248e4SAndreas Gohr
29808f248e4SAndreas Gohr    /**
29908f248e4SAndreas Gohr     * Function takes unicode character and returns the UTF-8 equivalent
30008f248e4SAndreas Gohr     * @param string $str
30108f248e4SAndreas Gohr     * @return string
30208f248e4SAndreas Gohr     */
303*18622736SAndreas Gohr    public function unicodeDef($unicode)
304*18622736SAndreas Gohr    {
30508f248e4SAndreas Gohr
30608f248e4SAndreas Gohr        $horizAdvY = $this->font->ascent + $this->font->descent;
30708f248e4SAndreas Gohr        $fontSize = floatval($this->font->size) / $this->font->unitsPerEm;
30808f248e4SAndreas Gohr
30908f248e4SAndreas Gohr        // extract character definition
31008f248e4SAndreas Gohr        $d = $this->font->glyphs[hexdec($unicode)]->d;
31108f248e4SAndreas Gohr
31208f248e4SAndreas Gohr        // transform typo from original SVG format to straight display
31308f248e4SAndreas Gohr        $d = $this->defScale($d, $fontSize, -$fontSize);
31408f248e4SAndreas Gohr        $d = $this->defTranslate($d, 0, $horizAdvY * $fontSize * 2);
31508f248e4SAndreas Gohr
31608f248e4SAndreas Gohr        return $d;
31708f248e4SAndreas Gohr    }
31808f248e4SAndreas Gohr
31908f248e4SAndreas Gohr    /**
32008f248e4SAndreas Gohr     * Returns the character width, as set in the font file
32108f248e4SAndreas Gohr     * @param string $str
32208f248e4SAndreas Gohr     * @param boolean $is_unicode
32308f248e4SAndreas Gohr     * @return float
32408f248e4SAndreas Gohr     */
325*18622736SAndreas Gohr    public function characterWidth($char, $is_unicode = false)
326*18622736SAndreas Gohr    {
32708f248e4SAndreas Gohr        if ($is_unicode) {
32808f248e4SAndreas Gohr            $letter = hexdec($char);
329*18622736SAndreas Gohr        } else {
33008f248e4SAndreas Gohr            $letter = $this->_utf8ToUnicode($char);
33108f248e4SAndreas Gohr        }
33208f248e4SAndreas Gohr
333*18622736SAndreas Gohr        if (!isset($this->font->glyphs[$letter])) {
334*18622736SAndreas Gohr            return null;
335*18622736SAndreas Gohr        }
33608f248e4SAndreas Gohr
33708f248e4SAndreas Gohr        $fontSize = floatval($this->font->size) / $this->font->unitsPerEm;
33808f248e4SAndreas Gohr        return $this->font->glyphs[$letter]->horizAdvX * $fontSize;
33908f248e4SAndreas Gohr    }
34008f248e4SAndreas Gohr
34108f248e4SAndreas Gohr    /**
34208f248e4SAndreas Gohr     * Applies a translate transformation to definition
34308f248e4SAndreas Gohr     * @param string $def definition
34408f248e4SAndreas Gohr     * @param float $x
34508f248e4SAndreas Gohr     * @param float $y
34608f248e4SAndreas Gohr     * @return string
34708f248e4SAndreas Gohr     */
348*18622736SAndreas Gohr    public function defTranslate($def, $x = 0, $y = 0)
349*18622736SAndreas Gohr    {
35008f248e4SAndreas Gohr        return $this->defApplyMatrix($def, array(1, 0, 0, 1, $x, $y));
35108f248e4SAndreas Gohr    }
35208f248e4SAndreas Gohr
35308f248e4SAndreas Gohr    /**
35408f248e4SAndreas Gohr     * Applies a translate transformation to definition
35508f248e4SAndreas Gohr     * @param string $def Definition
35608f248e4SAndreas Gohr     * @param integer $angle Rotation angle (degrees)
35708f248e4SAndreas Gohr     * @param integer $x X coordinate of rotation center
35808f248e4SAndreas Gohr     * @param integer $y Y coordinate of rotation center
35908f248e4SAndreas Gohr     * @return string
36008f248e4SAndreas Gohr     */
361*18622736SAndreas Gohr    public function defRotate($def, $angle, $x = 0, $y = 0)
362*18622736SAndreas Gohr    {
36308f248e4SAndreas Gohr        if ($x == 0 && $y == 0) {
36408f248e4SAndreas Gohr            $angle = deg2rad($angle);
36508f248e4SAndreas Gohr            return $this->defApplyMatrix($def, array(cos($angle), sin($angle), -sin($angle), cos($angle), 0, 0));
36608f248e4SAndreas Gohr        }
36708f248e4SAndreas Gohr
36808f248e4SAndreas Gohr        // rotate by a given point
36908f248e4SAndreas Gohr        $def = $this->defTranslate($def, $x, $y);
37008f248e4SAndreas Gohr        $def = $this->defRotate($def, $angle);
37108f248e4SAndreas Gohr        $def = $this->defTranslate($def, -$x, -$y);
37208f248e4SAndreas Gohr        return $def;
37308f248e4SAndreas Gohr    }
37408f248e4SAndreas Gohr
37508f248e4SAndreas Gohr    /**
37608f248e4SAndreas Gohr     * Applies a scale transformation to definition
37708f248e4SAndreas Gohr     * @param string $def definition
37808f248e4SAndreas Gohr     * @param integer $x
37908f248e4SAndreas Gohr     * @param integer $y
38008f248e4SAndreas Gohr     * @return string
38108f248e4SAndreas Gohr     */
382*18622736SAndreas Gohr    public function defScale($def, $x = 1, $y = 1)
383*18622736SAndreas Gohr    {
38408f248e4SAndreas Gohr        return $this->defApplyMatrix($def, array($x, 0, 0, $y, 0, 0));
38508f248e4SAndreas Gohr    }
38608f248e4SAndreas Gohr
38708f248e4SAndreas Gohr    /**
38808f248e4SAndreas Gohr     * Calculates the new definition with the matrix applied
38908f248e4SAndreas Gohr     * @param string $def
39008f248e4SAndreas Gohr     * @param array $matrix
39108f248e4SAndreas Gohr     * @return string
39208f248e4SAndreas Gohr     */
393*18622736SAndreas Gohr    public function defApplyMatrix($def, $matrix)
394*18622736SAndreas Gohr    {
39508f248e4SAndreas Gohr
39608f248e4SAndreas Gohr        // if there are several shapes in this definition, do the operation for each
39708f248e4SAndreas Gohr        preg_match_all('/M[^zZ]*[zZ]/', $def, $shapes);
39808f248e4SAndreas Gohr        $shapes = $shapes[0];
39908f248e4SAndreas Gohr        if (count($shapes) > 1) {
400*18622736SAndreas Gohr            foreach ($shapes as &$shape) {
40108f248e4SAndreas Gohr                $shape = $this->defApplyMatrix($shape, $matrix);
402*18622736SAndreas Gohr            }
40308f248e4SAndreas Gohr            return implode(' ', $shapes);
40408f248e4SAndreas Gohr        }
40508f248e4SAndreas Gohr
40608f248e4SAndreas Gohr        preg_match_all('/[a-zA-Z]+[^a-zA-Z]*/', $def, $instructions);
40708f248e4SAndreas Gohr        $instructions = $instructions[0];
40808f248e4SAndreas Gohr
40908f248e4SAndreas Gohr        $return = '';
41008f248e4SAndreas Gohr        foreach ($instructions as &$instruction) {
41108f248e4SAndreas Gohr            $i = preg_replace('/[^a-zA-Z]*/', '', $instruction);
41208f248e4SAndreas Gohr            preg_match_all('/\-?[0-9\.]+/', $instruction, $coords);
41308f248e4SAndreas Gohr            $coords = $coords[0];
41408f248e4SAndreas Gohr
41508f248e4SAndreas Gohr            if (empty($coords)) {
41608f248e4SAndreas Gohr                continue;
41708f248e4SAndreas Gohr            }
41808f248e4SAndreas Gohr
41908f248e4SAndreas Gohr            $new_coords = array();
42008f248e4SAndreas Gohr            while (count($coords) > 0) {
42108f248e4SAndreas Gohr
42208f248e4SAndreas Gohr                // do the matrix calculation stuff
42308f248e4SAndreas Gohr                list($a, $b, $c, $d, $e, $f) = $matrix;
42408f248e4SAndreas Gohr
42508f248e4SAndreas Gohr                // exception for relative instruction
42608f248e4SAndreas Gohr                if (preg_match('/[a-z]/', $i)) {
42708f248e4SAndreas Gohr                    $e = 0;
42808f248e4SAndreas Gohr                    $f = 0;
42908f248e4SAndreas Gohr                }
43008f248e4SAndreas Gohr
43108f248e4SAndreas Gohr                // convert horizontal lineto (relative)
43208f248e4SAndreas Gohr                if ($i == 'h') {
43308f248e4SAndreas Gohr                    $i = 'l';
43408f248e4SAndreas Gohr                    $x = floatval(array_shift($coords));
43508f248e4SAndreas Gohr                    $y = 0;
43608f248e4SAndreas Gohr
43708f248e4SAndreas Gohr                    // add new point's coordinates
43808f248e4SAndreas Gohr                    $current_point = array(
43908f248e4SAndreas Gohr                        $a * $x + $c * $y + $e,
44008f248e4SAndreas Gohr                        $b * $x + $d * $y + $f,
44108f248e4SAndreas Gohr                    );
44208f248e4SAndreas Gohr                    $new_coords = array_merge($new_coords, $current_point);
443*18622736SAndreas Gohr                } // convert vertical lineto (relative)
44408f248e4SAndreas Gohr                elseif ($i == 'v') {
44508f248e4SAndreas Gohr                    $i = 'l';
44608f248e4SAndreas Gohr                    $x = 0;
44708f248e4SAndreas Gohr                    $y = floatval(array_shift($coords));
44808f248e4SAndreas Gohr
44908f248e4SAndreas Gohr                    // add new point's coordinates
45008f248e4SAndreas Gohr                    $current_point = array(
45108f248e4SAndreas Gohr                        $a * $x + $c * $y + $e,
45208f248e4SAndreas Gohr                        $b * $x + $d * $y + $f,
45308f248e4SAndreas Gohr                    );
45408f248e4SAndreas Gohr                    $new_coords = array_merge($new_coords, $current_point);
455*18622736SAndreas Gohr                } // convert quadratic bezier curve (relative)
45608f248e4SAndreas Gohr                elseif ($i == 'q') {
45708f248e4SAndreas Gohr                    $x = floatval(array_shift($coords));
45808f248e4SAndreas Gohr                    $y = floatval(array_shift($coords));
45908f248e4SAndreas Gohr
46008f248e4SAndreas Gohr                    // add new point's coordinates
46108f248e4SAndreas Gohr                    $current_point = array(
46208f248e4SAndreas Gohr                        $a * $x + $c * $y + $e,
46308f248e4SAndreas Gohr                        $b * $x + $d * $y + $f,
46408f248e4SAndreas Gohr                    );
46508f248e4SAndreas Gohr                    $new_coords = array_merge($new_coords, $current_point);
46608f248e4SAndreas Gohr
46708f248e4SAndreas Gohr                    // same for 2nd point
46808f248e4SAndreas Gohr                    $x = floatval(array_shift($coords));
46908f248e4SAndreas Gohr                    $y = floatval(array_shift($coords));
47008f248e4SAndreas Gohr
47108f248e4SAndreas Gohr                    // add new point's coordinates
47208f248e4SAndreas Gohr                    $current_point = array(
47308f248e4SAndreas Gohr                        $a * $x + $c * $y + $e,
47408f248e4SAndreas Gohr                        $b * $x + $d * $y + $f,
47508f248e4SAndreas Gohr                    );
47608f248e4SAndreas Gohr                    $new_coords = array_merge($new_coords, $current_point);
47708f248e4SAndreas Gohr                }
47808f248e4SAndreas Gohr
47908f248e4SAndreas Gohr                // every other commands
48008f248e4SAndreas Gohr                // @TODO: handle 'a,c,s' (elliptic arc curve) commands
48108f248e4SAndreas Gohr                // cf. http://www.w3.org/TR/SVG/paths.html#PathDataCurveCommands
48208f248e4SAndreas Gohr                else {
48308f248e4SAndreas Gohr                    $x = floatval(array_shift($coords));
48408f248e4SAndreas Gohr                    $y = floatval(array_shift($coords));
48508f248e4SAndreas Gohr
48608f248e4SAndreas Gohr                    // add new point's coordinates
48708f248e4SAndreas Gohr                    $current_point = array(
48808f248e4SAndreas Gohr                        $a * $x + $c * $y + $e,
48908f248e4SAndreas Gohr                        $b * $x + $d * $y + $f,
49008f248e4SAndreas Gohr                    );
49108f248e4SAndreas Gohr                    $new_coords = array_merge($new_coords, $current_point);
49208f248e4SAndreas Gohr                }
49308f248e4SAndreas Gohr
49408f248e4SAndreas Gohr            }
49508f248e4SAndreas Gohr
49608f248e4SAndreas Gohr            $instruction = $i . implode(',', $new_coords);
49708f248e4SAndreas Gohr
49808f248e4SAndreas Gohr            // remove useless commas
49908f248e4SAndreas Gohr            $instruction = preg_replace('/,\-/', '-', $instruction);
50008f248e4SAndreas Gohr        }
50108f248e4SAndreas Gohr
50208f248e4SAndreas Gohr        return implode('', $instructions);
50308f248e4SAndreas Gohr    }
50408f248e4SAndreas Gohr
50508f248e4SAndreas Gohr
50608f248e4SAndreas Gohr
50708f248e4SAndreas Gohr    /**
50808f248e4SAndreas Gohr     *
50908f248e4SAndreas Gohr     * Short-hand methods
51008f248e4SAndreas Gohr     *
51108f248e4SAndreas Gohr     */
51208f248e4SAndreas Gohr
51308f248e4SAndreas Gohr    /**
51408f248e4SAndreas Gohr     * Return full SVG XML
51508f248e4SAndreas Gohr     * @return string
51608f248e4SAndreas Gohr     */
517*18622736SAndreas Gohr    public function asXML()
518*18622736SAndreas Gohr    {
51908f248e4SAndreas Gohr        return $this->svg->asXML();
52008f248e4SAndreas Gohr    }
52108f248e4SAndreas Gohr
52208f248e4SAndreas Gohr    /**
52308f248e4SAndreas Gohr     * Adds an attribute to the SVG
52408f248e4SAndreas Gohr     * @param string $key
52508f248e4SAndreas Gohr     * @param string $value
52608f248e4SAndreas Gohr     */
527*18622736SAndreas Gohr    public function addAttribute($key, $value)
528*18622736SAndreas Gohr    {
52908f248e4SAndreas Gohr        return $this->svg->addAttribute($key, $value);
53008f248e4SAndreas Gohr    }
53108f248e4SAndreas Gohr}
532