├── .gitignore ├── run.php ├── bin └── composer │ └── svg-icon-font-generator ├── composer.json ├── LICENSE ├── src └── MadeYourDay │ └── SVG │ ├── IconFontGeneratorCLI.php │ ├── IconFontGeneratorCLI │ ├── CreateCssCommand.php │ ├── CreateFontCommand.php │ ├── CreateFilesCommand.php │ └── CreateInfoCommand.php │ ├── Font.php │ ├── IconFontGenerator.php │ └── Document.php ├── README.md └── composer.lock /.gitignore: -------------------------------------------------------------------------------- 1 | # sublime project files 2 | /*.sublime-project 3 | /*.sublime-workspace 4 | 5 | /svg-icon-font-generator.phar 6 | /composer.phar 7 | -------------------------------------------------------------------------------- /run.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * For the full copyright and license information, please view the LICENSE 6 | * file that was distributed with this source code. 7 | */ 8 | 9 | require __DIR__.'/vendor/autoload.php'; 10 | 11 | $cli = new MadeYourDay\SVG\IconFontGeneratorCLI; 12 | $cli->run(); 13 | -------------------------------------------------------------------------------- /bin/composer/svg-icon-font-generator: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | 5 | * 6 | * For the full copyright and license information, please view the LICENSE 7 | * file that was distributed with this source code. 8 | */ 9 | 10 | require __DIR__ . '/../../../../autoload.php'; 11 | 12 | $cli = new MadeYourDay\SVG\IconFontGeneratorCLI; 13 | $cli->run(); 14 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name":"madeyourday/svg-icon-font-generator", 3 | "description":"Creates a SVG font from a set of SVG files and vice versa. The glyph mapping is based on the file names – that makes updating and extending easy and fast.", 4 | "homepage":"https://github.com/madeyourday/SVG-Icon-Font-Generator", 5 | "license":"MIT", 6 | "authors":[ 7 | { 8 | "name":"MADE/YOUR/DAY", 9 | "homepage":"https://github.com/madeyourday", 10 | "role":"Developer" 11 | } 12 | ], 13 | "support":{ 14 | "issues":"https://github.com/madeyourday/SVG-Icon-Font-Generator/issues", 15 | "source":"https://github.com/madeyourday/SVG-Icon-Font-Generator" 16 | }, 17 | "bin": ["bin/composer/svg-icon-font-generator"], 18 | "require": { 19 | "symfony/console": "~2.1" 20 | }, 21 | "autoload":{ 22 | "classmap":["src/"] 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2012-2016 MADE/YOUR/DAY 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is furnished 8 | to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /src/MadeYourDay/SVG/IconFontGeneratorCLI.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * For the full copyright and license information, please view the LICENSE 6 | * file that was distributed with this source code. 7 | */ 8 | 9 | namespace MadeYourDay\SVG; 10 | 11 | use Symfony\Component\Console\Application; 12 | use MadeYourDay\SVG\IconFontGeneratorCLI\CreateFontCommand; 13 | use MadeYourDay\SVG\IconFontGeneratorCLI\CreateFilesCommand; 14 | use MadeYourDay\SVG\IconFontGeneratorCLI\CreateInfoCommand; 15 | use MadeYourDay\SVG\IconFontGeneratorCLI\CreateCssCommand; 16 | 17 | /** 18 | * SVG Icon Font Generator Command Line Application 19 | * 20 | * @author Martin Auswöger 21 | */ 22 | class IconFontGeneratorCLI extends Application{ 23 | 24 | /** 25 | * constructor, sets up application information and commands 26 | */ 27 | public function __construct(){ 28 | 29 | parent::__construct('SVG Icon Font Generator', '0.1.3'); 30 | 31 | $this->add(new CreateFontCommand); 32 | $this->add(new CreateFilesCommand); 33 | $this->add(new CreateInfoCommand); 34 | $this->add(new CreateCssCommand); 35 | 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SVG-Icon-Font-Generator 2 | 3 | Creates a SVG font from a set of SVG files and vice versa. 4 | The glyph mapping is based on the file names – that makes updating and extending easy and fast. 5 | 6 | ## Installation 7 | 8 | You can create the svg-icon-font-generator.phar by yourself using the following commands: 9 | (this requires [composer.phar](http://getcomposer.org/) to be installed) 10 | 11 | git clone https://github.com/madeyourday/SVG-Icon-Font-Generator.git 12 | cd SVG-Icon-Font-Generator 13 | php composer.phar install 14 | php build.php 15 | 16 | Or download the latest release here: 17 | 18 | ### System Requirements 19 | * PHP 5.3 or higher 20 | * PHP mbstrings needs to be enabled 21 | * phar.readonly must be off. 22 | 23 | ## Usage 24 | 25 | ### Create a SVG font from a set of SVG files 26 | 27 | php svg-icon-font-generator.phar create-font /path/to/svg/files your-font.svg --rename-files 28 | 29 | The files should be named like this: 30 | * `arrow-up-x2191.svg` use the correct unicode symbol if possible 31 | * `magnifying-glass-xe001.svg` otherwise use the unicode "Private Use Area" (start from xe001, don't use xe000) 32 | * `key.svg` this file gets automatically mapped to a unicode "Private Use Area" symbol, if you use the `--rename-files` option this file will be renamed to something like `key-xe002.svg` 33 | 34 | The list above generates the class names `icon-arrow-up`, `icon-magnifying-glass` and `icon-key`. 35 | 36 | For creating new icons you can use this SVG template: 37 | 38 | An example set of SVG files can be found here: 39 | 40 | ### Create a set of SVG files from a SVG font 41 | 42 | php svg-icon-font-generator.phar create-files your-font.svg /path/to/svg/files 43 | 44 | ### Create a HTML info page from a SVG font 45 | 46 | php svg-icon-font-generator.phar create-info your-font.svg your-font-info.html 47 | 48 | ### Create a CSS file with icon classes from a SVG font 49 | 50 | php svg-icon-font-generator.phar create-css your-font.svg your-icons.css 51 | 52 | The icon class names are based on the `glyph-name`s specified in the SVG file. 53 | -------------------------------------------------------------------------------- /src/MadeYourDay/SVG/IconFontGeneratorCLI/CreateCssCommand.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * For the full copyright and license information, please view the LICENSE 6 | * file that was distributed with this source code. 7 | */ 8 | 9 | namespace MadeYourDay\SVG\IconFontGeneratorCLI; 10 | 11 | use Symfony\Component\Console\Command\Command; 12 | use Symfony\Component\Console\Input\InputArgument; 13 | use Symfony\Component\Console\Input\InputInterface; 14 | use Symfony\Component\Console\Input\InputOption; 15 | use Symfony\Component\Console\Output\OutputInterface; 16 | use Symfony\Component\Console\Formatter\OutputFormatterStyle; 17 | use MadeYourDay\SVG\IconFontGenerator; 18 | use MadeYourDay\SVG\Font; 19 | 20 | /** 21 | * create-css command 22 | * 23 | * @author Martin Auswöger 24 | */ 25 | class CreateCssCommand extends Command{ 26 | 27 | /** 28 | * configures the create-css command 29 | * 30 | * @return void 31 | */ 32 | protected function configure(){ 33 | $this 34 | ->setName('create-css') 35 | ->setDescription('Creates a CSS file with icon classes from a SVG font') 36 | ->addArgument('font-file', InputArgument::REQUIRED, 'path to the SVG font file') 37 | ->addArgument('output-file', InputArgument::REQUIRED, 'path where the CSS file should be saved') 38 | ; 39 | } 40 | 41 | /** 42 | * creates a CSS file with icon classes from a SVG font 43 | * 44 | * @param InputInterface $input input 45 | * @param OutputInterface $output output 46 | * @return void 47 | */ 48 | protected function execute(InputInterface $input, OutputInterface $output){ 49 | 50 | $fontFile = realpath($input->getArgument('font-file')); 51 | if($fontFile === false || !file_exists($fontFile)){ 52 | throw new \InvalidArgumentException('"'.$input->getArgument('font-file').'" does not exist'); 53 | } 54 | 55 | $outputFile = $input->getArgument('output-file'); 56 | 57 | $generator = new IconFontGenerator; 58 | 59 | $output->writeln('reading font file from "'.$fontFile.'" ...'); 60 | $generator->generateFromFont(new Font(array(), file_get_contents($fontFile))); 61 | 62 | $output->writeln('writing CSS file to "'.$outputFile.'" ...'); 63 | file_put_contents($outputFile, $generator->getCss()); 64 | 65 | $output->getFormatter()->setStyle('success', new OutputFormatterStyle(null, null, array('bold', 'reverse'))); 66 | 67 | $output->writeln('created CSS file successfully'); 68 | 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/MadeYourDay/SVG/IconFontGeneratorCLI/CreateFontCommand.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * For the full copyright and license information, please view the LICENSE 6 | * file that was distributed with this source code. 7 | */ 8 | 9 | namespace MadeYourDay\SVG\IconFontGeneratorCLI; 10 | 11 | use Symfony\Component\Console\Command\Command; 12 | use Symfony\Component\Console\Input\InputArgument; 13 | use Symfony\Component\Console\Input\InputInterface; 14 | use Symfony\Component\Console\Input\InputOption; 15 | use Symfony\Component\Console\Output\OutputInterface; 16 | use Symfony\Component\Console\Formatter\OutputFormatterStyle; 17 | use MadeYourDay\SVG\IconFontGenerator; 18 | 19 | /** 20 | * create-font command 21 | * 22 | * @author Martin Auswöger 23 | */ 24 | class CreateFontCommand extends Command{ 25 | 26 | /** 27 | * configures the create-font command 28 | * 29 | * @return void 30 | */ 31 | protected function configure(){ 32 | $this 33 | ->setName('create-font') 34 | ->setDescription('Creates a SVG Font out of SGV files from a directory') 35 | ->addArgument('directory', InputArgument::REQUIRED, 'path to directory containging SVG files') 36 | ->addArgument('output-file', InputArgument::REQUIRED, 'path to the output file') 37 | ->addOption('rename-files', null, InputOption::VALUE_NONE, 'if set, files without mapping information will be renamed to include the mapping information (e.g. my-icon.svg renamed to my-icon-xe001.svg)') 38 | ->addOption('name', null, InputOption::VALUE_REQUIRED, 'name of the font') 39 | ; 40 | } 41 | 42 | /** 43 | * creates a SVG Font out of SGV files from a directory 44 | * 45 | * @param InputInterface $input input 46 | * @param OutputInterface $output output 47 | * @return void 48 | */ 49 | protected function execute(InputInterface $input, OutputInterface $output){ 50 | 51 | $directory = $input->getArgument('directory'); 52 | $outputFile = $input->getArgument('output-file'); 53 | 54 | $generator = new IconFontGenerator; 55 | 56 | $output->writeln('reading files from "'.$directory.'" ...'); 57 | $generator->generateFromDir($directory, array( 58 | 'id' => $input->getOption('name') ?: 'SVGFont', 59 | ), $input->getOption('rename-files')); 60 | 61 | $output->writeln('writing font to "'.$outputFile.'" ...'); 62 | file_put_contents($outputFile, $generator->getFont()->getXML()); 63 | 64 | $output->getFormatter()->setStyle('success', new OutputFormatterStyle(null, null, array('bold', 'reverse'))); 65 | 66 | $output->writeln('created '.$outputFile.' successfully'); 67 | 68 | } 69 | 70 | } 71 | -------------------------------------------------------------------------------- /src/MadeYourDay/SVG/IconFontGeneratorCLI/CreateFilesCommand.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * For the full copyright and license information, please view the LICENSE 6 | * file that was distributed with this source code. 7 | */ 8 | 9 | namespace MadeYourDay\SVG\IconFontGeneratorCLI; 10 | 11 | use Symfony\Component\Console\Command\Command; 12 | use Symfony\Component\Console\Input\InputArgument; 13 | use Symfony\Component\Console\Input\InputInterface; 14 | use Symfony\Component\Console\Input\InputOption; 15 | use Symfony\Component\Console\Output\OutputInterface; 16 | use Symfony\Component\Console\Formatter\OutputFormatterStyle; 17 | use MadeYourDay\SVG\IconFontGenerator; 18 | use MadeYourDay\SVG\Font; 19 | 20 | /** 21 | * create-files command 22 | * 23 | * @author Martin Auswöger 24 | */ 25 | class CreateFilesCommand extends Command{ 26 | 27 | /** 28 | * configures the create-files command 29 | * 30 | * @return void 31 | */ 32 | protected function configure(){ 33 | $this 34 | ->setName('create-files') 35 | ->setDescription('Creates single SVG files out of a SVG font and saves them to the specified directory') 36 | ->addArgument('font-file', InputArgument::REQUIRED, 'path to the SVG font file') 37 | ->addArgument('output-directory', InputArgument::REQUIRED, 'path to the output directory') 38 | ; 39 | } 40 | 41 | /** 42 | * creates single SVG files out of a SVG font and saves them to the specified directory 43 | * 44 | * @param InputInterface $input input 45 | * @param OutputInterface $output output 46 | * @return void 47 | */ 48 | protected function execute(InputInterface $input, OutputInterface $output){ 49 | 50 | $fontFile = realpath($input->getArgument('font-file')); 51 | if($fontFile === false || !file_exists($fontFile)){ 52 | throw new \InvalidArgumentException('"'.$input->getArgument('font-file').'" does not exist'); 53 | } 54 | 55 | $outputDirectory = realpath($input->getArgument('output-directory')); 56 | if($outputDirectory === false || !file_exists($outputDirectory) || !is_dir($outputDirectory)){ 57 | throw new \InvalidArgumentException('"'.$input->getArgument('output-directory').'" is no directory'); 58 | } 59 | 60 | $generator = new IconFontGenerator; 61 | 62 | $output->writeln('reading font file from "'.$fontFile.'" ...'); 63 | $generator->generateFromFont(new Font(array(), file_get_contents($fontFile))); 64 | 65 | $output->writeln('writing SVG files to "'.$outputDirectory.'" ...'); 66 | $generator->saveGlyphsToDir($outputDirectory); 67 | 68 | $output->getFormatter()->setStyle('success', new OutputFormatterStyle(null, null, array('bold', 'reverse'))); 69 | 70 | $output->writeln('created SVG files successfully'); 71 | 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "a5a11aad3c1f9724cd8b317d03591a19", 8 | "content-hash": "5367801df54c27a17b91ccaddc0ca261", 9 | "packages": [ 10 | { 11 | "name": "symfony/console", 12 | "version": "v2.7.6", 13 | "source": { 14 | "type": "git", 15 | "url": "https://github.com/symfony/console.git", 16 | "reference": "5efd632294c8320ea52492db22292ff853a43766" 17 | }, 18 | "dist": { 19 | "type": "zip", 20 | "url": "https://api.github.com/repos/symfony/console/zipball/5efd632294c8320ea52492db22292ff853a43766", 21 | "reference": "5efd632294c8320ea52492db22292ff853a43766", 22 | "shasum": "" 23 | }, 24 | "require": { 25 | "php": ">=5.3.9" 26 | }, 27 | "require-dev": { 28 | "psr/log": "~1.0", 29 | "symfony/event-dispatcher": "~2.1", 30 | "symfony/process": "~2.1" 31 | }, 32 | "suggest": { 33 | "psr/log": "For using the console logger", 34 | "symfony/event-dispatcher": "", 35 | "symfony/process": "" 36 | }, 37 | "type": "library", 38 | "extra": { 39 | "branch-alias": { 40 | "dev-master": "2.7-dev" 41 | } 42 | }, 43 | "autoload": { 44 | "psr-4": { 45 | "Symfony\\Component\\Console\\": "" 46 | } 47 | }, 48 | "notification-url": "https://packagist.org/downloads/", 49 | "license": [ 50 | "MIT" 51 | ], 52 | "authors": [ 53 | { 54 | "name": "Fabien Potencier", 55 | "email": "fabien@symfony.com" 56 | }, 57 | { 58 | "name": "Symfony Community", 59 | "homepage": "https://symfony.com/contributors" 60 | } 61 | ], 62 | "description": "Symfony Console Component", 63 | "homepage": "https://symfony.com", 64 | "time": "2015-10-20 14:38:46" 65 | } 66 | ], 67 | "packages-dev": [], 68 | "aliases": [], 69 | "minimum-stability": "stable", 70 | "stability-flags": [], 71 | "prefer-stable": false, 72 | "prefer-lowest": false, 73 | "platform": [], 74 | "platform-dev": [] 75 | } 76 | -------------------------------------------------------------------------------- /src/MadeYourDay/SVG/Font.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * For the full copyright and license information, please view the LICENSE 6 | * file that was distributed with this source code. 7 | */ 8 | 9 | namespace MadeYourDay\SVG; 10 | 11 | use SimpleXMLElement; 12 | 13 | /** 14 | * SVG Font 15 | * 16 | * @author Martin Auswöger 17 | */ 18 | class Font{ 19 | 20 | /** 21 | * @var SimpleXMLElement XML document 22 | */ 23 | protected $xmlDocument; 24 | 25 | /** 26 | * @var array font options 27 | */ 28 | protected $options = array( 29 | 'id' => 'SVGFont', 30 | 'units-per-em' => 512, 31 | 'horiz-adv-x' => 512, 32 | 'ascent' => 448, 33 | 'descent' => -64, 34 | 'x-height' => 240, 35 | 'cap-height' => 480, 36 | ); 37 | 38 | /** 39 | * create a empty font or from a SVG XML string 40 | * 41 | * @param array $options font options 42 | * @param string $svgString SVG XML string 43 | */ 44 | public function __construct($options = array(), $svgString = null){ 45 | 46 | $newFont = false; 47 | if($svgString === null){ 48 | $newFont = true; 49 | $svgString = ' 50 | 51 | '; 52 | } 53 | 54 | $this->xmlDocument = new SimpleXMLElement($svgString); 55 | 56 | if(!count($this->xmlDocument->defs)){ 57 | $this->xmlDocument->addChild('defs'); 58 | } 59 | if(!count($this->xmlDocument->defs[0]->font)){ 60 | $this->xmlDocument->defs[0]->addChild('font'); 61 | } 62 | if(!count($this->xmlDocument->defs[0]->font[0]->{'font-face'})){ 63 | $this->xmlDocument->defs[0]->font[0]->addChild('font-face'); 64 | } 65 | if(!count($this->xmlDocument->defs[0]->font[0]->{'missing-glyph'})){ 66 | $this->xmlDocument->defs[0]->font[0]->addChild('missing-glyph'); 67 | } 68 | 69 | if(!$newFont){ 70 | $options = array_merge($this->getOptionsFromXML(), $options); 71 | } 72 | 73 | $this->setOptions($options); 74 | 75 | } 76 | 77 | /** 78 | * set font optinos 79 | * 80 | * @param array $options font options 81 | */ 82 | public function setOptions($options = array()){ 83 | 84 | $this->options = array_merge($this->options, $options); 85 | 86 | $this->xmlDocument->defs[0]->font[0]['id'] = $this->options['id']; 87 | $this->xmlDocument->defs[0]->font[0]['horiz-adv-x'] = $this->options['horiz-adv-x']; 88 | $this->xmlDocument->defs[0]->font[0]->{'font-face'}[0]['units-per-em'] = $this->options['units-per-em']; 89 | $this->xmlDocument->defs[0]->font[0]->{'font-face'}[0]['ascent'] = $this->options['ascent']; 90 | $this->xmlDocument->defs[0]->font[0]->{'font-face'}[0]['descent'] = $this->options['descent']; 91 | $this->xmlDocument->defs[0]->font[0]->{'font-face'}[0]['x-height'] = $this->options['x-height']; 92 | $this->xmlDocument->defs[0]->font[0]->{'font-face'}[0]['cap-height'] = $this->options['cap-height']; 93 | $this->xmlDocument->defs[0]->font[0]->{'missing-glyph'}[0]['horiz-adv-x'] = $this->options['horiz-adv-x']; 94 | 95 | } 96 | 97 | /** 98 | * returns font options stored in the XML document 99 | * 100 | * @return array font options 101 | */ 102 | protected function getOptionsFromXML(){ 103 | 104 | $options = array(); 105 | 106 | foreach(array('id', 'horiz-adv-x') as $key){ 107 | if(isset($this->xmlDocument->defs[0]->font[0][$key])){ 108 | $options[$key] = (string)$this->xmlDocument->defs[0]->font[0][$key]; 109 | } 110 | } 111 | foreach(array('units-per-em', 'ascent', 'descent', 'x-height', 'cap-height') as $key){ 112 | if(isset($this->xmlDocument->defs[0]->font[0]->{'font-face'}[0][$key])){ 113 | $options[$key] = (string)$this->xmlDocument->defs[0]->font[0]->{'font-face'}[0][$key]; 114 | } 115 | } 116 | 117 | return $options; 118 | 119 | } 120 | 121 | /** 122 | * get font options 123 | * 124 | * @return array font options 125 | */ 126 | public function getOptions(){ 127 | return $this->options; 128 | } 129 | 130 | /** 131 | * get XML string 132 | * 133 | * @return string XML SVG string 134 | */ 135 | public function getXML(){ 136 | return $this->xmlDocument->asXML(); 137 | } 138 | 139 | /** 140 | * add a glyph to the font 141 | * 142 | * @param string $char character of the glyph 143 | * @param string $path SVG path definition 144 | * @param string $name name of the glyph 145 | * @param float $width glyph width (horiz-adv-x) 146 | */ 147 | public function addGlyph($char, $path, $name = null, $width = null){ 148 | 149 | $glyph = $this->xmlDocument->defs[0]->font[0]->addChild('glyph'); 150 | $glyph->addAttribute('unicode', $char); 151 | if($name !== null){ 152 | $glyph->addAttribute('glyph-name', $name); 153 | } 154 | if($width !== null){ 155 | $glyph->addAttribute('horiz-adv-x', $width); 156 | } 157 | $glyph->addAttribute('d', $path); 158 | 159 | } 160 | 161 | /** 162 | * get all glyphs 163 | * 164 | * @return array set of glyph arrays containing char, path, name and width (name and with are optional) 165 | */ 166 | public function getGlyphs(){ 167 | 168 | if( 169 | !isset($this->xmlDocument->defs[0]->font[0]->glyph) || 170 | !count($this->xmlDocument->defs[0]->font[0]->glyph) 171 | ){ 172 | return array(); 173 | } 174 | $glyphs = array(); 175 | foreach($this->xmlDocument->defs[0]->font[0]->glyph as $xmlGlyph){ 176 | if(isset($xmlGlyph['unicode']) && isset($xmlGlyph['d'])){ 177 | $glyph = array( 178 | 'char' => (string)$xmlGlyph['unicode'], 179 | 'path' => (string)$xmlGlyph['d'], 180 | ); 181 | if(isset($xmlGlyph['glyph-name'])){ 182 | $glyph['name'] = (string)$xmlGlyph['glyph-name']; 183 | } 184 | if(isset($xmlGlyph['horiz-adv-x'])){ 185 | $glyph['width'] = (string)$xmlGlyph['horiz-adv-x']; 186 | } 187 | $glyphs[] = $glyph; 188 | } 189 | } 190 | return $glyphs; 191 | } 192 | 193 | } 194 | -------------------------------------------------------------------------------- /src/MadeYourDay/SVG/IconFontGeneratorCLI/CreateInfoCommand.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * For the full copyright and license information, please view the LICENSE 6 | * file that was distributed with this source code. 7 | */ 8 | 9 | namespace MadeYourDay\SVG\IconFontGeneratorCLI; 10 | 11 | use Symfony\Component\Console\Command\Command; 12 | use Symfony\Component\Console\Input\InputArgument; 13 | use Symfony\Component\Console\Input\InputInterface; 14 | use Symfony\Component\Console\Input\InputOption; 15 | use Symfony\Component\Console\Output\OutputInterface; 16 | use Symfony\Component\Console\Formatter\OutputFormatterStyle; 17 | use MadeYourDay\SVG\IconFontGenerator; 18 | use MadeYourDay\SVG\Font; 19 | 20 | /** 21 | * create-info command 22 | * 23 | * @author Martin Auswöger 24 | */ 25 | class CreateInfoCommand extends Command{ 26 | 27 | /** 28 | * configures the create-info command 29 | * 30 | * @return void 31 | */ 32 | protected function configure(){ 33 | $this 34 | ->setName('create-info') 35 | ->setDescription('Creates a HTML info page out of a SVG font') 36 | ->addArgument('font-file', InputArgument::REQUIRED, 'path to the SVG font file') 37 | ->addArgument('output-file', InputArgument::REQUIRED, 'path where the HTML page should be saved') 38 | ->addOption('as-list', 'l', InputOption::VALUE_NONE, 'if set the generated HTML file will be a simple unordered list instead of a full HTML document') 39 | ; 40 | } 41 | 42 | /** 43 | * creates a HTML info page out of a SVG font 44 | * 45 | * @param InputInterface $input input 46 | * @param OutputInterface $output output 47 | * @return void 48 | */ 49 | protected function execute(InputInterface $input, OutputInterface $output){ 50 | 51 | $fontFile = realpath($input->getArgument('font-file')); 52 | if($fontFile === false || !file_exists($fontFile)){ 53 | throw new \InvalidArgumentException('"'.$input->getArgument('font-file').'" does not exist'); 54 | } 55 | 56 | $outputFile = $input->getArgument('output-file'); 57 | 58 | $generator = new IconFontGenerator; 59 | 60 | $output->writeln('reading font file from "'.$fontFile.'" ...'); 61 | $generator->generateFromFont(new Font(array(), file_get_contents($fontFile))); 62 | 63 | $output->writeln('writing HTML file to "'.$outputFile.'" ...'); 64 | if ($input->getOption('as-list')) { 65 | $html = $this->getHTMLListFromGenerator($generator, basename($fontFile)); 66 | } 67 | else { 68 | $html = $this->getHTMLFromGenerator($generator, basename($fontFile)); 69 | } 70 | file_put_contents($outputFile, $html); 71 | 72 | $output->getFormatter()->setStyle('success', new OutputFormatterStyle(null, null, array('bold', 'reverse'))); 73 | 74 | $output->writeln('created HTML info page successfully'); 75 | 76 | } 77 | 78 | /** 79 | * creates the HTML for the info page 80 | * 81 | * @param IconFontGenerator $generator icon font generator 82 | * @param string $fontFile font file name 83 | * @return string HTML for the info page 84 | */ 85 | protected function getHTMLFromGenerator(IconFontGenerator $generator, $fontFile){ 86 | 87 | $fontOptions = $generator->getFont()->getOptions(); 88 | 89 | $html = ' 90 | 91 | 92 | '.htmlspecialchars($fontOptions['id']).' 93 | 153 | 154 | 155 |
'; 156 | 157 | $glyphNames = $generator->getGlyphNames(); 158 | asort($glyphNames); 159 | 160 | foreach($glyphNames as $unicode => $glyph){ 161 | $html .= '
162 |
163 |
icon-'.$glyph.'
164 | 165 | 166 | 167 |
'; 168 | } 169 | 170 | $html .= '
171 | 172 | '; 173 | 174 | return $html; 175 | 176 | } 177 | 178 | /** 179 | * creates a HTML list 180 | * 181 | * @param IconFontGenerator $generator icon font generator 182 | * @param string $fontFile font file name 183 | * @return string HTML unordered list 184 | */ 185 | protected function getHTMLListFromGenerator(IconFontGenerator $generator, $fontFile){ 186 | 187 | $fontOptions = $generator->getFont()->getOptions(); 188 | 189 | $html = '
    '; 190 | 191 | $glyphNames = $generator->getGlyphNames(); 192 | asort($glyphNames); 193 | 194 | foreach($glyphNames as $unicode => $glyph){ 195 | $html .= "\n\t" . 196 | '
  • ' . 198 | htmlspecialchars($glyph) . '
  • '; 199 | } 200 | 201 | $html .= "\n" . '
