xref: /plugin/captcha/EasySVG.php (revision 08f248e40c0e155880defbf4b95d145cf9080d3c)
1*08f248e4SAndreas Gohr<?php
2*08f248e4SAndreas Gohr/**
3*08f248e4SAndreas Gohr * EasySVG - Generate SVG from PHP
4*08f248e4SAndreas Gohr * @author Simon Tarchichi <kartsims@gmail.com>
5*08f248e4SAndreas Gohr * @version 0.1b
6*08f248e4SAndreas Gohr *
7*08f248e4SAndreas Gohr * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/transform
8*08f248e4SAndreas Gohr * @see http://stackoverflow.com/questions/14684846/flattening-svg-matrix-transforms-in-inkscape
9*08f248e4SAndreas Gohr * @see http://stackoverflow.com/questions/7742148/how-to-convert-text-to-svg-paths
10*08f248e4SAndreas Gohr */
11*08f248e4SAndreas Gohrclass EasySVG {
12*08f248e4SAndreas Gohr
13*08f248e4SAndreas Gohr    protected $font;
14*08f248e4SAndreas Gohr    protected $svg;
15*08f248e4SAndreas Gohr
16*08f248e4SAndreas Gohr    public function __construct() {
17*08f248e4SAndreas Gohr        // default font data
18*08f248e4SAndreas Gohr        $this->font = new stdClass;
19*08f248e4SAndreas Gohr        $this->font->id = '';
20*08f248e4SAndreas Gohr        $this->font->horizAdvX = 0;
21*08f248e4SAndreas Gohr        $this->font->unitsPerEm = 0;
22*08f248e4SAndreas Gohr        $this->font->ascent = 0;
23*08f248e4SAndreas Gohr        $this->font->descent = 0;
24*08f248e4SAndreas Gohr        $this->font->glyphs = array();
25*08f248e4SAndreas Gohr        $this->font->size = 20;
26*08f248e4SAndreas Gohr        $this->font->color = '';
27*08f248e4SAndreas Gohr        $this->font->lineHeight = 1;
28*08f248e4SAndreas Gohr        $this->font->letterSpacing = 0;
29*08f248e4SAndreas Gohr
30*08f248e4SAndreas Gohr        $this->clearSVG();
31*08f248e4SAndreas Gohr    }
32*08f248e4SAndreas Gohr
33*08f248e4SAndreas Gohr    public function clearSVG() {
34*08f248e4SAndreas Gohr        $this->svg = new SimpleXMLElement('<svg></svg>');
35*08f248e4SAndreas Gohr    }
36*08f248e4SAndreas Gohr
37*08f248e4SAndreas Gohr    /**
38*08f248e4SAndreas Gohr     * Function takes UTF-8 encoded string and returns unicode number for every character.
39*08f248e4SAndreas Gohr     * @param  string $str
40*08f248e4SAndreas Gohr     * @return string
41*08f248e4SAndreas Gohr     */
42*08f248e4SAndreas Gohr    private function _utf8ToUnicode( $str ) {
43*08f248e4SAndreas Gohr        $unicode = array();
44*08f248e4SAndreas Gohr        $values = array();
45*08f248e4SAndreas Gohr        $lookingFor = 1;
46*08f248e4SAndreas Gohr
47*08f248e4SAndreas Gohr        for ($i = 0; $i < strlen( $str ); $i++ ) {
48*08f248e4SAndreas Gohr            $thisValue = ord( $str[ $i ] );
49*08f248e4SAndreas Gohr            if ( $thisValue < 128 ) $unicode[] = $thisValue;
50*08f248e4SAndreas Gohr            else {
51*08f248e4SAndreas Gohr                if ( count( $values ) == 0 ) $lookingFor = ( $thisValue < 224 ) ? 2 : 3;
52*08f248e4SAndreas Gohr                $values[] = $thisValue;
53*08f248e4SAndreas Gohr                if ( count( $values ) == $lookingFor ) {
54*08f248e4SAndreas Gohr                    $number = ( $lookingFor == 3 ) ?
55*08f248e4SAndreas Gohr                        ( ( $values[0] % 16 ) * 4096 ) + ( ( $values[1] % 64 ) * 64 ) + ( $values[2] % 64 ):
56*08f248e4SAndreas Gohr                        ( ( $values[0] % 32 ) * 64 ) + ( $values[1] % 64 );
57*08f248e4SAndreas Gohr
58*08f248e4SAndreas Gohr                    $unicode[] = $number;
59*08f248e4SAndreas Gohr                    $values = array();
60*08f248e4SAndreas Gohr                    $lookingFor = 1;
61*08f248e4SAndreas Gohr                }
62*08f248e4SAndreas Gohr            }
63*08f248e4SAndreas Gohr        }
64*08f248e4SAndreas Gohr
65*08f248e4SAndreas Gohr        return $unicode;
66*08f248e4SAndreas Gohr    }
67*08f248e4SAndreas Gohr
68*08f248e4SAndreas Gohr    /**
69*08f248e4SAndreas Gohr     * Set font params (short-hand method)
70*08f248e4SAndreas Gohr     * @param string $filepath
71*08f248e4SAndreas Gohr     * @param integer $size
72*08f248e4SAndreas Gohr     * @param string $color
73*08f248e4SAndreas Gohr     */
74*08f248e4SAndreas Gohr    public function setFont( $filepath, $size, $color ) {
75*08f248e4SAndreas Gohr        $this->setFontSVG($filepath);
76*08f248e4SAndreas Gohr        $this->setFontSize($size);
77*08f248e4SAndreas Gohr        $this->setFontColor($color);
78*08f248e4SAndreas Gohr    }
79*08f248e4SAndreas Gohr
80*08f248e4SAndreas Gohr    /**
81*08f248e4SAndreas Gohr     * Set font size for display
82*08f248e4SAndreas Gohr     * @param int $size
83*08f248e4SAndreas Gohr     * @return void
84*08f248e4SAndreas Gohr     */
85*08f248e4SAndreas Gohr    public function setFontSize( $size ) {
86*08f248e4SAndreas Gohr        $this->font->size = $size;
87*08f248e4SAndreas Gohr    }
88*08f248e4SAndreas Gohr
89*08f248e4SAndreas Gohr    /**
90*08f248e4SAndreas Gohr     * Set font color
91*08f248e4SAndreas Gohr     * @param string $color
92*08f248e4SAndreas Gohr     * @return void
93*08f248e4SAndreas Gohr     */
94*08f248e4SAndreas Gohr    public function setFontColor( $color ) {
95*08f248e4SAndreas Gohr        $this->font->color = $color;
96*08f248e4SAndreas Gohr    }
97*08f248e4SAndreas Gohr
98*08f248e4SAndreas Gohr    /**
99*08f248e4SAndreas Gohr     * Set the line height from default (1) to custom value
100*08f248e4SAndreas Gohr     * @param  float $value
101*08f248e4SAndreas Gohr     * @return void
102*08f248e4SAndreas Gohr     */
103*08f248e4SAndreas Gohr    public function setLineHeight( $value ) {
104*08f248e4SAndreas Gohr        $this->font->lineHeight = $value;
105*08f248e4SAndreas Gohr    }
106*08f248e4SAndreas Gohr
107*08f248e4SAndreas Gohr    /**
108*08f248e4SAndreas Gohr     * Set the letter spacing from default (0) to custom value
109*08f248e4SAndreas Gohr     * @param  float $value
110*08f248e4SAndreas Gohr     * @return void
111*08f248e4SAndreas Gohr     */
112*08f248e4SAndreas Gohr    public function setLetterSpacing( $value ) {
113*08f248e4SAndreas Gohr        $this->font->letterSpacing = $value;
114*08f248e4SAndreas Gohr    }
115*08f248e4SAndreas Gohr
116*08f248e4SAndreas Gohr    /**
117*08f248e4SAndreas Gohr     * Function takes path to SVG font (local path) and processes its xml
118*08f248e4SAndreas Gohr     * to get path representation of every character and additional
119*08f248e4SAndreas Gohr     * font parameters
120*08f248e4SAndreas Gohr     * @param  string $filepath
121*08f248e4SAndreas Gohr     * @return void
122*08f248e4SAndreas Gohr     */
123*08f248e4SAndreas Gohr    public function setFontSVG( $filepath ) {
124*08f248e4SAndreas Gohr        $this->font->glyphs = array();
125*08f248e4SAndreas Gohr        $z = new XMLReader;
126*08f248e4SAndreas Gohr        $z->open($filepath);
127*08f248e4SAndreas Gohr
128*08f248e4SAndreas Gohr        // move to the first <product /> node
129*08f248e4SAndreas Gohr        while ($z->read()) {
130*08f248e4SAndreas Gohr            $name = $z->name;
131*08f248e4SAndreas Gohr
132*08f248e4SAndreas Gohr            if ($z->nodeType == XMLReader::ELEMENT) {
133*08f248e4SAndreas Gohr                if ($name == 'font') {
134*08f248e4SAndreas Gohr                    $this->font->id = $z->getAttribute('id');
135*08f248e4SAndreas Gohr                    $this->font->horizAdvX = $z->getAttribute('horiz-adv-x');
136*08f248e4SAndreas Gohr                }
137*08f248e4SAndreas Gohr
138*08f248e4SAndreas Gohr                if ($name == 'font-face') {
139*08f248e4SAndreas Gohr                    $this->font->unitsPerEm = $z->getAttribute('units-per-em');
140*08f248e4SAndreas Gohr                    $this->font->ascent = $z->getAttribute('ascent');
141*08f248e4SAndreas Gohr                    $this->font->descent = $z->getAttribute('descent');
142*08f248e4SAndreas Gohr                }
143*08f248e4SAndreas Gohr
144*08f248e4SAndreas Gohr                if ($name == 'glyph') {
145*08f248e4SAndreas Gohr                    $unicode = $z->getAttribute('unicode');
146*08f248e4SAndreas Gohr                    $unicode = $this->_utf8ToUnicode($unicode);
147*08f248e4SAndreas Gohr
148*08f248e4SAndreas Gohr                    if (isset($unicode[0])) {
149*08f248e4SAndreas Gohr                        $unicode = $unicode[0];
150*08f248e4SAndreas Gohr
151*08f248e4SAndreas Gohr                        $this->font->glyphs[$unicode] = new stdClass();
152*08f248e4SAndreas Gohr                        $this->font->glyphs[$unicode]->horizAdvX = $z->getAttribute('horiz-adv-x');
153*08f248e4SAndreas Gohr                        if (empty($this->font->glyphs[$unicode]->horizAdvX)) {
154*08f248e4SAndreas Gohr                            $this->font->glyphs[$unicode]->horizAdvX = $this->font->horizAdvX;
155*08f248e4SAndreas Gohr                        }
156*08f248e4SAndreas Gohr                        $this->font->glyphs[$unicode]->d = $z->getAttribute('d');
157*08f248e4SAndreas Gohr
158*08f248e4SAndreas Gohr                        // save em value for letter spacing (109 is unicode for the letter 'm')
159*08f248e4SAndreas Gohr                        if ($unicode == '109') {
160*08f248e4SAndreas Gohr                            $this->font->em = $this->font->glyphs[$unicode]->horizAdvX;
161*08f248e4SAndreas Gohr                        }
162*08f248e4SAndreas Gohr                    }
163*08f248e4SAndreas Gohr                }
164*08f248e4SAndreas Gohr            }
165*08f248e4SAndreas Gohr        }
166*08f248e4SAndreas Gohr    }
167*08f248e4SAndreas Gohr
168*08f248e4SAndreas Gohr    /**
169*08f248e4SAndreas Gohr     * Add a path to the SVG
170*08f248e4SAndreas Gohr     * @param string $def
171*08f248e4SAndreas Gohr     * @param array $attributes
172*08f248e4SAndreas Gohr     * @return SimpleXMLElement
173*08f248e4SAndreas Gohr     */
174*08f248e4SAndreas Gohr    public function addPath($def, $attributes=array()) {
175*08f248e4SAndreas Gohr        $path = $this->svg->addChild('path');
176*08f248e4SAndreas Gohr        foreach($attributes as $key=>$value){
177*08f248e4SAndreas Gohr            $path->addAttribute($key, $value);
178*08f248e4SAndreas Gohr        }
179*08f248e4SAndreas Gohr        $path->addAttribute('d', $def);
180*08f248e4SAndreas Gohr        return $path;
181*08f248e4SAndreas Gohr    }
182*08f248e4SAndreas Gohr
183*08f248e4SAndreas Gohr    /**
184*08f248e4SAndreas Gohr     * Add a text to the SVG
185*08f248e4SAndreas Gohr     * @param string $def
186*08f248e4SAndreas Gohr     * @param float $x
187*08f248e4SAndreas Gohr     * @param float $y
188*08f248e4SAndreas Gohr     * @param array $attributes
189*08f248e4SAndreas Gohr     * @return SimpleXMLElement
190*08f248e4SAndreas Gohr     */
191*08f248e4SAndreas Gohr    public function addText($text, $x=0, $y=0, $attributes=array()) {
192*08f248e4SAndreas Gohr        $def = $this->textDef($text);
193*08f248e4SAndreas Gohr
194*08f248e4SAndreas Gohr        if($x!=0 || $y!=0){
195*08f248e4SAndreas Gohr            $def = $this->defTranslate($def, $x, $y);
196*08f248e4SAndreas Gohr        }
197*08f248e4SAndreas Gohr
198*08f248e4SAndreas Gohr        if($this->font->color) {
199*08f248e4SAndreas Gohr            $attributes['fill'] = $this->font->color;
200*08f248e4SAndreas Gohr        }
201*08f248e4SAndreas Gohr
202*08f248e4SAndreas Gohr        return $this->addPath($def, $attributes);
203*08f248e4SAndreas Gohr    }
204*08f248e4SAndreas Gohr
205*08f248e4SAndreas Gohr
206*08f248e4SAndreas Gohr    /**
207*08f248e4SAndreas Gohr     * Function takes UTF-8 encoded string and size, returns xml for SVG paths representing this string.
208*08f248e4SAndreas Gohr     * @param string $text UTF-8 encoded text
209*08f248e4SAndreas Gohr     * @return string xml for text converted into SVG paths
210*08f248e4SAndreas Gohr     */
211*08f248e4SAndreas Gohr    public function textDef($text) {
212*08f248e4SAndreas Gohr        $def = array();
213*08f248e4SAndreas Gohr
214*08f248e4SAndreas Gohr        $horizAdvX = 0;
215*08f248e4SAndreas Gohr        $horizAdvY = $this->font->ascent + $this->font->descent;
216*08f248e4SAndreas Gohr        $fontSize = floatval($this->font->size) / $this->font->unitsPerEm;
217*08f248e4SAndreas Gohr        $text = $this->_utf8ToUnicode($text);
218*08f248e4SAndreas Gohr
219*08f248e4SAndreas Gohr        for($i = 0; $i < count($text); $i++) {
220*08f248e4SAndreas Gohr
221*08f248e4SAndreas Gohr            $letter = $text[$i];
222*08f248e4SAndreas Gohr
223*08f248e4SAndreas Gohr            // line break support (10 is unicode for linebreak)
224*08f248e4SAndreas Gohr            if($letter==10){
225*08f248e4SAndreas Gohr                $horizAdvX = 0;
226*08f248e4SAndreas Gohr                $horizAdvY += $this->font->lineHeight * ( $this->font->ascent + $this->font->descent );
227*08f248e4SAndreas Gohr                continue;
228*08f248e4SAndreas Gohr            }
229*08f248e4SAndreas Gohr
230*08f248e4SAndreas Gohr            // extract character definition
231*08f248e4SAndreas Gohr            $d = $this->font->glyphs[$letter]->d;
232*08f248e4SAndreas Gohr
233*08f248e4SAndreas Gohr            // transform typo from original SVG format to straight display
234*08f248e4SAndreas Gohr            $d = $this->defScale($d, $fontSize, -$fontSize);
235*08f248e4SAndreas Gohr            $d = $this->defTranslate($d, $horizAdvX, $horizAdvY*$fontSize*2);
236*08f248e4SAndreas Gohr
237*08f248e4SAndreas Gohr            $def[] = $d;
238*08f248e4SAndreas Gohr
239*08f248e4SAndreas Gohr            // next letter's position
240*08f248e4SAndreas Gohr            $horizAdvX += $this->font->glyphs[$letter]->horizAdvX * $fontSize + $this->font->em * $this->font->letterSpacing * $fontSize;
241*08f248e4SAndreas Gohr        }
242*08f248e4SAndreas Gohr        return implode(' ', $def);
243*08f248e4SAndreas Gohr    }
244*08f248e4SAndreas Gohr
245*08f248e4SAndreas Gohr
246*08f248e4SAndreas Gohr    /**
247*08f248e4SAndreas Gohr     * Function takes UTF-8 encoded string and size, returns width and height of the whole text
248*08f248e4SAndreas Gohr     * @param string $text UTF-8 encoded text
249*08f248e4SAndreas Gohr     * @return array ($width, $height)
250*08f248e4SAndreas Gohr     */
251*08f248e4SAndreas Gohr    public function textDimensions($text) {
252*08f248e4SAndreas Gohr        $def = array();
253*08f248e4SAndreas Gohr
254*08f248e4SAndreas Gohr        $fontSize = floatval($this->font->size) / $this->font->unitsPerEm;
255*08f248e4SAndreas Gohr        $text = $this->_utf8ToUnicode($text);
256*08f248e4SAndreas Gohr
257*08f248e4SAndreas Gohr        $lineWidth = 0;
258*08f248e4SAndreas Gohr        $lineHeight = ( $this->font->ascent + $this->font->descent ) * $fontSize * 2;
259*08f248e4SAndreas Gohr
260*08f248e4SAndreas Gohr        $width = 0;
261*08f248e4SAndreas Gohr        $height = $lineHeight;
262*08f248e4SAndreas Gohr
263*08f248e4SAndreas Gohr        for($i = 0; $i < count($text); $i++) {
264*08f248e4SAndreas Gohr
265*08f248e4SAndreas Gohr            $letter = $text[$i];
266*08f248e4SAndreas Gohr
267*08f248e4SAndreas Gohr            // line break support (10 is unicode for linebreak)
268*08f248e4SAndreas Gohr            if($letter==10){
269*08f248e4SAndreas Gohr                $width = $lineWidth>$width ? $lineWidth : $width;
270*08f248e4SAndreas Gohr                $height += $lineHeight * $this->font->lineHeight;
271*08f248e4SAndreas Gohr                $lineWidth = 0;
272*08f248e4SAndreas Gohr                continue;
273*08f248e4SAndreas Gohr            }
274*08f248e4SAndreas Gohr
275*08f248e4SAndreas Gohr            $lineWidth += $this->font->glyphs[$letter]->horizAdvX * $fontSize + $this->font->em * $this->font->letterSpacing * $fontSize;
276*08f248e4SAndreas Gohr        }
277*08f248e4SAndreas Gohr
278*08f248e4SAndreas Gohr        // only keep the widest line's width
279*08f248e4SAndreas Gohr        $width = $lineWidth>$width ? $lineWidth : $width;
280*08f248e4SAndreas Gohr
281*08f248e4SAndreas Gohr        return array($width, $height);
282*08f248e4SAndreas Gohr    }
283*08f248e4SAndreas Gohr
284*08f248e4SAndreas Gohr
285*08f248e4SAndreas Gohr    /**
286*08f248e4SAndreas Gohr     * Function takes unicode character and returns the UTF-8 equivalent
287*08f248e4SAndreas Gohr     * @param  string $str
288*08f248e4SAndreas Gohr     * @return string
289*08f248e4SAndreas Gohr     */
290*08f248e4SAndreas Gohr    public function unicodeDef( $unicode ) {
291*08f248e4SAndreas Gohr
292*08f248e4SAndreas Gohr        $horizAdvY = $this->font->ascent + $this->font->descent;
293*08f248e4SAndreas Gohr        $fontSize =  floatval($this->font->size) / $this->font->unitsPerEm;
294*08f248e4SAndreas Gohr
295*08f248e4SAndreas Gohr        // extract character definition
296*08f248e4SAndreas Gohr        $d = $this->font->glyphs[hexdec($unicode)]->d;
297*08f248e4SAndreas Gohr
298*08f248e4SAndreas Gohr        // transform typo from original SVG format to straight display
299*08f248e4SAndreas Gohr        $d = $this->defScale($d, $fontSize, -$fontSize);
300*08f248e4SAndreas Gohr        $d = $this->defTranslate($d, 0, $horizAdvY*$fontSize*2);
301*08f248e4SAndreas Gohr
302*08f248e4SAndreas Gohr        return $d;
303*08f248e4SAndreas Gohr    }
304*08f248e4SAndreas Gohr
305*08f248e4SAndreas Gohr    /**
306*08f248e4SAndreas Gohr     * Returns the character width, as set in the font file
307*08f248e4SAndreas Gohr     * @param  string  $str
308*08f248e4SAndreas Gohr     * @param  boolean $is_unicode
309*08f248e4SAndreas Gohr     * @return float
310*08f248e4SAndreas Gohr     */
311*08f248e4SAndreas Gohr    public function characterWidth( $char, $is_unicode = false ) {
312*08f248e4SAndreas Gohr        if ($is_unicode){
313*08f248e4SAndreas Gohr            $letter = hexdec($char);
314*08f248e4SAndreas Gohr        }
315*08f248e4SAndreas Gohr        else {
316*08f248e4SAndreas Gohr            $letter = $this->_utf8ToUnicode($char);
317*08f248e4SAndreas Gohr        }
318*08f248e4SAndreas Gohr
319*08f248e4SAndreas Gohr        if (!isset($this->font->glyphs[$letter]))
320*08f248e4SAndreas Gohr            return NULL;
321*08f248e4SAndreas Gohr
322*08f248e4SAndreas Gohr        $fontSize = floatval($this->font->size) / $this->font->unitsPerEm;
323*08f248e4SAndreas Gohr        return $this->font->glyphs[$letter]->horizAdvX * $fontSize;
324*08f248e4SAndreas Gohr    }
325*08f248e4SAndreas Gohr
326*08f248e4SAndreas Gohr
327*08f248e4SAndreas Gohr    /**
328*08f248e4SAndreas Gohr     * Applies a translate transformation to definition
329*08f248e4SAndreas Gohr     * @param  string  $def definition
330*08f248e4SAndreas Gohr     * @param  float $x
331*08f248e4SAndreas Gohr     * @param  float $y
332*08f248e4SAndreas Gohr     * @return string
333*08f248e4SAndreas Gohr     */
334*08f248e4SAndreas Gohr    public function defTranslate($def, $x=0, $y=0){
335*08f248e4SAndreas Gohr        return $this->defApplyMatrix($def, array(1, 0, 0, 1, $x, $y));
336*08f248e4SAndreas Gohr    }
337*08f248e4SAndreas Gohr
338*08f248e4SAndreas Gohr    /**
339*08f248e4SAndreas Gohr     * Applies a translate transformation to definition
340*08f248e4SAndreas Gohr     * @param  string  $def    Definition
341*08f248e4SAndreas Gohr     * @param  integer $angle  Rotation angle (degrees)
342*08f248e4SAndreas Gohr     * @param  integer $x      X coordinate of rotation center
343*08f248e4SAndreas Gohr     * @param  integer $y      Y coordinate of rotation center
344*08f248e4SAndreas Gohr     * @return string
345*08f248e4SAndreas Gohr     */
346*08f248e4SAndreas Gohr    public function defRotate($def, $angle, $x=0, $y=0){
347*08f248e4SAndreas Gohr        if($x==0 && $y==0){
348*08f248e4SAndreas Gohr            $angle = deg2rad($angle);
349*08f248e4SAndreas Gohr            return $this->defApplyMatrix($def, array(cos($angle), sin($angle), -sin($angle), cos($angle), 0, 0));
350*08f248e4SAndreas Gohr        }
351*08f248e4SAndreas Gohr
352*08f248e4SAndreas Gohr        // rotate by a given point
353*08f248e4SAndreas Gohr        $def = $this->defTranslate($def, $x, $y);
354*08f248e4SAndreas Gohr        $def = $this->defRotate($def, $angle);
355*08f248e4SAndreas Gohr        $def = $this->defTranslate($def, -$x, -$y);
356*08f248e4SAndreas Gohr        return $def;
357*08f248e4SAndreas Gohr    }
358*08f248e4SAndreas Gohr
359*08f248e4SAndreas Gohr    /**
360*08f248e4SAndreas Gohr     * Applies a scale transformation to definition
361*08f248e4SAndreas Gohr     * @param  string  $def definition
362*08f248e4SAndreas Gohr     * @param  integer $x
363*08f248e4SAndreas Gohr     * @param  integer $y
364*08f248e4SAndreas Gohr     * @return string
365*08f248e4SAndreas Gohr     */
366*08f248e4SAndreas Gohr    public function defScale($def, $x=1, $y=1){
367*08f248e4SAndreas Gohr        return $this->defApplyMatrix($def, array($x, 0, 0, $y, 0, 0));
368*08f248e4SAndreas Gohr    }
369*08f248e4SAndreas Gohr
370*08f248e4SAndreas Gohr    /**
371*08f248e4SAndreas Gohr     * Calculates the new definition with the matrix applied
372*08f248e4SAndreas Gohr     * @param  string $def
373*08f248e4SAndreas Gohr     * @param  array  $matrix
374*08f248e4SAndreas Gohr     * @return string
375*08f248e4SAndreas Gohr     */
376*08f248e4SAndreas Gohr    public function defApplyMatrix($def, $matrix){
377*08f248e4SAndreas Gohr
378*08f248e4SAndreas Gohr        // if there are several shapes in this definition, do the operation for each
379*08f248e4SAndreas Gohr        preg_match_all('/M[^zZ]*[zZ]/', $def, $shapes);
380*08f248e4SAndreas Gohr        $shapes = $shapes[0];
381*08f248e4SAndreas Gohr        if(count($shapes)>1){
382*08f248e4SAndreas Gohr            foreach($shapes as &$shape)
383*08f248e4SAndreas Gohr                $shape = $this->defApplyMatrix($shape, $matrix);
384*08f248e4SAndreas Gohr            return implode(' ', $shapes);
385*08f248e4SAndreas Gohr        }
386*08f248e4SAndreas Gohr
387*08f248e4SAndreas Gohr        preg_match_all('/[a-zA-Z]+[^a-zA-Z]*/', $def, $instructions);
388*08f248e4SAndreas Gohr        $instructions = $instructions[0];
389*08f248e4SAndreas Gohr
390*08f248e4SAndreas Gohr        $return = '';
391*08f248e4SAndreas Gohr        foreach($instructions as &$instruction){
392*08f248e4SAndreas Gohr            $i = preg_replace('/[^a-zA-Z]*/', '', $instruction);
393*08f248e4SAndreas Gohr            preg_match_all('/\-?[0-9\.]+/', $instruction, $coords);
394*08f248e4SAndreas Gohr            $coords = $coords[0];
395*08f248e4SAndreas Gohr
396*08f248e4SAndreas Gohr            if(empty($coords)){
397*08f248e4SAndreas Gohr                continue;
398*08f248e4SAndreas Gohr            }
399*08f248e4SAndreas Gohr
400*08f248e4SAndreas Gohr            $new_coords = array();
401*08f248e4SAndreas Gohr            while(count($coords)>0){
402*08f248e4SAndreas Gohr
403*08f248e4SAndreas Gohr                // do the matrix calculation stuff
404*08f248e4SAndreas Gohr                list($a, $b, $c, $d, $e, $f) = $matrix;
405*08f248e4SAndreas Gohr
406*08f248e4SAndreas Gohr                // exception for relative instruction
407*08f248e4SAndreas Gohr                if( preg_match('/[a-z]/', $i) ){
408*08f248e4SAndreas Gohr                    $e = 0;
409*08f248e4SAndreas Gohr                    $f = 0;
410*08f248e4SAndreas Gohr                }
411*08f248e4SAndreas Gohr
412*08f248e4SAndreas Gohr                // convert horizontal lineto (relative)
413*08f248e4SAndreas Gohr                if( $i=='h' ){
414*08f248e4SAndreas Gohr                    $i = 'l';
415*08f248e4SAndreas Gohr                    $x = floatval( array_shift($coords) );
416*08f248e4SAndreas Gohr                    $y = 0;
417*08f248e4SAndreas Gohr
418*08f248e4SAndreas Gohr                    // add new point's coordinates
419*08f248e4SAndreas Gohr                    $current_point = array(
420*08f248e4SAndreas Gohr                        $a*$x + $c*$y + $e,
421*08f248e4SAndreas Gohr                        $b*$x + $d*$y + $f,
422*08f248e4SAndreas Gohr                    );
423*08f248e4SAndreas Gohr                    $new_coords = array_merge($new_coords, $current_point);
424*08f248e4SAndreas Gohr                }
425*08f248e4SAndreas Gohr
426*08f248e4SAndreas Gohr                // convert vertical lineto (relative)
427*08f248e4SAndreas Gohr                elseif( $i=='v' ){
428*08f248e4SAndreas Gohr                    $i = 'l';
429*08f248e4SAndreas Gohr                    $x = 0;
430*08f248e4SAndreas Gohr                    $y = floatval( array_shift($coords) );
431*08f248e4SAndreas Gohr
432*08f248e4SAndreas Gohr                    // add new point's coordinates
433*08f248e4SAndreas Gohr                    $current_point = array(
434*08f248e4SAndreas Gohr                        $a*$x + $c*$y + $e,
435*08f248e4SAndreas Gohr                        $b*$x + $d*$y + $f,
436*08f248e4SAndreas Gohr                    );
437*08f248e4SAndreas Gohr                    $new_coords = array_merge($new_coords, $current_point);
438*08f248e4SAndreas Gohr                }
439*08f248e4SAndreas Gohr
440*08f248e4SAndreas Gohr                // convert quadratic bezier curve (relative)
441*08f248e4SAndreas Gohr                elseif( $i=='q' ){
442*08f248e4SAndreas Gohr                    $x = floatval( array_shift($coords) );
443*08f248e4SAndreas Gohr                    $y = floatval( array_shift($coords) );
444*08f248e4SAndreas Gohr
445*08f248e4SAndreas Gohr                    // add new point's coordinates
446*08f248e4SAndreas Gohr                    $current_point = array(
447*08f248e4SAndreas Gohr                        $a*$x + $c*$y + $e,
448*08f248e4SAndreas Gohr                        $b*$x + $d*$y + $f,
449*08f248e4SAndreas Gohr                    );
450*08f248e4SAndreas Gohr                    $new_coords = array_merge($new_coords, $current_point);
451*08f248e4SAndreas Gohr
452*08f248e4SAndreas Gohr                    // same for 2nd point
453*08f248e4SAndreas Gohr                    $x = floatval( array_shift($coords) );
454*08f248e4SAndreas Gohr                    $y = floatval( array_shift($coords) );
455*08f248e4SAndreas Gohr
456*08f248e4SAndreas Gohr                    // add new point's coordinates
457*08f248e4SAndreas Gohr                    $current_point = array(
458*08f248e4SAndreas Gohr                        $a*$x + $c*$y + $e,
459*08f248e4SAndreas Gohr                        $b*$x + $d*$y + $f,
460*08f248e4SAndreas Gohr                    );
461*08f248e4SAndreas Gohr                    $new_coords = array_merge($new_coords, $current_point);
462*08f248e4SAndreas Gohr                }
463*08f248e4SAndreas Gohr
464*08f248e4SAndreas Gohr                // every other commands
465*08f248e4SAndreas Gohr                // @TODO: handle 'a,c,s' (elliptic arc curve) commands
466*08f248e4SAndreas Gohr                // cf. http://www.w3.org/TR/SVG/paths.html#PathDataCurveCommands
467*08f248e4SAndreas Gohr                else{
468*08f248e4SAndreas Gohr                    $x = floatval( array_shift($coords) );
469*08f248e4SAndreas Gohr                    $y = floatval( array_shift($coords) );
470*08f248e4SAndreas Gohr
471*08f248e4SAndreas Gohr                    // add new point's coordinates
472*08f248e4SAndreas Gohr                    $current_point = array(
473*08f248e4SAndreas Gohr                        $a*$x + $c*$y + $e,
474*08f248e4SAndreas Gohr                        $b*$x + $d*$y + $f,
475*08f248e4SAndreas Gohr                    );
476*08f248e4SAndreas Gohr                    $new_coords = array_merge($new_coords, $current_point);
477*08f248e4SAndreas Gohr                }
478*08f248e4SAndreas Gohr
479*08f248e4SAndreas Gohr
480*08f248e4SAndreas Gohr            }
481*08f248e4SAndreas Gohr
482*08f248e4SAndreas Gohr            $instruction = $i . implode(',', $new_coords);
483*08f248e4SAndreas Gohr
484*08f248e4SAndreas Gohr            // remove useless commas
485*08f248e4SAndreas Gohr            $instruction = preg_replace('/,\-/','-', $instruction);
486*08f248e4SAndreas Gohr        }
487*08f248e4SAndreas Gohr
488*08f248e4SAndreas Gohr        return implode('', $instructions);
489*08f248e4SAndreas Gohr    }
490*08f248e4SAndreas Gohr
491*08f248e4SAndreas Gohr
492*08f248e4SAndreas Gohr
493*08f248e4SAndreas Gohr    /**
494*08f248e4SAndreas Gohr     *
495*08f248e4SAndreas Gohr     * Short-hand methods
496*08f248e4SAndreas Gohr     *
497*08f248e4SAndreas Gohr     */
498*08f248e4SAndreas Gohr
499*08f248e4SAndreas Gohr
500*08f248e4SAndreas Gohr    /**
501*08f248e4SAndreas Gohr     * Return full SVG XML
502*08f248e4SAndreas Gohr     * @return string
503*08f248e4SAndreas Gohr     */
504*08f248e4SAndreas Gohr    public function asXML(){
505*08f248e4SAndreas Gohr        return $this->svg->asXML();
506*08f248e4SAndreas Gohr    }
507*08f248e4SAndreas Gohr
508*08f248e4SAndreas Gohr    /**
509*08f248e4SAndreas Gohr     * Adds an attribute to the SVG
510*08f248e4SAndreas Gohr     * @param string $key
511*08f248e4SAndreas Gohr     * @param string $value
512*08f248e4SAndreas Gohr     */
513*08f248e4SAndreas Gohr    public function addAttribute($key, $value){
514*08f248e4SAndreas Gohr        return $this->svg->addAttribute($key, $value);
515*08f248e4SAndreas Gohr    }
516*08f248e4SAndreas Gohr}
517