├── .gitignore ├── .travis.yml ├── nbproject ├── project.xml └── project.properties ├── phpunit.xml ├── favicon ├── composer.json ├── LICENSE ├── src ├── helpers.php ├── Html.php ├── Config.php └── GenerateCommand.php ├── tests └── HieuLe │ └── Favicon │ ├── ConfigTest.php │ └── HtmlTest.php └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | composer.phar 2 | vendor/ 3 | bin/ 4 | composer.lock 5 | /nbproject/private/ -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.3 5 | - 5.4 6 | - 5.5 7 | - 5.6 8 | - hhvm 9 | 10 | before_script: 11 | - composer self-update 12 | - composer install --prefer-source --no-interaction --dev 13 | 14 | script: phpunit -------------------------------------------------------------------------------- /nbproject/project.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | org.netbeans.modules.php.project 4 | 5 | 6 | favicon 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 13 | 14 | 15 | ./tests/ 16 | 17 | 18 | -------------------------------------------------------------------------------- /favicon: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | add(new GenerateCommand); 34 | $application->run(); 35 | 36 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hieu-le/favicon", 3 | "description": "A configurable PHP solution to auto generate favicons and HTML meta tags from a original PNG file.", 4 | "type": "library", 5 | "keywords": [ 6 | "favicon", 7 | "generate", 8 | "png", 9 | "ico" 10 | ], 11 | "license": "MIT", 12 | "authors": [ 13 | { 14 | "name": "Hieu Le", 15 | "email": "letrunghieu.cse09@gmail.com", 16 | "homepage": "http://www.hieule.info" 17 | } 18 | ], 19 | "require": { 20 | "php": ">=5.3.3", 21 | "imagine/imagine": "~0.1", 22 | "symfony/console": "~2.0" 23 | }, 24 | "suggest": { 25 | "ext-imagick": "To use the binary and generate favicon images" 26 | }, 27 | "bin": [ 28 | "favicon" 29 | ], 30 | "config": { 31 | "bin-dir": "bin" 32 | }, 33 | "autoload": { 34 | "psr-4": { 35 | "HieuLe\\Favicon\\": "src/" 36 | }, 37 | "files": ["src/helpers.php"] 38 | }, 39 | "require-dev": { 40 | "phpunit/phpunit": "~4.0" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Hieu Le Trung 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/helpers.php: -------------------------------------------------------------------------------- 1 | link and meta tags for favicons 28 | * 29 | * @param int $option decide which type of favicon will be included in the final result. Default will be FAVICON_ENABLE_ALL to include any thing. The old apple touch link tags will be exclude with FAVICON_NO_OLD_APPLE bit, the android manifest.json link tag will be exclude with FAVICON_NO_ANDROID bit and the Microsoft Windows and IE msapplication meta tags will be excluded with FAVICON_NO_MS bit. 30 | * @param array $msOptions more information for Windows and IE msapplication meta tags. The input array can have these field: 31 | * 36 | * 37 | * @return string the output HTML 38 | */ 39 | function favicon($option = FAVICON_ENABLE_ALL, array $msOptions = array()) 40 | { 41 | $noOldApple = FAVICON_NO_OLD_APPLE & $option; 42 | $noAndroid = FAVICON_NO_ANDROID & $option; 43 | $noMs = FAVICON_NO_MS & $option; 44 | $tileColor = strtoupper(isset($msOptions['tile_color']) ? $msOptions['tile_color'] : '#ffffff'); 45 | $browserConfigFile = isset($msOptions['browser_config_file']) ? $msOptions['browser_config_file'] : ''; 46 | $appName = isset($msOptions['application_name']) ? $msOptions['application_name'] : ''; 47 | return HieuLe\Favicon\Html::output($noOldApple, $noAndroid, $noMs, $tileColor, $browserConfigFile, $appName); 48 | } 49 | 50 | } 51 | 52 | -------------------------------------------------------------------------------- /tests/HieuLe/Favicon/ConfigTest.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class ConfigTest extends \PHPUnit_Framework_TestCase 11 | { 12 | 13 | public function testGetSizes() 14 | { 15 | $sizes = Config::getSizes(); 16 | $this->assertCount(23, $sizes); 17 | } 18 | 19 | public function testGetSizesNoOldApple() 20 | { 21 | $sizes = Config::getSizes(true); 22 | $this->assertCount(19, $sizes); 23 | $this->assertArrayNotHasKey('apple-touch-icon-57x57.png', $sizes); 24 | $this->assertArrayNotHasKey('apple-touch-icon-60x60.png', $sizes); 25 | $this->assertArrayNotHasKey('apple-touch-icon-72x72.png', $sizes); 26 | $this->assertArrayNotHasKey('apple-touch-icon-114x114.png', $sizes); 27 | } 28 | 29 | public function testgetSizesNoAndroid() 30 | { 31 | $sizes = Config::getSizes(false, true); 32 | $this->assertCount(18, $sizes); 33 | $this->assertArrayNotHasKey('android-chrome-36x36.png', $sizes); 34 | $this->assertArrayNotHasKey('android-chrome-48x48.png', $sizes); 35 | $this->assertArrayNotHasKey('android-chrome-72x72.png', $sizes); 36 | $this->assertArrayNotHasKey('android-chrome-96x96.png', $sizes); 37 | $this->assertArrayNotHasKey('android-chrome-144x144.png', $sizes); 38 | } 39 | 40 | public function testGetSizesNoMs() 41 | { 42 | $sizes = Config::getSizes(false, true); 43 | $this->assertCount(18, $sizes); 44 | $this->assertArrayNotHasKey('mstile-70x70', $sizes); 45 | $this->assertArrayNotHasKey('mstile-144x144', $sizes); 46 | $this->assertArrayNotHasKey('mstile-150x150', $sizes); 47 | $this->assertArrayNotHasKey('mstile-310x310', $sizes); 48 | $this->assertArrayNotHasKey('mstile-310x150', $sizes); 49 | } 50 | 51 | public function testGetSizesNoAll() 52 | { 53 | $sizes = Config::getSizes(true, true, true); 54 | $this->assertCount(9, $sizes); 55 | } 56 | 57 | public function testGetTileSettings() 58 | { 59 | $opt = Config::getTileSettings('mstile-310x310.png'); 60 | $this->assertCount(5, $opt); 61 | $this->assertArrayHasKey('w', $opt); 62 | $this->assertArrayHasKey('h', $opt); 63 | $this->assertArrayHasKey('icon', $opt); 64 | $this->assertArrayHasKey('top', $opt); 65 | $this->assertArrayHasKey('left', $opt); 66 | } 67 | 68 | /** 69 | * @expectedException \RuntimeException 70 | */ 71 | public function testGetTileSettingsError() 72 | { 73 | $opt = Config::getTileSettings('mstile-300x300.png'); 74 | 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /src/Html.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class Html 11 | { 12 | 13 | /** 14 | * Write meta and link tags 15 | * 16 | * @param bool $noOldApple exclude old apple touch link 17 | * @param bool $noAndroid exclude android manifest.json file 18 | * @param bool $noMs exclude msapplication meta tags 19 | * @param tring $tileColor the color of Windows tile 20 | * @param string $browserConfigFile the path to browserconfig.xml file or null to disable this 21 | * @param string $appName the name of application when pinned 22 | * 23 | * @return string 24 | */ 25 | public static function output($noOldApple = false, $noAndroid = false, $noMs = false, $tileColor = '#FFFFFF', $browserConfigFile = '', $appName = '') 26 | { 27 | $result = array(); 28 | if (!$noMs) 29 | { 30 | if (!$browserConfigFile) 31 | { 32 | $result[] = ''; 33 | } 34 | else 35 | { 36 | $result[] = ''; 37 | } 38 | } 39 | if (!$noOldApple) 40 | { 41 | $result[] = ''; 42 | $result[] = ''; 43 | $result[] = ''; 44 | $result[] = ''; 45 | } 46 | $result[] = ''; 47 | $result[] = ''; 48 | $result[] = ''; 49 | $result[] = ''; 50 | $result[] = ''; 51 | $result[] = ''; 52 | $result[] = ''; 53 | if (!$noAndroid) 54 | { 55 | $result[] = ''; 56 | } 57 | if (!$noMs) 58 | { 59 | if ($appName) 60 | { 61 | $result[] = ''; 62 | } 63 | $result[] = ''; 64 | $result[] = ''; 65 | $result[] = ''; 66 | $result[] = ''; 67 | $result[] = ''; 68 | $result[] = ''; 69 | } 70 | 71 | return implode("\n", $result); 72 | } 73 | 74 | } 75 | -------------------------------------------------------------------------------- /nbproject/project.properties: -------------------------------------------------------------------------------- 1 | auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.expand-tabs=false 2 | auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.indent-shift-width=4 3 | auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.spaces-per-tab=4 4 | auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.tab-size=4 5 | auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.text-limit-width=80 6 | auxiliary.org-netbeans-modules-editor-indent.CodeStyle.project.text-line-wrap=none 7 | auxiliary.org-netbeans-modules-editor-indent.CodeStyle.usedProfile=project 8 | auxiliary.org-netbeans-modules-editor-indent.text.javascript.CodeStyle.project.expand-tabs=true 9 | auxiliary.org-netbeans-modules-editor-indent.text.javascript.CodeStyle.project.indent-shift-width=4 10 | auxiliary.org-netbeans-modules-editor-indent.text.javascript.CodeStyle.project.spaces-per-tab=4 11 | auxiliary.org-netbeans-modules-editor-indent.text.javascript.CodeStyle.project.tab-size=4 12 | auxiliary.org-netbeans-modules-editor-indent.text.javascript.CodeStyle.project.text-limit-width=80 13 | auxiliary.org-netbeans-modules-editor-indent.text.javascript.CodeStyle.project.text-line-wrap=none 14 | auxiliary.org-netbeans-modules-editor-indent.text.x-php5.CodeStyle.project.alignMultilineCallArgs=true 15 | auxiliary.org-netbeans-modules-editor-indent.text.x-php5.CodeStyle.project.alignMultilineImplements=true 16 | auxiliary.org-netbeans-modules-editor-indent.text.x-php5.CodeStyle.project.alignMultilineMethodParams=true 17 | auxiliary.org-netbeans-modules-editor-indent.text.x-php5.CodeStyle.project.catchBracePlacement=NEW_LINE 18 | auxiliary.org-netbeans-modules-editor-indent.text.x-php5.CodeStyle.project.classDeclBracePlacement=NEW_LINE 19 | auxiliary.org-netbeans-modules-editor-indent.text.x-php5.CodeStyle.project.expand-tabs=true 20 | auxiliary.org-netbeans-modules-editor-indent.text.x-php5.CodeStyle.project.forBracePlacement=NEW_LINE 21 | auxiliary.org-netbeans-modules-editor-indent.text.x-php5.CodeStyle.project.groupAlignmentArrayInit=true 22 | auxiliary.org-netbeans-modules-editor-indent.text.x-php5.CodeStyle.project.groupAlignmentAssignment=true 23 | auxiliary.org-netbeans-modules-editor-indent.text.x-php5.CodeStyle.project.ifBracePlacement=NEW_LINE 24 | auxiliary.org-netbeans-modules-editor-indent.text.x-php5.CodeStyle.project.indent-shift-width=4 25 | auxiliary.org-netbeans-modules-editor-indent.text.x-php5.CodeStyle.project.methodDeclBracePlacement=NEW_LINE 26 | auxiliary.org-netbeans-modules-editor-indent.text.x-php5.CodeStyle.project.placeCatchOnNewLine=true 27 | auxiliary.org-netbeans-modules-editor-indent.text.x-php5.CodeStyle.project.placeElseOnNewLine=true 28 | auxiliary.org-netbeans-modules-editor-indent.text.x-php5.CodeStyle.project.placeFinallyOnNewLine=true 29 | auxiliary.org-netbeans-modules-editor-indent.text.x-php5.CodeStyle.project.placeWhileOnNewLine=true 30 | auxiliary.org-netbeans-modules-editor-indent.text.x-php5.CodeStyle.project.spaceAfterTypeCast=false 31 | auxiliary.org-netbeans-modules-editor-indent.text.x-php5.CodeStyle.project.spaces-per-tab=4 32 | auxiliary.org-netbeans-modules-editor-indent.text.x-php5.CodeStyle.project.switchBracePlacement=NEW_LINE 33 | auxiliary.org-netbeans-modules-editor-indent.text.x-php5.CodeStyle.project.tab-size=4 34 | auxiliary.org-netbeans-modules-editor-indent.text.x-php5.CodeStyle.project.text-limit-width=80 35 | auxiliary.org-netbeans-modules-editor-indent.text.x-php5.CodeStyle.project.text-line-wrap=none 36 | auxiliary.org-netbeans-modules-editor-indent.text.x-php5.CodeStyle.project.useTraitBodyBracePlacement=NEW_LINE 37 | auxiliary.org-netbeans-modules-editor-indent.text.x-php5.CodeStyle.project.whileBracePlacement=NEW_LINE 38 | include.path=${php.global.include.path} 39 | php.version=PHP_53 40 | source.encoding=UTF-8 41 | src.dir=. 42 | tags.asp=false 43 | tags.short=false 44 | web.root=. 45 | -------------------------------------------------------------------------------- /src/Config.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class Config 11 | { 12 | 13 | /** 14 | * All available sizes 15 | * 16 | * @var array 17 | */ 18 | private static $_sizes = array( 19 | 'favicon-16x16.png' => 16, 20 | 'favicon-32x32.png' => 32, 21 | 'favicon-96x96.png' => 96, 22 | 'android-chrome-36x36.png' => 36, 23 | 'android-chrome-48x48.png' => 48, 24 | 'android-chrome-72x72.png' => 72, 25 | 'android-chrome-96x96.png' => 96, 26 | 'android-chrome-144x144.png' => 144, 27 | 'android-chrome-192x192.png' => 192, 28 | 'mstile-70x70.png' => 70, 29 | 'mstile-144x144.png' => 144, 30 | 'mstile-150x150.png' => 150, 31 | 'mstile-310x310.png' => 310, 32 | 'mstile-310x150.png' => array(310, 150), 33 | 'apple-touch-icon.png' => 57, 34 | 'apple-touch-icon-57x57.png' => 57, 35 | 'apple-touch-icon-60x60.png' => 60, 36 | 'apple-touch-icon-72x72.png' => 72, 37 | 'apple-touch-icon-76x76.png' => 76, 38 | 'apple-touch-icon-114x114.png' => 114, 39 | 'apple-touch-icon-120x120.png' => 120, 40 | 'apple-touch-icon-152x152.png' => 152, 41 | 'apple-touch-icon-180x180.png' => 180, 42 | ); 43 | private static $_tileSettings = array( 44 | 'mstile-70x70.png' => array( 45 | 'w' => 126, 46 | 'h' => 126, 47 | 'icon' => 100, 48 | 'top' => 13, 49 | 'left' => 13, 50 | ), 51 | 'mstile-150x150.png' => array( 52 | 'w' => 270, 53 | 'h' => 270, 54 | 'icon' => 124, 55 | 'top' => 73, 56 | 'left' => 50, 57 | ), 58 | 'mstile-310x310.png' => array( 59 | 'w' => 558, 60 | 'h' => 558, 61 | 'icon' => 260, 62 | 'top' => 149, 63 | 'left' => 128, 64 | ), 65 | 'mstile-310x150.png' => array( 66 | 'w' => 558, 67 | 'h' => 270, 68 | 'icon' => 130, 69 | 'top' => 214, 70 | 'left' => 45, 71 | ), 72 | ); 73 | 74 | /** 75 | * Return sizes from options 76 | * 77 | * @param bool $noOldApple exclude old Apple touch image sizes 78 | * @param bool $noAndroid exclude manifest.json file and images for Androids 79 | * @param bool $noMs exclude images for Windows and IE11 80 | * @return array 81 | */ 82 | public static function getSizes($noOldApple = false, $noAndroid = false, $noMs = false) 83 | { 84 | $result = array_merge(self::$_sizes, array()); 85 | if ($noOldApple) 86 | { 87 | unset($result['apple-touch-icon-57x57.png']); 88 | unset($result['apple-touch-icon-60x60.png']); 89 | unset($result['apple-touch-icon-72x72.png']); 90 | unset($result['apple-touch-icon-114x114.png']); 91 | } 92 | if ($noAndroid) 93 | { 94 | unset($result['android-chrome-36x36.png']); 95 | unset($result['android-chrome-48x48.png']); 96 | unset($result['android-chrome-72x72.png']); 97 | unset($result['android-chrome-96x96.png']); 98 | unset($result['android-chrome-144x144.png']); 99 | } 100 | if ($noMs) 101 | { 102 | unset($result['mstile-70x70.png']); 103 | unset($result['mstile-144x144.png']); 104 | unset($result['mstile-150x150.png']); 105 | unset($result['mstile-310x310.png']); 106 | unset($result['mstile-310x150.png']); 107 | } 108 | return $result; 109 | } 110 | 111 | /** 112 | * Get the settings for Windows tile image size 113 | * 114 | * @param type $name 115 | * @return array 116 | * @throws \RuntimeException 117 | */ 118 | public static function getTileSettings($name) 119 | { 120 | if (!isset(self::$_tileSettings[$name])) 121 | { 122 | throw new \RuntimeException('Invalid image name'); 123 | } 124 | return self::$_tileSettings[$name]; 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # favicon 2 | 3 | [![Build Status](https://travis-ci.org/letrunghieu/favicon.svg?branch=master)](https://travis-ci.org/letrunghieu/favicon) [![Latest Stable Version](https://poser.pugx.org/hieu-le/favicon/v/stable.svg)](https://packagist.org/packages/hieu-le/favicon) [![Total Downloads](https://poser.pugx.org/hieu-le/favicon/downloads.svg)](https://packagist.org/packages/hieu-le/favicon) [![Latest Unstable Version](https://poser.pugx.org/hieu-le/favicon/v/unstable.svg)](https://packagist.org/packages/hieu-le/favicon) [![License](https://poser.pugx.org/hieu-le/favicon/license.svg)](https://packagist.org/packages/hieu-le/favicon) 4 | 5 | A configurable PHP solution to auto generate favicons and HTML tags from a original PNG file. 6 | 7 | ## What does this package do? 8 | 9 | In short, it will help you display correct favicon for your website with just **one** original PNG image. 10 | 11 | In more details, it supports: 12 | 13 | * create **one** ICO file and **many** PNG files with many favicon sizes from just **one** original PNG image as well as a `manifest.json` file for Android devices. Both input file path and output folder (which contains images and json files) are configurable via a command line interface. 14 | * Generate suitable `meta` and `link` tags for desktop web browsers as well as mobile touch devices to properly display favicon. 15 | 16 | ## Installation 17 | 18 | We need [PHP imagick extension](http://php.net/manual/en/book.imagick.php) or [PHP GD extension](http://php.net/manual/en/book.image.php) for generating images. By default, the Imagick extension is loaded, if you cannot install it, you can switch to using GD ~~via command line option~~ if available. 19 | 20 | You will need (Composer)[] to use this package. After install Composer, add this dependency into your `composer.json` file. 21 | 22 | ``` 23 | "hieu-le/favicon" : "~1.0" 24 | ``` 25 | 26 | Run `composer update` and start using. 27 | 28 | ## Generate images 29 | 30 | To use the command line to to generate favicon files: 31 | 32 | ``` 33 | $ vendor/bin/favicon generate [--ico-64] [--ico-48] [--no-old-apple] [--no-android] [--no-ms] [--app-name="..."] input [output] 34 | ``` 35 | 36 | Arguments: 37 | 38 | * `input`: path to the input image files, which is required 39 | * `output`: path to the folder which contains output files. If this folder does not exist, the package will try to create it. This argument is optional, default value is current folder. 40 | 41 | Options: 42 | 43 | * ~~`--use-gd`: use GD extension instead of Imagick extension~~ The Imagick ext is used by default. GD library is used if cannot load Imagick ext. 44 | * `--ico-64`: include the 64x64 image inside the output ICO file (which contains only 16x16 and 32x32 images by default) 45 | * `--ico-48`: include the 48x48 image inside the output ICO file (which contains only 16x16 and 32x32 images by default). Both `--ico-48` and `--ico-64` options make the output icon file larger a lot. 46 | * `--no-old-apple`: exclude pngs files that used by old Apple touch devices 47 | * `--no-android`: exclude `manifest.json` files and PNG files for Android devices 48 | * `--no-ms`: exclude images for Windows tile 49 | * `--app-name="..."` set the application name in the `manifest.json` file. Default is an empty string. 50 | 51 | ## Output HTML tags 52 | 53 | Call the `favicon` function inside your HTML template as follow: 54 | 55 | ```php 56 | echo favicon($option = FAVICON_ENABLE_ALL, array $msOptions = array()) 57 | ``` 58 | 59 | The `$option` argument is a bitmask with following bit: 60 | 61 | * `FAVICON_NO_OLD_APPLE` : do not include old apple touch `link` tags 62 | * `FAVICON_NO_ANDROID` : do not include Android `manifest.xml` link tag 63 | * `FAVICON_NO_MS` : do not include Windows and IE `meta` tags 64 | 65 | The default value is `FAVICON_ENABLE_ALL` turns of all these three bit and include everything in the final output. Here are some examples: 66 | 67 | * To exclude old apple touch `link` tags: `FAVICON_NO_OLD_APPLE` 68 | * To exclude Android manifest file and IE `meta` tags: `FAVICON_NO_ANDROID | FAVICON_NO_MS` 69 | * To exclude all these additional tags: `FAVICON_NO_OLD_APPLE | FAVICON_NO_ANDROID | FAVICON_NO_MS` 70 | 71 | The `$msOptions` argument is an array contains information for Windows and IE. It can has these fields: 72 | 73 | * `tile_color`: the background of live tile when this site is pinned, default is white (`#ffffff`) 74 | * `browser_config_file`: the path to browser config XML file if you have it. By default, it is set to an empty string to prevent IE from auto looking `browserconfig.xml` file 75 | * `application_name`: the default application name displayed when user pinned this site 76 | 77 | The result of 78 | 79 | ```php 80 | echo favicon(FAVICON_ENABLE_ALL, array( 81 | 'tile_color' => '#F0F0F0', 82 | 'browser_config_file' => 'IEConfig.xml', 83 | 'application_name' => 'Hieu Le Favicon' 84 | )); 85 | ``` 86 | is a HTML segment link this: 87 | 88 | ```html 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | ``` 110 | 111 | 112 | ## License 113 | 114 | The package is released under MIT license. See the [`LICENSE`](https://github.com/letrunghieu/favicon/blob/master/LICENSE) file for more detail. 115 | -------------------------------------------------------------------------------- /src/GenerateCommand.php: -------------------------------------------------------------------------------- 1 | 15 | */ 16 | class GenerateCommand extends Command 17 | { 18 | 19 | /** 20 | * Configuration object 21 | * 22 | * @var Config 23 | */ 24 | private $_config; 25 | 26 | /** 27 | * Input file path 28 | * 29 | * @var string 30 | */ 31 | private $_inputFile; 32 | 33 | /** 34 | * Output folder which contains ico and png files 35 | * 36 | */ 37 | private $_outputFolder; 38 | 39 | /** 40 | * Imagine object 41 | * 42 | * @var \Imagine\Image\AbstractImagine 43 | */ 44 | private $_imagine; 45 | 46 | /** 47 | * Include the 64x64 image in the ICO or not 48 | * 49 | * @var bool 50 | */ 51 | private $_use64Icon; 52 | 53 | /** 54 | * Include the 48x48 image in the ICO or not 55 | * 56 | * @var bool 57 | */ 58 | private $_use48Icon; 59 | 60 | /** 61 | * Exclude old apple touch images 62 | * 63 | * @var type 64 | */ 65 | private $_noOldApple; 66 | 67 | /** 68 | * Exclude manifest.json and Android images 69 | * 70 | * @var type 71 | */ 72 | private $_noAndroid; 73 | 74 | /** 75 | * Exclude Windows and IE tile images 76 | * 77 | * @var type 78 | */ 79 | private $_noMs; 80 | 81 | /** 82 | * Android manifest app name 83 | * 84 | * @var string 85 | */ 86 | private $_appName; 87 | 88 | protected function configure() 89 | { 90 | $this 91 | ->setName('generate') 92 | ->setDescription('Generate favicons from an original PNG image') 93 | ->addArgument('input', InputArgument::REQUIRED, 'Input PNG image') 94 | ->addArgument('output', InputArgument::OPTIONAL, 'Output folder', './') 95 | ->addOption('ico-64', null, InputOption::VALUE_NONE, 'Include 64x64 image in the ICO file (larger file size)') 96 | ->addOption('ico-48', null, InputOption::VALUE_NONE, 'Include 48x48 image in the ICO file (larger file size)') 97 | ->addOption('no-old-apple', null, InputOption::VALUE_NONE, 'Exclude old apple touch images') 98 | ->addOption('no-android', null, InputOption::VALUE_NONE, 'Exclude manifest.json and Android images') 99 | ->addOption('no-ms', null, InputOption::VALUE_NONE, 'Exclude Windows and IE tile images') 100 | ->addOption('app-name', null, InputOption::VALUE_REQUIRED, 'Android manifest app name', "") 101 | ; 102 | } 103 | 104 | protected function execute(InputInterface $input, OutputInterface $output) 105 | { 106 | $this->_prepare($input, $output); 107 | 108 | $this->_generateIco($output); 109 | 110 | $this->_generatePngs($output); 111 | 112 | if (!$this->_noAndroid) 113 | { 114 | $this->_generateManifestJson($output); 115 | } 116 | } 117 | 118 | private function _prepare(InputInterface $input, OutputInterface $output) 119 | { 120 | $this->_imagine = $this->_getImagine($output); 121 | $this->_outputFolder = rtrim($input->getArgument('output'), " /") . "/"; 122 | $this->_inputFile = $input->getArgument('input'); 123 | $this->_use64Icon = $input->getOption('ico-64'); 124 | $this->_use48Icon = $input->getOption('ico-48'); 125 | $this->_noOldApple = $input->getOption('no-old-apple'); 126 | $this->_noAndroid = $input->getOption('no-android'); 127 | $this->_noMs = $input->getOption('no-ms'); 128 | $this->_appName = $input->getOption('app-name'); 129 | 130 | if (!file_exists($this->_inputFile) && !is_file($this->_inputFile)) 131 | { 132 | $output->writeln("Input file does not exist: {$this->_inputFile}"); 133 | } 134 | 135 | if (!file_exists($this->_outputFolder) || !is_dir($this->_outputFolder)) 136 | { 137 | mkdir($this->_outputFolder, 0755, true); 138 | } 139 | } 140 | 141 | /** 142 | * Generate ICO icon 143 | * 144 | * @param OutputInterface $output 145 | * 146 | * @return bool 147 | */ 148 | private function _generateIco(OutputInterface $output) 149 | { 150 | $filename = $this->_outputFolder . "favicon.ico"; 151 | $output->writeln("Creating: {$filename}"); 152 | 153 | 154 | $originalImage = $this->_imagine->open($this->_inputFile)->strip(); 155 | $originalImage->copy() 156 | ->resize(new \Imagine\Image\Box(16, 16)) 157 | ->save('tmp-16.bmp'); 158 | 159 | $icon = $this->_imagine->open('tmp-16.bmp'); 160 | $icon->layers() 161 | ->add($originalImage->copy()->resize(new \Imagine\Image\Box(32, 32))); 162 | 163 | if ($this->_use48Icon) 164 | { 165 | $icon->layers()->add($originalImage->copy()->resize(new \Imagine\Image\Box(48, 48))); 166 | } 167 | if ($this->_use64Icon) 168 | { 169 | $icon->layers()->add($originalImage->copy()->resize(new \Imagine\Image\Box(64, 64))); 170 | } 171 | 172 | $icon->save($filename, array( 173 | 'flatten' => false 174 | )); 175 | 176 | unlink('tmp-16.bmp'); 177 | return true; 178 | } 179 | 180 | private function _generatePngs(OutputInterface $output) 181 | { 182 | $sizes = Config::getSizes($this->_noOldApple, $this->_noAndroid, $this->_noMs); 183 | $originalImage = $this->_imagine->open($this->_inputFile)->strip(); 184 | $pallete = new \Imagine\Image\Palette\RGB(); 185 | $background = $pallete->color('#000', 0); 186 | foreach ($sizes as $imageName => $size) 187 | { 188 | $output->writeln("Creating: {$imageName}"); 189 | $imagePath = $this->_outputFolder . $imageName; 190 | if (substr($imageName, 0, 6) == 'mstile' && $size != 144) 191 | { 192 | $opt = Config::getTileSettings($imageName); 193 | $tile = $this->_imagine->create(new \Imagine\Image\Box($opt['w'], $opt['h']), $background); 194 | $tile->paste( 195 | $originalImage->copy()->resize(new \Imagine\Image\Box($opt['icon'], $opt['icon'])), new \Imagine\Image\Point($opt['top'], $opt['left']) 196 | ); 197 | $tile->save($imagePath); 198 | } 199 | else 200 | { 201 | $originalImage->copy() 202 | ->resize(new \Imagine\Image\Box($size, $size)) 203 | ->save($imagePath); 204 | } 205 | } 206 | } 207 | 208 | private function _generateManifestJson(OutputInterface $output) 209 | { 210 | $output->writeln("Creating: manifest.json"); 211 | $manifest = array( 212 | 'name' => $this->_appName, 213 | 'icons' => array(), 214 | ); 215 | foreach (array(36, 48, 72, 96, 144, 192) as $size) 216 | { 217 | $manifest['icons'][] = array( 218 | 'src' => "/android-chrome-{$size}x{$size}.png", 219 | 'sizes' => "{$size}x{$size}", 220 | 'type' => "image/png", 221 | 'density' => round($size / 48.0, 2) 222 | ); 223 | } 224 | $json = json_encode($manifest, JSON_PRETTY_PRINT); 225 | $jsonFilePath = $this->_outputFolder . "manifest.json"; 226 | file_put_contents($jsonFilePath, $json); 227 | } 228 | 229 | /** 230 | * Get the imagine object based on the options. Default will use Imagick 231 | * extension 232 | * 233 | * @param InputInterface $input 234 | * @param OutputInterface $output 235 | * @return \Imagine\Image\AbstractImagine 236 | */ 237 | private function _getImagine(OutputInterface $output) 238 | { 239 | // Use Imagick extension by default 240 | if (extension_loaded('imagick') && class_exists("Imagick")) 241 | { 242 | $output->writeln('Imagick library is used!'); 243 | return new \Imagine\Imagick\Imagine(); 244 | } 245 | // fall back to GD 246 | if (extension_loaded('gd') && function_exists('gd_info')) 247 | { 248 | $output->writeln('GD library is used!'); 249 | return new \Imagine\Gd\Imagine(); 250 | } 251 | // Quit if can not find both extensions 252 | $output->writeln("The `imagick` or `gd` extension must be loaded to generate image"); 253 | exit(1); 254 | } 255 | 256 | } 257 | -------------------------------------------------------------------------------- /tests/HieuLe/Favicon/HtmlTest.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class HtmlTest extends \PHPUnit_Framework_TestCase 11 | { 12 | 13 | public function testOutputNoConfig() 14 | { 15 | $expected = array( 16 | '', 17 | '', 18 | '', 19 | '', 20 | '', 21 | '', 22 | '', 23 | '', 24 | '', 25 | '', 26 | '', 27 | '', 28 | '', 29 | '', 30 | '', 31 | '', 32 | '', 33 | '', 34 | '', 35 | ); 36 | $html = Html::output(); 37 | $this->assertEquals(implode("\n", $expected), $html); 38 | } 39 | 40 | public function testOutputNoOldApple() 41 | { 42 | $expected = array( 43 | '', 44 | '', 45 | '', 46 | '', 47 | '', 48 | '', 49 | '', 50 | '', 51 | '', 52 | '', 53 | '', 54 | '', 55 | '', 56 | '', 57 | '', 58 | ); 59 | $html = Html::output(true); 60 | $this->assertEquals(implode("\n", $expected), $html); 61 | } 62 | 63 | public function testOutputNoAndroid() 64 | { 65 | $expected = array( 66 | '', 67 | '', 68 | '', 69 | '', 70 | '', 71 | '', 72 | '', 73 | '', 74 | '', 75 | '', 76 | '', 77 | '', 78 | '', 79 | '', 80 | '', 81 | '', 82 | '', 83 | '', 84 | ); 85 | $html = Html::output(false, true); 86 | $this->assertEquals(implode("\n", $expected), $html); 87 | } 88 | 89 | public function testOutputNoMs() 90 | { 91 | $expected = array( 92 | '', 93 | '', 94 | '', 95 | '', 96 | '', 97 | '', 98 | '', 99 | '', 100 | '', 101 | '', 102 | '', 103 | '', 104 | ); 105 | $html = Html::output(false, false, true); 106 | $this->assertEquals(implode("\n", $expected), $html); 107 | } 108 | 109 | public function testOutputFullMsInfo() 110 | { 111 | $expected = array( 112 | '', 113 | '', 114 | '', 115 | '', 116 | '', 117 | '', 118 | '', 119 | '', 120 | '', 121 | '', 122 | '', 123 | '', 124 | '', 125 | '', 126 | '', 127 | '', 128 | '', 129 | '', 130 | '', 131 | '', 132 | ); 133 | $html = Html::output(false, false, false, '#F0F0F0', 'IEConfig.xml', 'Foo App'); 134 | $this->assertEquals(implode("\n", $expected), $html); 135 | } 136 | 137 | public function testHelperFunctionWithMs() 138 | { 139 | $expected = array( 140 | '', 141 | '', 142 | '', 143 | '', 144 | '', 145 | '', 146 | '', 147 | '', 148 | '', 149 | '', 150 | '', 151 | '', 152 | '', 153 | '', 154 | '', 155 | '', 156 | '', 157 | '', 158 | '', 159 | '', 160 | ); 161 | $html = favicon(FAVICON_ENABLE_ALL, array( 162 | 'tile_color' => '#F0F0F0', 163 | 'browser_config_file' => 'IEConfig.xml', 164 | 'application_name' => 'Foo App' 165 | )); 166 | $this->assertEquals(implode("\n", $expected), $html); 167 | } 168 | 169 | public function testHelperFunctionMinimal() 170 | { 171 | $expected = array( 172 | '', 173 | '', 174 | '', 175 | '', 176 | '', 177 | '', 178 | '', 179 | ); 180 | $html = favicon(FAVICON_NO_OLD_APPLE | FAVICON_NO_ANDROID | FAVICON_NO_MS); 181 | $this->assertEquals(implode("\n", $expected), $html); 182 | } 183 | 184 | } 185 | --------------------------------------------------------------------------------