' . "\n"; 202 | 203 | return $html; 204 | 205 | } 206 | 207 | } 208 | -------------------------------------------------------------------------------- /src/MadeYourDay/SVG/IconFontGenerator.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * For the full copyright and license information, please view the LICENSE 6 | * file that was distributed with this source code. 7 | */ 8 | 9 | namespace MadeYourDay\SVG; 10 | 11 | /** 12 | * SVG Icon Font Generator 13 | * 14 | * @author Martin Auswöger 15 | */ 16 | class IconFontGenerator{ 17 | 18 | /** 19 | * @var Font 20 | */ 21 | protected $font; 22 | 23 | /** 24 | * @var array mapping information file path and name mapped to glyph character 25 | */ 26 | protected $mapping = array(); 27 | 28 | /** 29 | * generate a font from a directory containing svg files (one file per glyph) 30 | * by naming convention the file name can specify which character should be mapped to the glyph 31 | * - my-icon.png => creates a glyph with the name 'my-icon' mapped to a character in the Unicode Private Use Area 32 | * - my-icon-x263a.png => creates a glyph with the name 'my-icon' mapped to the unicode character U+263A "☺" 33 | * 34 | * @param string $path SVG path definition (consider the font coordinate system has an inverted y axis) 35 | * @param array $fontOptions font options for the Font object 36 | * @param boolean $renameSourceFiles if true, files without mapping information will be renamed 37 | * @return static this 38 | */ 39 | public function generateFromDir($path, $fontOptions = array(), $renameSourceFiles = false){ 40 | 41 | $this->font = new Font($fontOptions); 42 | $this->mapping = array(); 43 | 44 | $fontOptions = $this->font->getOptions(); 45 | 46 | $files = scandir($path); 47 | foreach($files as $fileName){ 48 | if(strtolower(substr($fileName, -4)) === '.svg'){ 49 | if(preg_match('(^(.*)-x([0-9a-f]{2,6})\\.svg$)i', $fileName, $matches)){ 50 | $iconName = strtolower($matches[1]); 51 | $iconCode = static::hexToUnicode(strtolower($matches[2])); 52 | if(isset($this->mapping[$iconCode])){ 53 | throw new \Exception('Duplicate glyph '.$iconCode.' '.$iconName); 54 | } 55 | $this->mapping[$iconCode] = array( 56 | 'path' => $path.DIRECTORY_SEPARATOR.$fileName, 57 | 'name' => $iconName, 58 | ); 59 | } 60 | } 61 | } 62 | foreach($files as $fileName){ 63 | if(strtolower(substr($fileName, -4)) === '.svg'){ 64 | if(!preg_match('(^(.*)-x([0-9a-f]{2,6})\\.svg$)i', $fileName)){ 65 | $iconName = strtolower(substr($fileName, 0, -4)); 66 | $code = hexdec('e001'); 67 | while(isset($this->mapping[static::hexToUnicode(dechex($code))])){ 68 | $code++; 69 | } 70 | if($renameSourceFiles){ 71 | if(!rename($path.DIRECTORY_SEPARATOR.$fileName, $path.DIRECTORY_SEPARATOR.$iconName.'-x'.dechex($code).'.svg')){ 72 | throw new \Exception('unable to rename "'.$path.DIRECTORY_SEPARATOR.$fileName.'"'); 73 | } 74 | $fileName = $iconName.'-x'.dechex($code).'.svg'; 75 | } 76 | $this->mapping[static::hexToUnicode(dechex($code))] = array( 77 | 'path' => $path.DIRECTORY_SEPARATOR.$fileName, 78 | 'name' => $iconName, 79 | ); 80 | } 81 | } 82 | } 83 | foreach($this->mapping as $code => $icon){ 84 | try{ 85 | $iconDoc = new Document(file_get_contents($icon['path'])); 86 | $viewBox = $iconDoc->getViewBox(); 87 | $this->font->addGlyph( 88 | $code, 89 | $iconDoc->getPath($fontOptions['units-per-em']/$viewBox['height'], null, 'vertical', true, 0, $fontOptions['descent']), 90 | $icon['name'], 91 | round($viewBox['width']*$fontOptions['units-per-em']/$viewBox['height']) 92 | ); 93 | } 94 | catch(\Exception $e){ 95 | throw new \Exception(basename($icon['path']).': '.$e->getMessage()); 96 | } 97 | } 98 | 99 | return $this; 100 | 101 | } 102 | 103 | /** 104 | * generate a font based on a existing SVG font 105 | * 106 | * @param Font $font font object 107 | * @return static this 108 | */ 109 | public function generateFromFont(Font $font){ 110 | 111 | $this->mapping = array(); 112 | $this->font = $font; 113 | return $this; 114 | 115 | } 116 | 117 | /** 118 | * get the font object 119 | * 120 | * @return Font font object 121 | */ 122 | public function getFont(){ 123 | return $this->font; 124 | } 125 | 126 | /** 127 | * get glyph names 128 | * 129 | * @return array array with the characters (in hex) as keys and the glyph names as values 130 | */ 131 | public function getGlyphNames(){ 132 | 133 | $glyphNames = array(); 134 | foreach($this->font->getGlyphs() as $glyph){ 135 | $glyphNames[static::unicodeToHex($glyph['char'])] = empty($glyph['name']) ? null : $glyph['name']; 136 | } 137 | return $glyphNames; 138 | 139 | } 140 | 141 | /** 142 | * get css definition for icon class names 143 | * 144 | * @return string css class definitions 145 | */ 146 | public function getCss(){ 147 | 148 | $css = ''; 149 | foreach($this->getGlyphNames() as $unicode => $name){ 150 | $css .= ".icon-".$name.":before {"."\n"; 151 | $css .= "\tcontent: \"\\".str_replace('-', '\\', $unicode)."\";\n"; 152 | $css .= "}\n"; 153 | } 154 | return $css; 155 | 156 | } 157 | 158 | /** 159 | * save the current font as single svg files in a directory (counterpart of generateFromDir) 160 | * 161 | * @param string $dir directory path 162 | * @return static this 163 | */ 164 | public function saveGlyphsToDir($dir){ 165 | 166 | $fontOptions = $this->font->getOptions(); 167 | 168 | $svgTemplate = ''."\n". 169 | ''."\n". 170 | ''."\n". 183 | ' '."\n". 184 | ' '."\n"; 185 | for($i = 32; $i < 512; $i += 32){ 186 | $color = 'A9CCDB'; 187 | if($i === 448){ 188 | $color = 'FF0000'; 189 | } 190 | $svgTemplate .= ' '."\n"; 191 | } 192 | for($i = 32; $i < 512; $i += 32){ 193 | $svgTemplate .= ' '."\n"; 194 | } 195 | $svgTemplate .= ' '."\n". 196 | ' '."\n". 197 | ''."\n"; 198 | 199 | if(!is_dir($dir)){ 200 | throw new \InvalidArgumentException('$dir must be a writable directory'); 201 | } 202 | 203 | foreach($this->font->getGlyphs() as $glyph){ 204 | 205 | $targetPath = $dir.DIRECTORY_SEPARATOR.(empty($glyph['name']) ? 'icon' : preg_replace('([^a-z0-9]+)i', '-', $glyph['name'])).'-x'.static::unicodeToHex($glyph['char']).'.svg'; 206 | 207 | if(isset($this->mapping[$glyph['char']])){ 208 | 209 | if(!copy($this->mapping[$glyph['char']]['path'], $targetPath)){ 210 | throw new \Exception('unable to copy "'.$this->mapping[$glyph['char']]['path'].'" to "'.$targetPath.'"'); 211 | } 212 | 213 | } 214 | else{ 215 | 216 | $glyphDocument = Document::createFromPath($glyph['path'], empty($fontOptions['horiz-adv-x']) ? $fontOptions['units-per-em'] : $fontOptions['horiz-adv-x'], $fontOptions['units-per-em']); 217 | if(file_put_contents( 218 | $targetPath, 219 | str_replace(array('%%%PATH%%%', '%%%WIDTH%%%'), array( 220 | $glyphDocument->getPath(512/$fontOptions['units-per-em'], null, 'vertical', true, 0, -64), 221 | empty($glyph['width']) ? 512 : ($glyph['width']*512/$fontOptions['units-per-em']) 222 | ), $svgTemplate) 223 | ) === false){ 224 | throw new \Exception('unable to write to "'.$targetPath.'"'); 225 | } 226 | 227 | } 228 | 229 | } 230 | 231 | return $this; 232 | 233 | } 234 | 235 | /** 236 | * get the unicode hex representation of a unicode character 237 | * 238 | * @param string $char single character encoded as UTF-8 239 | * @return string unicode hex representation of the character 240 | */ 241 | public static function unicodeToHex($char){ 242 | 243 | if(!is_string($char) || !mb_strlen($char, 'utf-8')){ 244 | throw new \InvalidArgumentException('$char must be one single character'); 245 | } 246 | 247 | $unicode = unpack('N*', mb_convert_encoding($char, 'UCS-4BE', 'UTF-8')); 248 | 249 | return implode('-', array_map('dechex', $unicode)); 250 | } 251 | 252 | /** 253 | * get the unicode character of a unicode hex string 254 | * 255 | * @param string $char unicode hex representation (e.g. "263a") 256 | * @return string single character encoded as UTF-8 257 | */ 258 | protected static function hexToUnicode($char){ 259 | 260 | if(!is_string($char) || !preg_match('(^[0-9a-f]{2,6}$)i', $char)){ 261 | throw new \InvalidArgumentException('$char must be one single unicode character as hex string'); 262 | } 263 | 264 | return mb_convert_encoding('&#x'.strtolower($char).';', 'UTF-8', 'HTML-ENTITIES'); 265 | 266 | } 267 | 268 | } 269 | -------------------------------------------------------------------------------- /src/MadeYourDay/SVG/Document.php: -------------------------------------------------------------------------------- 1 | 4 | * 5 | * For the full copyright and license information, please view the LICENSE 6 | * file that was distributed with this source code. 7 | */ 8 | 9 | namespace MadeYourDay\SVG; 10 | 11 | use SimpleXMLElement; 12 | 13 | /** 14 | * SVG Document 15 | * 16 | * @author Martin Auswöger 17 | */ 18 | class Document{ 19 | 20 | /** 21 | * @var SimpleXMLElement XML document element 22 | */ 23 | protected $xmlDocument; 24 | 25 | /** 26 | * @var array conversion table between several units and pixels 27 | */ 28 | protected $unitInPixels = array( 29 | 'px' => 1, 30 | 'pt' => 1.25, 31 | 'mm' => 3.543307096633, 32 | 'pc' => 15, 33 | 'cm' => 35.43307096633, 34 | 'in' => 90, 35 | ); 36 | 37 | /** 38 | * creates a Document instance from a xml string 39 | * 40 | * @param string $svgString XML SVG content 41 | */ 42 | public function __construct($svgString){ 43 | 44 | $this->xmlDocument = new SimpleXMLElement($svgString); 45 | 46 | } 47 | 48 | /** 49 | * creates a Document instance from a simple SVG path definition 50 | * 51 | * @param string $path SVG path definition 52 | * @param integer $width document width (default 512) 53 | * @param integer $height document height (default 512) 54 | * @return static Document instance 55 | */ 56 | public static function createFromPath($path, $width = 512, $height = 512){ 57 | 58 | return new static(''. 59 | ''. 60 | ''. 61 | ''. 62 | ''); 63 | 64 | } 65 | 66 | /** 67 | * returns an viewbox array containing x/y offsets and with/height 68 | * 69 | * @return array|null array with x,y,width,height values 70 | */ 71 | public function getViewBox(){ 72 | 73 | if(!empty($this->xmlDocument['viewBox'])){ 74 | 75 | $viewBox = $this->getValuesFromList($this->xmlDocument['viewBox']); 76 | if(count($viewBox) !== 4){ 77 | return null; 78 | } 79 | return array( 80 | 'x' => $viewBox[0]*1, 81 | 'y' => $viewBox[1]*1, 82 | 'width' => $viewBox[2]*1, 83 | 'height' => $viewBox[3]*1, 84 | ); 85 | 86 | } 87 | 88 | if(!empty($this->xmlDocument['width']) && !empty($this->xmlDocument['height'])){ 89 | 90 | $width = trim($this->xmlDocument['width']); 91 | $height = trim($this->xmlDocument['height']); 92 | if(isset($this->unitInPixels[substr($width, -2)])){ 93 | $width = $width*$this->unitInPixels[substr($width, -2)]; 94 | } 95 | if(isset($this->unitInPixels[substr($height, -2)])){ 96 | $height = $height*$this->unitInPixels[substr($height, -2)]; 97 | } 98 | return array( 99 | 'x' => 0, 100 | 'y' => 0, 101 | 'width' => $width*1, 102 | 'height' => $height*1, 103 | ); 104 | 105 | } 106 | 107 | return null; 108 | 109 | } 110 | 111 | /** 112 | * returns one single SVG path definition for all elements in the document 113 | * 114 | * @param float $scale a positive number how much the path should be scaled (1 means 100%) 115 | * @param integer $roundPrecision number of decimal digits to round to or null to disable rounding 116 | * @param string $flip 'none', 'horizontal' or 'vertical' (requires a valid view box) 117 | * @param boolean $onlyFilled ignore non filled objects 118 | * @param integer $xOffset x offset 119 | * @param integer $yOffset y offset 120 | * @return string SVG path definition 121 | */ 122 | public function getPath($scale = 1, $roundPrecision = null, $flip = 'none', $onlyFilled = true, $xOffset = 0, $yOffset = 0){ 123 | 124 | $path = $this->getPathPart($this->xmlDocument, $onlyFilled); 125 | 126 | if($scale !== 1 || $roundPrecision !== null || $flip !== 'none' || $xOffset !== 0 || $yOffset !== 0){ 127 | $path = $this->transformPath($path, $scale, $roundPrecision, $flip, $xOffset / $scale, $yOffset / $scale); 128 | } 129 | 130 | return trim($path); 131 | 132 | } 133 | 134 | /** 135 | * returns one single SVG path definition for all elements in the specified element 136 | * 137 | * @param SimpleXMLElement $xmlElement group or svg element 138 | * @param boolean $onlyFilled ignore non filled objects 139 | * @return string SVG path definition 140 | */ 141 | protected function getPathPart(SimpleXMLElement $xmlElement, $onlyFilled){ 142 | 143 | $path = ''; 144 | 145 | if($xmlElement === null){ 146 | $xmlElement = $this->xmlDocument; 147 | } 148 | 149 | foreach($xmlElement->children() as $child){ 150 | $childName = $child->getName(); 151 | if(!empty($child['transform'])){ 152 | throw new \Exception('Transforms are currently not supported!'); 153 | } 154 | if($childName === 'g'){ 155 | $path .= ' '.$this->getPathPart($child, $onlyFilled); 156 | } 157 | else{ 158 | if($onlyFilled && (string)$child['fill'] === 'none'){ 159 | continue; 160 | } 161 | if($childName === 'polygon'){ 162 | $path .= ' '.$this->getPathFromPolygon($child); 163 | } 164 | elseif($childName === 'rect'){ 165 | $path .= ' '.$this->getPathFromRect($child); 166 | } 167 | elseif($childName === 'circle'){ 168 | $path .= ' '.$this->getPathFromCircle($child); 169 | } 170 | elseif($childName === 'ellipse'){ 171 | $path .= ' '.$this->getPathFromEllipse($child); 172 | } 173 | elseif($childName === 'path'){ 174 | $pathPart = trim($child['d']); 175 | if(substr($pathPart, 0, 1) === 'm'){ 176 | $pathPart = 'M'.substr($pathPart, 1); 177 | } 178 | $path .= ' '.$pathPart; 179 | } 180 | } 181 | } 182 | 183 | return trim($path); 184 | 185 | } 186 | 187 | /** 188 | * transforms a SVG path definition by the given parameters 189 | * 190 | * @param string $path SVG path definition 191 | * @param float $scale a positive number how much the path should be scaled (1 means 100%) 192 | * @param integer $roundPrecision number of decimal digits to round to or null to disable rounding 193 | * @param string $flip 'none', 'horizontal' or 'vertical' (requires a valid view box) 194 | * @param integer $xOffset x offset 195 | * @param integer $yOffset y offset 196 | * @return string SVG path definition 197 | */ 198 | protected function transformPath($path, $scale, $roundPrecision, $flip, $xOffset, $yOffset){ 199 | 200 | if($flip === 'horizontal' || $flip === 'vertical'){ 201 | $viewBox = $this->getViewBox(); 202 | } 203 | 204 | return preg_replace_callback('([m,l,h,v,c,s,q,t,a,z](?:[\\s,]*-?(?=\\.?\\d)\\d*(?:\\.\\d+)?)*)i', function($maches) use ($scale, $roundPrecision, $flip, $xOffset, $yOffset, $viewBox){ 205 | 206 | $command = substr($maches[0], 0, 1); 207 | $absoluteCommand = strtoupper($command) === $command; 208 | $xyCommand = in_array(strtolower($command), array('m','l','c','s','q','t')); 209 | $xCommand = strtolower($command) === 'h'; 210 | $yCommand = strtolower($command) === 'v'; 211 | if(strtolower($command) === 'z'){ 212 | return $command; 213 | } 214 | if(strtolower($command) === 'a'){ 215 | throw new \Exception('Path command "A" is currently not supportet!'); 216 | } 217 | $values = $this->getValuesFromList(substr($maches[0], 1)); 218 | 219 | foreach($values as $key => $value) { 220 | 221 | if( 222 | $flip === 'horizontal' && 223 | ((!($key%2) && $xyCommand) || $xCommand) 224 | ){ 225 | $values[$key] *= -1; 226 | if($absoluteCommand){ 227 | $values[$key] += $viewBox['width']; 228 | } 229 | } 230 | if( 231 | $flip === 'vertical' && 232 | (($key%2 && $xyCommand) || $yCommand) 233 | ){ 234 | $values[$key] *= -1; 235 | if($absoluteCommand){ 236 | $values[$key] += $viewBox['height']; 237 | } 238 | } 239 | if( 240 | $absoluteCommand && 241 | ((!($key%2) && $xyCommand) || $xCommand) 242 | ){ 243 | $values[$key] += $xOffset; 244 | } 245 | if( 246 | $absoluteCommand && 247 | (($key%2 && $xyCommand) || $yCommand) 248 | ){ 249 | $values[$key] += $yOffset; 250 | } 251 | $values[$key] *= $scale; 252 | if($roundPrecision !== null){ 253 | $values[$key] = round($values[$key], $roundPrecision); 254 | } 255 | 256 | } 257 | 258 | return $command.implode(' ', $values); 259 | 260 | }, $path); 261 | 262 | } 263 | 264 | /** 265 | * converts a polygon object to a SVG path definition 266 | * 267 | * @param SimpleXMLElement $polygon polygon element 268 | * @return string SVG path definition 269 | */ 270 | protected function getPathFromPolygon(SimpleXMLElement $polygon){ 271 | 272 | $points = $this->getValuesFromList($polygon['points']); 273 | $path = 'M'.array_shift($points).' '.array_shift($points); 274 | while(count($points)){ 275 | $path .= 'L'.array_shift($points).' '.array_shift($points); 276 | } 277 | return $path.'Z'; 278 | 279 | } 280 | 281 | /** 282 | * converts a rect object to a SVG path definition 283 | * 284 | * @param SimpleXMLElement $rect rect element 285 | * @return string SVG path definition 286 | */ 287 | protected function getPathFromRect(SimpleXMLElement $rect){ 288 | 289 | if(empty($rect['width']) || $rect['width'] < 0 || empty($rect['height']) || $rect['height'] < 0){ 290 | return ''; 291 | } 292 | if(empty($rect['x'])){ 293 | $rect['x'] = 0; 294 | } 295 | if(empty($rect['y'])){ 296 | $rect['y'] = 0; 297 | } 298 | return 'M'.$rect['x'].' '.$rect['y'].'l'.$rect['width'].' 0l0 '.$rect['height'].'l'.(-$rect['width']).' 0Z'; 299 | 300 | } 301 | 302 | /** 303 | * converts a circle object to a SVG path definition 304 | * 305 | * @param SimpleXMLElement $circle circle element 306 | * @return string SVG path definition 307 | */ 308 | protected function getPathFromCircle(SimpleXMLElement $circle){ 309 | 310 | $mult = 0.55228475; 311 | return 312 | 'M'.($circle['cx']-$circle['r']).' '.$circle['cy']. 313 | 'C'.($circle['cx']-$circle['r']).' '.($circle['cy']-$circle['r']*$mult).' '.($circle['cx']-$circle['r']*$mult).' '.($circle['cy']-$circle['r']).' '.$circle['cx'].' '.($circle['cy']-$circle['r']). 314 | 'C'.($circle['cx']+$circle['r']*$mult).' '.($circle['cy']-$circle['r']).' '.($circle['cx']+$circle['r']).' '.($circle['cy']-$circle['r']*$mult).' '.($circle['cx']+$circle['r']).' '.$circle['cy']. 315 | 'C'.($circle['cx']+$circle['r']).' '.($circle['cy']+$circle['r']*$mult).' '.($circle['cx']+$circle['r']*$mult).' '.($circle['cy']+$circle['r']).' '.$circle['cx'].' '.($circle['cy']+$circle['r']). 316 | 'C'.($circle['cx']-$circle['r']*$mult).' '.($circle['cy']+$circle['r']).' '.($circle['cx']-$circle['r']).' '.($circle['cy']+$circle['r']*$mult).' '.($circle['cx']-$circle['r']).' '.$circle['cy']. 317 | 'Z'; 318 | 319 | } 320 | 321 | /** 322 | * converts a ellipse object to a SVG path definition 323 | * 324 | * @param SimpleXMLElement $ellipse ellipse element 325 | * @return string SVG path definition 326 | */ 327 | protected function getPathFromEllipse(SimpleXMLElement $ellipse){ 328 | 329 | $mult = 0.55228475; 330 | return 331 | 'M'.($ellipse['cx']-$ellipse['rx']).' '.$ellipse['cy']. 332 | 'C'.($ellipse['cx']-$ellipse['rx']).' '.($ellipse['cy']-$ellipse['ry']*$mult).' '.($ellipse['cx']-$ellipse['rx']*$mult).' '.($ellipse['cy']-$ellipse['ry']).' '.$ellipse['cx'].' '.($ellipse['cy']-$ellipse['ry']). 333 | 'C'.($ellipse['cx']+$ellipse['rx']*$mult).' '.($ellipse['cy']-$ellipse['ry']).' '.($ellipse['cx']+$ellipse['rx']).' '.($ellipse['cy']-$ellipse['ry']*$mult).' '.($ellipse['cx']+$ellipse['rx']).' '.$ellipse['cy']. 334 | 'C'.($ellipse['cx']+$ellipse['rx']).' '.($ellipse['cy']+$ellipse['ry']*$mult).' '.($ellipse['cx']+$ellipse['rx']*$mult).' '.($ellipse['cy']+$ellipse['ry']).' '.$ellipse['cx'].' '.($ellipse['cy']+$ellipse['ry']). 335 | 'C'.($ellipse['cx']-$ellipse['rx']*$mult).' '.($ellipse['cy']+$ellipse['ry']).' '.($ellipse['cx']-$ellipse['rx']).' '.($ellipse['cy']+$ellipse['ry']*$mult).' '.($ellipse['cx']-$ellipse['rx']).' '.$ellipse['cy']. 336 | 'Z'; 337 | 338 | } 339 | 340 | /** 341 | * converts a string list into single values 342 | * 343 | * Should match 123,-123 1.23-1.23 as four numbers (123, -123, 1.23, -1.23) 344 | * Should match 1.2.3 as two numbers (1.2, 0.3) 345 | * Should match -.1.2.3 as three numbers (-0.1, 0.2, 0.3) 346 | * 347 | * @param string $list 348 | * @return string[] 349 | */ 350 | protected function getValuesFromList($list){ 351 | 352 | if(preg_match_all('(-?(?=\\.?\\d)\\d*(?:\\.\\d+)?)i', $list, $values)){ 353 | return $values[0]; 354 | } 355 | return array(); 356 | 357 | } 358 | 359 | } 360 | --------------------------------------------------------------------------------