Răsfoiți Sursa

Merge branch 'master' into php8

El RIDO 3 ani în urmă
părinte
comite
c44b252aa8
52 a modificat fișierele cu 6932 adăugiri și 474 ștergeri
  1. 1 0
      CHANGELOG.md
  2. 5 4
      cfg/conf.sample.php
  3. 2 1
      composer.json
  4. 251 442
      composer.lock
  5. 3 3
      i18n/co.json
  6. 1 1
      lib/Configuration.php
  7. 12 1
      lib/Model/Comment.php
  8. 10 3
      tst/ModelTest.php
  9. 142 15
      vendor/composer/ClassLoader.php
  10. 350 0
      vendor/composer/InstalledVersions.php
  11. 39 0
      vendor/composer/autoload_classmap.php
  12. 1 0
      vendor/composer/autoload_psr4.php
  13. 11 4
      vendor/composer/autoload_real.php
  14. 47 0
      vendor/composer/autoload_static.php
  15. 59 0
      vendor/composer/installed.php
  16. 26 0
      vendor/composer/platform_check.php
  17. 130 0
      vendor/jdenticon/jdenticon/src/Canvas/Canvas.php
  18. 408 0
      vendor/jdenticon/jdenticon/src/Canvas/CanvasContext.php
  19. 228 0
      vendor/jdenticon/jdenticon/src/Canvas/ColorUtils.php
  20. 141 0
      vendor/jdenticon/jdenticon/src/Canvas/Matrix.php
  21. 92 0
      vendor/jdenticon/jdenticon/src/Canvas/Png/PngBuffer.php
  22. 238 0
      vendor/jdenticon/jdenticon/src/Canvas/Png/PngEncoder.php
  23. 92 0
      vendor/jdenticon/jdenticon/src/Canvas/Png/PngPalette.php
  24. 41 0
      vendor/jdenticon/jdenticon/src/Canvas/Point.php
  25. 44 0
      vendor/jdenticon/jdenticon/src/Canvas/Rasterization/Edge.php
  26. 27 0
      vendor/jdenticon/jdenticon/src/Canvas/Rasterization/EdgeIntersection.php
  27. 24 0
      vendor/jdenticon/jdenticon/src/Canvas/Rasterization/EdgeSuperSampleIntersection.php
  28. 158 0
      vendor/jdenticon/jdenticon/src/Canvas/Rasterization/EdgeTable.php
  29. 30 0
      vendor/jdenticon/jdenticon/src/Canvas/Rasterization/Layer.php
  30. 150 0
      vendor/jdenticon/jdenticon/src/Canvas/Rasterization/LayerManager.php
  31. 379 0
      vendor/jdenticon/jdenticon/src/Canvas/Rasterization/Rasterizer.php
  32. 198 0
      vendor/jdenticon/jdenticon/src/Canvas/Rasterization/SuperSampleBuffer.php
  33. 27 0
      vendor/jdenticon/jdenticon/src/Canvas/Rasterization/SuperSampleRange.php
  34. 605 0
      vendor/jdenticon/jdenticon/src/Color.php
  35. 492 0
      vendor/jdenticon/jdenticon/src/Identicon.php
  36. 460 0
      vendor/jdenticon/jdenticon/src/IdenticonStyle.php
  37. 209 0
      vendor/jdenticon/jdenticon/src/Rendering/AbstractRenderer.php
  38. 84 0
      vendor/jdenticon/jdenticon/src/Rendering/ColorTheme.php
  39. 290 0
      vendor/jdenticon/jdenticon/src/Rendering/IconGenerator.php
  40. 296 0
      vendor/jdenticon/jdenticon/src/Rendering/ImagickRenderer.php
  41. 123 0
      vendor/jdenticon/jdenticon/src/Rendering/InternalPngRenderer.php
  42. 54 0
      vendor/jdenticon/jdenticon/src/Rendering/Point.php
  43. 60 0
      vendor/jdenticon/jdenticon/src/Rendering/Rectangle.php
  44. 141 0
      vendor/jdenticon/jdenticon/src/Rendering/RendererInterface.php
  45. 83 0
      vendor/jdenticon/jdenticon/src/Rendering/SvgPath.php
  46. 133 0
      vendor/jdenticon/jdenticon/src/Rendering/SvgRenderer.php
  47. 79 0
      vendor/jdenticon/jdenticon/src/Rendering/Transform.php
  48. 35 0
      vendor/jdenticon/jdenticon/src/Rendering/TriangleDirection.php
  49. 62 0
      vendor/jdenticon/jdenticon/src/Shapes/Shape.php
  50. 71 0
      vendor/jdenticon/jdenticon/src/Shapes/ShapeCategory.php
  51. 243 0
      vendor/jdenticon/jdenticon/src/Shapes/ShapeDefinitions.php
  52. 45 0
      vendor/jdenticon/jdenticon/src/Shapes/ShapePosition.php

+ 1 - 0
CHANGELOG.md

@@ -3,6 +3,7 @@
   * **1.4.1 (not yet released)**
     * ADDED: Translations for Turkish, Slovak and Greek
     * ADDED: S3 Storage backend (#994)
+    * CHANGED: Switched to Jdenticons as the default for comment icons (#793)
     * CHANGED: Avoid `SUPER` privilege for setting the `sql_mode` for MariaDB/MySQL (#919)
     * CHANGED: Upgrading libraries to: zlib 1.2.13
     * FIXED: Revert to CREATE INDEX without IF NOT EXISTS clauses, to support MySQL (#943)

+ 5 - 4
cfg/conf.sample.php

@@ -65,10 +65,11 @@ languageselection = false
 ; qrcode = true
 
 ; (optional) IP based icons are a weak mechanism to detect if a comment was from
-; a different user when the same username was used in a comment. It might be
-; used to get the IP of a non anonymous comment poster if the server salt is
-; leaked and a SHA256 HMAC rainbow table is generated for all (relevant) IPs.
-; Can be set to one these values: "none" / "vizhash" / "identicon" (default).
+; a different user when the same username was used in a comment. It might get
+; used to get the IP of a comment poster if the server salt is leaked and a
+; SHA512 HMAC rainbow table is generated for all (relevant) IPs.
+; Can be set to one these values:
+; "none" / "vizhash" / "identicon" / "jdenticon" (default).
 ; icon = "none"
 
 ; Content Security Policy headers allow a website to restrict what sources are

+ 2 - 1
composer.json

@@ -27,7 +27,8 @@
 		"php" : "^5.6.0 || ^7.0 || ^8.0",
 		"paragonie/random_compat" : "2.0.21",
 		"yzalis/identicon" : "2.0.0",
-		"mlocati/ip-lib" : "1.18.0"
+		"mlocati/ip-lib" : "1.18.0",
+		"jdenticon/jdenticon": "^1.0"
 	},
 	"suggest" : {
 		"google/cloud-storage" : "1.26.1",

Fișier diff suprimat deoarece este prea mare
+ 251 - 442
composer.lock


+ 3 - 3
i18n/co.json

@@ -187,7 +187,7 @@
     "URL shortener may expose your decrypt key in URL.": "Un ammuzzatore d’indirizzu pò palisà a vostra chjave di dicifratura in l’indirizzu.",
     "Save paste": "Arregistrà l’appiccicu",
     "Your IP is not authorized to create pastes.": "U vostru indirizzu IP ùn hè micca auturizatu à creà l’appiccichi.",
-    "Trying to shorten a URL that isn't pointing at our instance.": "Trying to shorten a URL that isn't pointing at our instance.",
-    "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".",
-    "Error parsing YOURLS response.": "Error parsing YOURLS response."
+    "Trying to shorten a URL that isn't pointing at our instance.": "Pruvate d’ammuzzà un indirizzu web chì ùn punta micca versu a vostra instanza.",
+    "Error calling YOURLS. Probably a configuration issue, like wrong or missing \"apiurl\" or \"signature\".": "Sbagliu à a chjama di YOURLS. Seria forse una cunfigurazione gattiva, tale una \"apiurl\" o \"signature\" falsa o assente.",
+    "Error parsing YOURLS response.": "Sbagliu durante l’analisa di a risposta di YOURLS."
 }

+ 1 - 1
lib/Configuration.php

@@ -53,7 +53,7 @@ class Configuration
             'languagedefault'          => '',
             'urlshortener'             => '',
             'qrcode'                   => true,
-            'icon'                     => 'identicon',
+            'icon'                     => 'jdenticon',
             'cspheader'                => 'default-src \'none\'; base-uri \'self\'; form-action \'none\'; manifest-src \'self\'; connect-src * blob:; script-src \'self\' \'unsafe-eval\'; style-src \'self\'; font-src \'self\'; frame-ancestors \'none\'; img-src \'self\' data: blob:; media-src blob:; object-src blob:; sandbox allow-same-origin allow-scripts allow-forms allow-popups allow-modals allow-downloads',
             'zerobincompatibility'     => false,
             'httpwarning'              => true,

+ 12 - 1
lib/Model/Comment.php

@@ -14,6 +14,7 @@ namespace PrivateBin\Model;
 
 use Exception;
 use Identicon\Identicon;
+use Jdenticon\Identicon as Jdenticon;
 use PrivateBin\Persistence\TrafficLimiter;
 use PrivateBin\Vizhash16x16;
 
@@ -164,7 +165,17 @@ class Comment extends AbstractModel
         if ($icon != 'none') {
             $pngdata = '';
             $hmac    = TrafficLimiter::getHash();
-            if ($icon == 'identicon') {
+            if ($icon == 'jdenticon') {
+                $jdenticon = new Jdenticon(array(
+                    'hash'  => $hmac,
+                    'size'  => 16,
+                    'style' => array(
+                        'backgroundColor'   => '#fff0', // fully transparent, for dark mode
+                        'padding'           => 0,
+                    ),
+                ));
+                $pngdata   = $jdenticon->getImageDataUri('png');
+            } elseif ($icon == 'identicon') {
                 $identicon = new Identicon();
                 $pngdata   = $identicon->getImageDataUri($hmac, 16);
             } elseif ($icon == 'vizhash') {

+ 10 - 3
tst/ModelTest.php

@@ -1,6 +1,6 @@
 <?php
 
-use Identicon\Identicon;
+use Jdenticon\Identicon;
 use PHPUnit\Framework\TestCase;
 use PrivateBin\Configuration;
 use PrivateBin\Data\Database;
@@ -307,8 +307,15 @@ class ModelTest extends TestCase
         $comment->get();
         $comment->store();
 
-        $identicon = new Identicon();
-        $pngdata   = $identicon->getImageDataUri(TrafficLimiter::getHash(), 16);
+        $identicon = new Identicon(array(
+            'hash'  => TrafficLimiter::getHash(),
+            'size'  => 16,
+            'style' => array(
+                'backgroundColor'   => '#fff0', // fully transparent, for dark mode
+                'padding'           => 0,
+            ),
+        ));
+        $pngdata   = $identicon->getImageDataUri('png');
         $comment   = current($this->_model->getPaste(Helper::getPasteId())->get()['comments']);
         $this->assertEquals($pngdata, $comment['meta']['icon'], 'icon gets set');
     }

+ 142 - 15
vendor/composer/ClassLoader.php

@@ -37,57 +37,130 @@ namespace Composer\Autoload;
  *
  * @author Fabien Potencier <fabien@symfony.com>
  * @author Jordi Boggiano <j.boggiano@seld.be>
- * @see    http://www.php-fig.org/psr/psr-0/
- * @see    http://www.php-fig.org/psr/psr-4/
+ * @see    https://www.php-fig.org/psr/psr-0/
+ * @see    https://www.php-fig.org/psr/psr-4/
  */
 class ClassLoader
 {
+    /** @var ?string */
+    private $vendorDir;
+
     // PSR-4
+    /**
+     * @var array[]
+     * @psalm-var array<string, array<string, int>>
+     */
     private $prefixLengthsPsr4 = array();
+    /**
+     * @var array[]
+     * @psalm-var array<string, array<int, string>>
+     */
     private $prefixDirsPsr4 = array();
+    /**
+     * @var array[]
+     * @psalm-var array<string, string>
+     */
     private $fallbackDirsPsr4 = array();
 
     // PSR-0
+    /**
+     * @var array[]
+     * @psalm-var array<string, array<string, string[]>>
+     */
     private $prefixesPsr0 = array();
+    /**
+     * @var array[]
+     * @psalm-var array<string, string>
+     */
     private $fallbackDirsPsr0 = array();
 
+    /** @var bool */
     private $useIncludePath = false;
+
+    /**
+     * @var string[]
+     * @psalm-var array<string, string>
+     */
     private $classMap = array();
+
+    /** @var bool */
     private $classMapAuthoritative = false;
+
+    /**
+     * @var bool[]
+     * @psalm-var array<string, bool>
+     */
     private $missingClasses = array();
+
+    /** @var ?string */
     private $apcuPrefix;
 
+    /**
+     * @var self[]
+     */
+    private static $registeredLoaders = array();
+
+    /**
+     * @param ?string $vendorDir
+     */
+    public function __construct($vendorDir = null)
+    {
+        $this->vendorDir = $vendorDir;
+    }
+
+    /**
+     * @return string[]
+     */
     public function getPrefixes()
     {
         if (!empty($this->prefixesPsr0)) {
-            return call_user_func_array('array_merge', $this->prefixesPsr0);
+            return call_user_func_array('array_merge', array_values($this->prefixesPsr0));
         }
 
         return array();
     }
 
+    /**
+     * @return array[]
+     * @psalm-return array<string, array<int, string>>
+     */
     public function getPrefixesPsr4()
     {
         return $this->prefixDirsPsr4;
     }
 
+    /**
+     * @return array[]
+     * @psalm-return array<string, string>
+     */
     public function getFallbackDirs()
     {
         return $this->fallbackDirsPsr0;
     }
 
+    /**
+     * @return array[]
+     * @psalm-return array<string, string>
+     */
     public function getFallbackDirsPsr4()
     {
         return $this->fallbackDirsPsr4;
     }
 
+    /**
+     * @return string[] Array of classname => path
+     * @psalm-return array<string, string>
+     */
     public function getClassMap()
     {
         return $this->classMap;
     }
 
     /**
-     * @param array $classMap Class to filename map
+     * @param string[] $classMap Class to filename map
+     * @psalm-param array<string, string> $classMap
+     *
+     * @return void
      */
     public function addClassMap(array $classMap)
     {
@@ -102,9 +175,11 @@ class ClassLoader
      * Registers a set of PSR-0 directories for a given prefix, either
      * appending or prepending to the ones previously set for this prefix.
      *
-     * @param string       $prefix  The prefix
-     * @param array|string $paths   The PSR-0 root directories
-     * @param bool         $prepend Whether to prepend the directories
+     * @param string          $prefix  The prefix
+     * @param string[]|string $paths   The PSR-0 root directories
+     * @param bool            $prepend Whether to prepend the directories
+     *
+     * @return void
      */
     public function add($prefix, $paths, $prepend = false)
     {
@@ -147,11 +222,13 @@ class ClassLoader
      * Registers a set of PSR-4 directories for a given namespace, either
      * appending or prepending to the ones previously set for this namespace.
      *
-     * @param string       $prefix  The prefix/namespace, with trailing '\\'
-     * @param array|string $paths   The PSR-4 base directories
-     * @param bool         $prepend Whether to prepend the directories
+     * @param string          $prefix  The prefix/namespace, with trailing '\\'
+     * @param string[]|string $paths   The PSR-4 base directories
+     * @param bool            $prepend Whether to prepend the directories
      *
      * @throws \InvalidArgumentException
+     *
+     * @return void
      */
     public function addPsr4($prefix, $paths, $prepend = false)
     {
@@ -195,8 +272,10 @@ class ClassLoader
      * Registers a set of PSR-0 directories for a given prefix,
      * replacing any others previously set for this prefix.
      *
-     * @param string       $prefix The prefix
-     * @param array|string $paths  The PSR-0 base directories
+     * @param string          $prefix The prefix
+     * @param string[]|string $paths  The PSR-0 base directories
+     *
+     * @return void
      */
     public function set($prefix, $paths)
     {
@@ -211,10 +290,12 @@ class ClassLoader
      * Registers a set of PSR-4 directories for a given namespace,
      * replacing any others previously set for this namespace.
      *
-     * @param string       $prefix The prefix/namespace, with trailing '\\'
-     * @param array|string $paths  The PSR-4 base directories
+     * @param string          $prefix The prefix/namespace, with trailing '\\'
+     * @param string[]|string $paths  The PSR-4 base directories
      *
      * @throws \InvalidArgumentException
+     *
+     * @return void
      */
     public function setPsr4($prefix, $paths)
     {
@@ -234,6 +315,8 @@ class ClassLoader
      * Turns on searching the include path for class files.
      *
      * @param bool $useIncludePath
+     *
+     * @return void
      */
     public function setUseIncludePath($useIncludePath)
     {
@@ -256,6 +339,8 @@ class ClassLoader
      * that have not been registered with the class map.
      *
      * @param bool $classMapAuthoritative
+     *
+     * @return void
      */
     public function setClassMapAuthoritative($classMapAuthoritative)
     {
@@ -276,6 +361,8 @@ class ClassLoader
      * APCu prefix to use to cache found/not-found classes, if the extension is enabled.
      *
      * @param string|null $apcuPrefix
+     *
+     * @return void
      */
     public function setApcuPrefix($apcuPrefix)
     {
@@ -296,25 +383,44 @@ class ClassLoader
      * Registers this instance as an autoloader.
      *
      * @param bool $prepend Whether to prepend the autoloader or not
+     *
+     * @return void
      */
     public function register($prepend = false)
     {
         spl_autoload_register(array($this, 'loadClass'), true, $prepend);
+
+        if (null === $this->vendorDir) {
+            return;
+        }
+
+        if ($prepend) {
+            self::$registeredLoaders = array($this->vendorDir => $this) + self::$registeredLoaders;
+        } else {
+            unset(self::$registeredLoaders[$this->vendorDir]);
+            self::$registeredLoaders[$this->vendorDir] = $this;
+        }
     }
 
     /**
      * Unregisters this instance as an autoloader.
+     *
+     * @return void
      */
     public function unregister()
     {
         spl_autoload_unregister(array($this, 'loadClass'));
+
+        if (null !== $this->vendorDir) {
+            unset(self::$registeredLoaders[$this->vendorDir]);
+        }
     }
 
     /**
      * Loads the given class or interface.
      *
      * @param  string    $class The name of the class
-     * @return bool|null True if loaded, null otherwise
+     * @return true|null True if loaded, null otherwise
      */
     public function loadClass($class)
     {
@@ -323,6 +429,8 @@ class ClassLoader
 
             return true;
         }
+
+        return null;
     }
 
     /**
@@ -367,6 +475,21 @@ class ClassLoader
         return $file;
     }
 
+    /**
+     * Returns the currently registered loaders indexed by their corresponding vendor directories.
+     *
+     * @return self[]
+     */
+    public static function getRegisteredLoaders()
+    {
+        return self::$registeredLoaders;
+    }
+
+    /**
+     * @param  string       $class
+     * @param  string       $ext
+     * @return string|false
+     */
     private function findFileWithExtension($class, $ext)
     {
         // PSR-4 lookup
@@ -438,6 +561,10 @@ class ClassLoader
  * Scope isolated include.
  *
  * Prevents access to $this/self from included files.
+ *
+ * @param  string $file
+ * @return void
+ * @private
  */
 function includeFile($file)
 {

+ 350 - 0
vendor/composer/InstalledVersions.php

@@ -0,0 +1,350 @@
+<?php
+
+/*
+ * This file is part of Composer.
+ *
+ * (c) Nils Adermann <naderman@naderman.de>
+ *     Jordi Boggiano <j.boggiano@seld.be>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Composer;
+
+use Composer\Autoload\ClassLoader;
+use Composer\Semver\VersionParser;
+
+/**
+ * This class is copied in every Composer installed project and available to all
+ *
+ * See also https://getcomposer.org/doc/07-runtime.md#installed-versions
+ *
+ * To require its presence, you can require `composer-runtime-api ^2.0`
+ */
+class InstalledVersions
+{
+    /**
+     * @var mixed[]|null
+     * @psalm-var array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}|array{}|null
+     */
+    private static $installed;
+
+    /**
+     * @var bool|null
+     */
+    private static $canGetVendors;
+
+    /**
+     * @var array[]
+     * @psalm-var array<string, array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
+     */
+    private static $installedByVendor = array();
+
+    /**
+     * Returns a list of all package names which are present, either by being installed, replaced or provided
+     *
+     * @return string[]
+     * @psalm-return list<string>
+     */
+    public static function getInstalledPackages()
+    {
+        $packages = array();
+        foreach (self::getInstalled() as $installed) {
+            $packages[] = array_keys($installed['versions']);
+        }
+
+        if (1 === \count($packages)) {
+            return $packages[0];
+        }
+
+        return array_keys(array_flip(\call_user_func_array('array_merge', $packages)));
+    }
+
+    /**
+     * Returns a list of all package names with a specific type e.g. 'library'
+     *
+     * @param  string   $type
+     * @return string[]
+     * @psalm-return list<string>
+     */
+    public static function getInstalledPackagesByType($type)
+    {
+        $packagesByType = array();
+
+        foreach (self::getInstalled() as $installed) {
+            foreach ($installed['versions'] as $name => $package) {
+                if (isset($package['type']) && $package['type'] === $type) {
+                    $packagesByType[] = $name;
+                }
+            }
+        }
+
+        return $packagesByType;
+    }
+
+    /**
+     * Checks whether the given package is installed
+     *
+     * This also returns true if the package name is provided or replaced by another package
+     *
+     * @param  string $packageName
+     * @param  bool   $includeDevRequirements
+     * @return bool
+     */
+    public static function isInstalled($packageName, $includeDevRequirements = true)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (isset($installed['versions'][$packageName])) {
+                return $includeDevRequirements || empty($installed['versions'][$packageName]['dev_requirement']);
+            }
+        }
+
+        return false;
+    }
+
+    /**
+     * Checks whether the given package satisfies a version constraint
+     *
+     * e.g. If you want to know whether version 2.3+ of package foo/bar is installed, you would call:
+     *
+     *   Composer\InstalledVersions::satisfies(new VersionParser, 'foo/bar', '^2.3')
+     *
+     * @param  VersionParser $parser      Install composer/semver to have access to this class and functionality
+     * @param  string        $packageName
+     * @param  string|null   $constraint  A version constraint to check for, if you pass one you have to make sure composer/semver is required by your package
+     * @return bool
+     */
+    public static function satisfies(VersionParser $parser, $packageName, $constraint)
+    {
+        $constraint = $parser->parseConstraints($constraint);
+        $provided = $parser->parseConstraints(self::getVersionRanges($packageName));
+
+        return $provided->matches($constraint);
+    }
+
+    /**
+     * Returns a version constraint representing all the range(s) which are installed for a given package
+     *
+     * It is easier to use this via isInstalled() with the $constraint argument if you need to check
+     * whether a given version of a package is installed, and not just whether it exists
+     *
+     * @param  string $packageName
+     * @return string Version constraint usable with composer/semver
+     */
+    public static function getVersionRanges($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            $ranges = array();
+            if (isset($installed['versions'][$packageName]['pretty_version'])) {
+                $ranges[] = $installed['versions'][$packageName]['pretty_version'];
+            }
+            if (array_key_exists('aliases', $installed['versions'][$packageName])) {
+                $ranges = array_merge($ranges, $installed['versions'][$packageName]['aliases']);
+            }
+            if (array_key_exists('replaced', $installed['versions'][$packageName])) {
+                $ranges = array_merge($ranges, $installed['versions'][$packageName]['replaced']);
+            }
+            if (array_key_exists('provided', $installed['versions'][$packageName])) {
+                $ranges = array_merge($ranges, $installed['versions'][$packageName]['provided']);
+            }
+
+            return implode(' || ', $ranges);
+        }
+
+        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+    }
+
+    /**
+     * @param  string      $packageName
+     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+     */
+    public static function getVersion($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            if (!isset($installed['versions'][$packageName]['version'])) {
+                return null;
+            }
+
+            return $installed['versions'][$packageName]['version'];
+        }
+
+        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+    }
+
+    /**
+     * @param  string      $packageName
+     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as version, use satisfies or getVersionRanges if you need to know if a given version is present
+     */
+    public static function getPrettyVersion($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            if (!isset($installed['versions'][$packageName]['pretty_version'])) {
+                return null;
+            }
+
+            return $installed['versions'][$packageName]['pretty_version'];
+        }
+
+        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+    }
+
+    /**
+     * @param  string      $packageName
+     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as reference
+     */
+    public static function getReference($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            if (!isset($installed['versions'][$packageName]['reference'])) {
+                return null;
+            }
+
+            return $installed['versions'][$packageName]['reference'];
+        }
+
+        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+    }
+
+    /**
+     * @param  string      $packageName
+     * @return string|null If the package is being replaced or provided but is not really installed, null will be returned as install path. Packages of type metapackages also have a null install path.
+     */
+    public static function getInstallPath($packageName)
+    {
+        foreach (self::getInstalled() as $installed) {
+            if (!isset($installed['versions'][$packageName])) {
+                continue;
+            }
+
+            return isset($installed['versions'][$packageName]['install_path']) ? $installed['versions'][$packageName]['install_path'] : null;
+        }
+
+        throw new \OutOfBoundsException('Package "' . $packageName . '" is not installed');
+    }
+
+    /**
+     * @return array
+     * @psalm-return array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}
+     */
+    public static function getRootPackage()
+    {
+        $installed = self::getInstalled();
+
+        return $installed[0]['root'];
+    }
+
+    /**
+     * Returns the raw installed.php data for custom implementations
+     *
+     * @deprecated Use getAllRawData() instead which returns all datasets for all autoloaders present in the process. getRawData only returns the first dataset loaded, which may not be what you expect.
+     * @return array[]
+     * @psalm-return array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}
+     */
+    public static function getRawData()
+    {
+        @trigger_error('getRawData only returns the first dataset loaded, which may not be what you expect. Use getAllRawData() instead which returns all datasets for all autoloaders present in the process.', E_USER_DEPRECATED);
+
+        if (null === self::$installed) {
+            // only require the installed.php file if this file is loaded from its dumped location,
+            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+            if (substr(__DIR__, -8, 1) !== 'C') {
+                self::$installed = include __DIR__ . '/installed.php';
+            } else {
+                self::$installed = array();
+            }
+        }
+
+        return self::$installed;
+    }
+
+    /**
+     * Returns the raw data of all installed.php which are currently loaded for custom implementations
+     *
+     * @return array[]
+     * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
+     */
+    public static function getAllRawData()
+    {
+        return self::getInstalled();
+    }
+
+    /**
+     * Lets you reload the static array from another file
+     *
+     * This is only useful for complex integrations in which a project needs to use
+     * this class but then also needs to execute another project's autoloader in process,
+     * and wants to ensure both projects have access to their version of installed.php.
+     *
+     * A typical case would be PHPUnit, where it would need to make sure it reads all
+     * the data it needs from this class, then call reload() with
+     * `require $CWD/vendor/composer/installed.php` (or similar) as input to make sure
+     * the project in which it runs can then also use this class safely, without
+     * interference between PHPUnit's dependencies and the project's dependencies.
+     *
+     * @param  array[] $data A vendor/composer/installed.php data set
+     * @return void
+     *
+     * @psalm-param array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>} $data
+     */
+    public static function reload($data)
+    {
+        self::$installed = $data;
+        self::$installedByVendor = array();
+    }
+
+    /**
+     * @return array[]
+     * @psalm-return list<array{root: array{name: string, version: string, reference: string, pretty_version: string, aliases: string[], dev: bool, install_path: string, type: string}, versions: array<string, array{dev_requirement: bool, pretty_version?: string, version?: string, aliases?: string[], reference?: string, replaced?: string[], provided?: string[], install_path?: string, type?: string}>}>
+     */
+    private static function getInstalled()
+    {
+        if (null === self::$canGetVendors) {
+            self::$canGetVendors = method_exists('Composer\Autoload\ClassLoader', 'getRegisteredLoaders');
+        }
+
+        $installed = array();
+
+        if (self::$canGetVendors) {
+            foreach (ClassLoader::getRegisteredLoaders() as $vendorDir => $loader) {
+                if (isset(self::$installedByVendor[$vendorDir])) {
+                    $installed[] = self::$installedByVendor[$vendorDir];
+                } elseif (is_file($vendorDir.'/composer/installed.php')) {
+                    $installed[] = self::$installedByVendor[$vendorDir] = require $vendorDir.'/composer/installed.php';
+                    if (null === self::$installed && strtr($vendorDir.'/composer', '\\', '/') === strtr(__DIR__, '\\', '/')) {
+                        self::$installed = $installed[count($installed) - 1];
+                    }
+                }
+            }
+        }
+
+        if (null === self::$installed) {
+            // only require the installed.php file if this file is loaded from its dumped location,
+            // and not from its source location in the composer/composer package, see https://github.com/composer/composer/issues/9937
+            if (substr(__DIR__, -8, 1) !== 'C') {
+                self::$installed = require __DIR__ . '/installed.php';
+            } else {
+                self::$installed = array();
+            }
+        }
+        $installed[] = self::$installed;
+
+        return $installed;
+    }
+}

+ 39 - 0
vendor/composer/autoload_classmap.php

@@ -6,6 +6,7 @@ $vendorDir = dirname(dirname(__FILE__));
 $baseDir = dirname($vendorDir);
 
 return array(
+    'Composer\\InstalledVersions' => $vendorDir . '/composer/InstalledVersions.php',
     'IPLib\\Address\\AddressInterface' => $vendorDir . '/mlocati/ip-lib/src/Address/AddressInterface.php',
     'IPLib\\Address\\AssignedRange' => $vendorDir . '/mlocati/ip-lib/src/Address/AssignedRange.php',
     'IPLib\\Address\\IPv4' => $vendorDir . '/mlocati/ip-lib/src/Address/IPv4.php',
@@ -28,12 +29,49 @@ return array(
     'Identicon\\Generator\\ImageMagickGenerator' => $vendorDir . '/yzalis/identicon/src/Identicon/Generator/ImageMagickGenerator.php',
     'Identicon\\Generator\\SvgGenerator' => $vendorDir . '/yzalis/identicon/src/Identicon/Generator/SvgGenerator.php',
     'Identicon\\Identicon' => $vendorDir . '/yzalis/identicon/src/Identicon/Identicon.php',
+    'Jdenticon\\Canvas\\Canvas' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Canvas.php',
+    'Jdenticon\\Canvas\\CanvasContext' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/CanvasContext.php',
+    'Jdenticon\\Canvas\\ColorUtils' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/ColorUtils.php',
+    'Jdenticon\\Canvas\\Matrix' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Matrix.php',
+    'Jdenticon\\Canvas\\Png\\PngBuffer' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Png/PngBuffer.php',
+    'Jdenticon\\Canvas\\Png\\PngEncoder' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Png/PngEncoder.php',
+    'Jdenticon\\Canvas\\Png\\PngPalette' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Png/PngPalette.php',
+    'Jdenticon\\Canvas\\Point' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Point.php',
+    'Jdenticon\\Canvas\\Rasterization\\Edge' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Rasterization/Edge.php',
+    'Jdenticon\\Canvas\\Rasterization\\EdgeIntersection' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Rasterization/EdgeIntersection.php',
+    'Jdenticon\\Canvas\\Rasterization\\EdgeSuperSampleIntersection' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Rasterization/EdgeSuperSampleIntersection.php',
+    'Jdenticon\\Canvas\\Rasterization\\EdgeTable' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Rasterization/EdgeTable.php',
+    'Jdenticon\\Canvas\\Rasterization\\Layer' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Rasterization/Layer.php',
+    'Jdenticon\\Canvas\\Rasterization\\LayerManager' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Rasterization/LayerManager.php',
+    'Jdenticon\\Canvas\\Rasterization\\Rasterizer' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Rasterization/Rasterizer.php',
+    'Jdenticon\\Canvas\\Rasterization\\SuperSampleBuffer' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Rasterization/SuperSampleBuffer.php',
+    'Jdenticon\\Canvas\\Rasterization\\SuperSampleRange' => $vendorDir . '/jdenticon/jdenticon/src/Canvas/Rasterization/SuperSampleRange.php',
+    'Jdenticon\\Color' => $vendorDir . '/jdenticon/jdenticon/src/Color.php',
+    'Jdenticon\\Identicon' => $vendorDir . '/jdenticon/jdenticon/src/Identicon.php',
+    'Jdenticon\\IdenticonStyle' => $vendorDir . '/jdenticon/jdenticon/src/IdenticonStyle.php',
+    'Jdenticon\\Rendering\\AbstractRenderer' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/AbstractRenderer.php',
+    'Jdenticon\\Rendering\\ColorTheme' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/ColorTheme.php',
+    'Jdenticon\\Rendering\\IconGenerator' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/IconGenerator.php',
+    'Jdenticon\\Rendering\\ImagickRenderer' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/ImagickRenderer.php',
+    'Jdenticon\\Rendering\\InternalPngRenderer' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/InternalPngRenderer.php',
+    'Jdenticon\\Rendering\\Point' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/Point.php',
+    'Jdenticon\\Rendering\\Rectangle' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/Rectangle.php',
+    'Jdenticon\\Rendering\\RendererInterface' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/RendererInterface.php',
+    'Jdenticon\\Rendering\\SvgPath' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/SvgPath.php',
+    'Jdenticon\\Rendering\\SvgRenderer' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/SvgRenderer.php',
+    'Jdenticon\\Rendering\\Transform' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/Transform.php',
+    'Jdenticon\\Rendering\\TriangleDirection' => $vendorDir . '/jdenticon/jdenticon/src/Rendering/TriangleDirection.php',
+    'Jdenticon\\Shapes\\Shape' => $vendorDir . '/jdenticon/jdenticon/src/Shapes/Shape.php',
+    'Jdenticon\\Shapes\\ShapeCategory' => $vendorDir . '/jdenticon/jdenticon/src/Shapes/ShapeCategory.php',
+    'Jdenticon\\Shapes\\ShapeDefinitions' => $vendorDir . '/jdenticon/jdenticon/src/Shapes/ShapeDefinitions.php',
+    'Jdenticon\\Shapes\\ShapePosition' => $vendorDir . '/jdenticon/jdenticon/src/Shapes/ShapePosition.php',
     'PrivateBin\\Configuration' => $baseDir . '/lib/Configuration.php',
     'PrivateBin\\Controller' => $baseDir . '/lib/Controller.php',
     'PrivateBin\\Data\\AbstractData' => $baseDir . '/lib/Data/AbstractData.php',
     'PrivateBin\\Data\\Database' => $baseDir . '/lib/Data/Database.php',
     'PrivateBin\\Data\\Filesystem' => $baseDir . '/lib/Data/Filesystem.php',
     'PrivateBin\\Data\\GoogleCloudStorage' => $baseDir . '/lib/Data/GoogleCloudStorage.php',
+    'PrivateBin\\Data\\S3Storage' => $baseDir . '/lib/Data/S3Storage.php',
     'PrivateBin\\Filter' => $baseDir . '/lib/Filter.php',
     'PrivateBin\\FormatV2' => $baseDir . '/lib/FormatV2.php',
     'PrivateBin\\I18n' => $baseDir . '/lib/I18n.php',
@@ -49,4 +87,5 @@ return array(
     'PrivateBin\\Request' => $baseDir . '/lib/Request.php',
     'PrivateBin\\View' => $baseDir . '/lib/View.php',
     'PrivateBin\\Vizhash16x16' => $baseDir . '/lib/Vizhash16x16.php',
+    'PrivateBin\\YourlsProxy' => $baseDir . '/lib/YourlsProxy.php',
 );

+ 1 - 0
vendor/composer/autoload_psr4.php

@@ -7,6 +7,7 @@ $baseDir = dirname($vendorDir);
 
 return array(
     'PrivateBin\\' => array($baseDir . '/lib'),
+    'Jdenticon\\' => array($vendorDir . '/jdenticon/jdenticon/src'),
     'Identicon\\' => array($vendorDir . '/yzalis/identicon/src/Identicon'),
     'IPLib\\' => array($vendorDir . '/mlocati/ip-lib/src'),
 );

+ 11 - 4
vendor/composer/autoload_real.php

@@ -22,13 +22,15 @@ class ComposerAutoloaderInitDontChange
             return self::$loader;
         }
 
+        require __DIR__ . '/platform_check.php';
+
         spl_autoload_register(array('ComposerAutoloaderInitDontChange', 'loadClassLoader'), true, true);
-        self::$loader = $loader = new \Composer\Autoload\ClassLoader();
+        self::$loader = $loader = new \Composer\Autoload\ClassLoader(\dirname(\dirname(__FILE__)));
         spl_autoload_unregister(array('ComposerAutoloaderInitDontChange', 'loadClassLoader'));
 
         $useStaticLoader = PHP_VERSION_ID >= 50600 && !defined('HHVM_VERSION') && (!function_exists('zend_loader_file_encoded') || !zend_loader_file_encoded());
         if ($useStaticLoader) {
-            require_once __DIR__ . '/autoload_static.php';
+            require __DIR__ . '/autoload_static.php';
 
             call_user_func(\Composer\Autoload\ComposerStaticInitDontChange::getInitializer($loader));
         } else {
@@ -63,11 +65,16 @@ class ComposerAutoloaderInitDontChange
     }
 }
 
+/**
+ * @param string $fileIdentifier
+ * @param string $file
+ * @return void
+ */
 function composerRequireDontChange($fileIdentifier, $file)
 {
     if (empty($GLOBALS['__composer_autoload_files'][$fileIdentifier])) {
-        require $file;
-
         $GLOBALS['__composer_autoload_files'][$fileIdentifier] = true;
+
+        require $file;
     }
 }

+ 47 - 0
vendor/composer/autoload_static.php

@@ -15,6 +15,10 @@ class ComposerStaticInitDontChange
         array (
             'PrivateBin\\' => 11,
         ),
+        'J' => 
+        array (
+            'Jdenticon\\' => 10,
+        ),
         'I' => 
         array (
             'Identicon\\' => 10,
@@ -27,6 +31,10 @@ class ComposerStaticInitDontChange
         array (
             0 => __DIR__ . '/../..' . '/lib',
         ),
+        'Jdenticon\\' => 
+        array (
+            0 => __DIR__ . '/..' . '/jdenticon/jdenticon/src',
+        ),
         'Identicon\\' => 
         array (
             0 => __DIR__ . '/..' . '/yzalis/identicon/src/Identicon',
@@ -38,6 +46,7 @@ class ComposerStaticInitDontChange
     );
 
     public static $classMap = array (
+        'Composer\\InstalledVersions' => __DIR__ . '/..' . '/composer/InstalledVersions.php',
         'IPLib\\Address\\AddressInterface' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/AddressInterface.php',
         'IPLib\\Address\\AssignedRange' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/AssignedRange.php',
         'IPLib\\Address\\IPv4' => __DIR__ . '/..' . '/mlocati/ip-lib/src/Address/IPv4.php',
@@ -60,12 +69,49 @@ class ComposerStaticInitDontChange
         'Identicon\\Generator\\ImageMagickGenerator' => __DIR__ . '/..' . '/yzalis/identicon/src/Identicon/Generator/ImageMagickGenerator.php',
         'Identicon\\Generator\\SvgGenerator' => __DIR__ . '/..' . '/yzalis/identicon/src/Identicon/Generator/SvgGenerator.php',
         'Identicon\\Identicon' => __DIR__ . '/..' . '/yzalis/identicon/src/Identicon/Identicon.php',
+        'Jdenticon\\Canvas\\Canvas' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Canvas.php',
+        'Jdenticon\\Canvas\\CanvasContext' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/CanvasContext.php',
+        'Jdenticon\\Canvas\\ColorUtils' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/ColorUtils.php',
+        'Jdenticon\\Canvas\\Matrix' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Matrix.php',
+        'Jdenticon\\Canvas\\Png\\PngBuffer' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Png/PngBuffer.php',
+        'Jdenticon\\Canvas\\Png\\PngEncoder' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Png/PngEncoder.php',
+        'Jdenticon\\Canvas\\Png\\PngPalette' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Png/PngPalette.php',
+        'Jdenticon\\Canvas\\Point' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Point.php',
+        'Jdenticon\\Canvas\\Rasterization\\Edge' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Rasterization/Edge.php',
+        'Jdenticon\\Canvas\\Rasterization\\EdgeIntersection' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Rasterization/EdgeIntersection.php',
+        'Jdenticon\\Canvas\\Rasterization\\EdgeSuperSampleIntersection' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Rasterization/EdgeSuperSampleIntersection.php',
+        'Jdenticon\\Canvas\\Rasterization\\EdgeTable' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Rasterization/EdgeTable.php',
+        'Jdenticon\\Canvas\\Rasterization\\Layer' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Rasterization/Layer.php',
+        'Jdenticon\\Canvas\\Rasterization\\LayerManager' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Rasterization/LayerManager.php',
+        'Jdenticon\\Canvas\\Rasterization\\Rasterizer' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Rasterization/Rasterizer.php',
+        'Jdenticon\\Canvas\\Rasterization\\SuperSampleBuffer' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Rasterization/SuperSampleBuffer.php',
+        'Jdenticon\\Canvas\\Rasterization\\SuperSampleRange' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Canvas/Rasterization/SuperSampleRange.php',
+        'Jdenticon\\Color' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Color.php',
+        'Jdenticon\\Identicon' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Identicon.php',
+        'Jdenticon\\IdenticonStyle' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/IdenticonStyle.php',
+        'Jdenticon\\Rendering\\AbstractRenderer' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/AbstractRenderer.php',
+        'Jdenticon\\Rendering\\ColorTheme' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/ColorTheme.php',
+        'Jdenticon\\Rendering\\IconGenerator' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/IconGenerator.php',
+        'Jdenticon\\Rendering\\ImagickRenderer' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/ImagickRenderer.php',
+        'Jdenticon\\Rendering\\InternalPngRenderer' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/InternalPngRenderer.php',
+        'Jdenticon\\Rendering\\Point' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/Point.php',
+        'Jdenticon\\Rendering\\Rectangle' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/Rectangle.php',
+        'Jdenticon\\Rendering\\RendererInterface' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/RendererInterface.php',
+        'Jdenticon\\Rendering\\SvgPath' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/SvgPath.php',
+        'Jdenticon\\Rendering\\SvgRenderer' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/SvgRenderer.php',
+        'Jdenticon\\Rendering\\Transform' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/Transform.php',
+        'Jdenticon\\Rendering\\TriangleDirection' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Rendering/TriangleDirection.php',
+        'Jdenticon\\Shapes\\Shape' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Shapes/Shape.php',
+        'Jdenticon\\Shapes\\ShapeCategory' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Shapes/ShapeCategory.php',
+        'Jdenticon\\Shapes\\ShapeDefinitions' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Shapes/ShapeDefinitions.php',
+        'Jdenticon\\Shapes\\ShapePosition' => __DIR__ . '/..' . '/jdenticon/jdenticon/src/Shapes/ShapePosition.php',
         'PrivateBin\\Configuration' => __DIR__ . '/../..' . '/lib/Configuration.php',
         'PrivateBin\\Controller' => __DIR__ . '/../..' . '/lib/Controller.php',
         'PrivateBin\\Data\\AbstractData' => __DIR__ . '/../..' . '/lib/Data/AbstractData.php',
         'PrivateBin\\Data\\Database' => __DIR__ . '/../..' . '/lib/Data/Database.php',
         'PrivateBin\\Data\\Filesystem' => __DIR__ . '/../..' . '/lib/Data/Filesystem.php',
         'PrivateBin\\Data\\GoogleCloudStorage' => __DIR__ . '/../..' . '/lib/Data/GoogleCloudStorage.php',
+        'PrivateBin\\Data\\S3Storage' => __DIR__ . '/../..' . '/lib/Data/S3Storage.php',
         'PrivateBin\\Filter' => __DIR__ . '/../..' . '/lib/Filter.php',
         'PrivateBin\\FormatV2' => __DIR__ . '/../..' . '/lib/FormatV2.php',
         'PrivateBin\\I18n' => __DIR__ . '/../..' . '/lib/I18n.php',
@@ -81,6 +127,7 @@ class ComposerStaticInitDontChange
         'PrivateBin\\Request' => __DIR__ . '/../..' . '/lib/Request.php',
         'PrivateBin\\View' => __DIR__ . '/../..' . '/lib/View.php',
         'PrivateBin\\Vizhash16x16' => __DIR__ . '/../..' . '/lib/Vizhash16x16.php',
+        'PrivateBin\\YourlsProxy' => __DIR__ . '/../..' . '/lib/YourlsProxy.php',
     );
 
     public static function getInitializer(ClassLoader $loader)

+ 59 - 0
vendor/composer/installed.php

@@ -0,0 +1,59 @@
+<?php return array(
+    'root' => array(
+        'pretty_version' => 'dev-master',
+        'version' => 'dev-master',
+        'type' => 'project',
+        'install_path' => __DIR__ . '/../../',
+        'aliases' => array(),
+        'reference' => '78aa70e3ab9277172489f9d88f8fd08cc1d03c97',
+        'name' => 'privatebin/privatebin',
+        'dev' => false,
+    ),
+    'versions' => array(
+        'jdenticon/jdenticon' => array(
+            'pretty_version' => '1.0.1',
+            'version' => '1.0.1.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../jdenticon/jdenticon',
+            'aliases' => array(),
+            'reference' => '994ee07293fb978f983393ffcb2c0250592a6ac4',
+            'dev_requirement' => false,
+        ),
+        'mlocati/ip-lib' => array(
+            'pretty_version' => '1.18.0',
+            'version' => '1.18.0.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../mlocati/ip-lib',
+            'aliases' => array(),
+            'reference' => 'c77bd0b1f3e3956c7e9661e75cb1f54ed67d95d2',
+            'dev_requirement' => false,
+        ),
+        'paragonie/random_compat' => array(
+            'pretty_version' => 'v2.0.21',
+            'version' => '2.0.21.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../paragonie/random_compat',
+            'aliases' => array(),
+            'reference' => '96c132c7f2f7bc3230723b66e89f8f150b29d5ae',
+            'dev_requirement' => false,
+        ),
+        'privatebin/privatebin' => array(
+            'pretty_version' => 'dev-master',
+            'version' => 'dev-master',
+            'type' => 'project',
+            'install_path' => __DIR__ . '/../../',
+            'aliases' => array(),
+            'reference' => '78aa70e3ab9277172489f9d88f8fd08cc1d03c97',
+            'dev_requirement' => false,
+        ),
+        'yzalis/identicon' => array(
+            'pretty_version' => '2.0.0',
+            'version' => '2.0.0.0',
+            'type' => 'library',
+            'install_path' => __DIR__ . '/../yzalis/identicon',
+            'aliases' => array(),
+            'reference' => 'ff5ed090129cab9bfa2a322857d4a01d107aa0ae',
+            'dev_requirement' => false,
+        ),
+    ),
+);

+ 26 - 0
vendor/composer/platform_check.php

@@ -0,0 +1,26 @@
+<?php
+
+// platform_check.php @generated by Composer
+
+$issues = array();
+
+if (!(PHP_VERSION_ID >= 50600)) {
+    $issues[] = 'Your Composer dependencies require a PHP version ">= 5.6.0". You are running ' . PHP_VERSION . '.';
+}
+
+if ($issues) {
+    if (!headers_sent()) {
+        header('HTTP/1.1 500 Internal Server Error');
+    }
+    if (!ini_get('display_errors')) {
+        if (PHP_SAPI === 'cli' || PHP_SAPI === 'phpdbg') {
+            fwrite(STDERR, 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . implode(PHP_EOL, $issues) . PHP_EOL.PHP_EOL);
+        } elseif (!headers_sent()) {
+            echo 'Composer detected issues in your platform:' . PHP_EOL.PHP_EOL . str_replace('You are running '.PHP_VERSION.'.', '', implode(PHP_EOL, $issues)) . PHP_EOL.PHP_EOL;
+        }
+    }
+    trigger_error(
+        'Composer detected issues in your platform: ' . implode(' ', $issues),
+        E_USER_ERROR
+    );
+}

+ 130 - 0
vendor/jdenticon/jdenticon/src/Canvas/Canvas.php

@@ -0,0 +1,130 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Canvas;
+
+use Jdenticon\Canvas\Rasterization\Edge;
+use Jdenticon\Canvas\Rasterization\EdgeTable;
+use Jdenticon\Canvas\Rasterization\Rasterizer;
+use Jdenticon\Canvas\Png\PngPalette;
+use Jdenticon\Canvas\Png\PngEncoder;
+use Jdenticon\Canvas\CanvasContext;
+use Jdenticon\Canvas\ColorUtils;
+
+class Canvas
+{
+    private $edges;
+
+    /**
+     * Creates a new canvas with the specified dimensions given in pixels.
+     *
+     * @param integer $width  Canvas width in pixels.
+     * @param integer $height  Canvas height in pixels.
+     */
+    public function __construct($width, $height) 
+    {
+        $this->width = $width;
+        $this->height = $height;
+        $this->edges = new EdgeTable($width, $height);
+    }
+
+    /**
+     * The width of the canvas in pixels.
+     *
+     * @var integer
+     */
+    public $width = 0;
+
+    /**
+     * The height of the canvas in pixels.
+     *
+     * @var integer
+     */
+    public $height = 0;
+
+    /**
+     * Specifies the background color. Allowed values are:
+     * - 32 bit integers on the format 0xRRGGBBAA
+     * - strings on the format #RGB
+     * - strings on the format #RRGGBB
+     * - strings on the format #RRGGBBAA
+     *
+     * @var integer|string
+     */
+    public $backColor = 0x00000000;
+
+    /**
+     * Gets a context used to draw polygons on this canvas.
+     *
+     * @returns \Jdenticon\Canvas\CanvasContext
+     */
+    public function getContext() 
+    {
+        return new CanvasContext($this, $this->edges);
+    }
+
+    /**
+     * Renders the canvas as a PNG data stream.
+     *
+     * @param array $keywords  Keywords to be written to the PNG stream. 
+     *       See https://www.w3.org/TR/PNG/#11keywords.
+     * @returns string
+     */
+    public function toPng($keywords = array()) 
+    {
+        $colorRanges = array();
+        
+        Rasterizer::rasterize(
+            $colorRanges, $this->edges, 
+            $this->width, $this->height);
+        
+        $backColor = ColorUtils::parse($this->backColor);
+        if (ColorUtils::alpha($backColor) > 0) {
+            $isColor = false;
+            
+            foreach ($colorRanges as & $value) {
+                if ($isColor) {
+                    $value = ColorUtils::over($value, $backColor);
+                    $isColor = false;
+                } else {
+                    $isColor = true;
+                }
+            }
+            
+            unset($value);
+        }
+
+        $palette = new PngPalette($colorRanges);
+        $png = new PngEncoder();
+
+        $png->writeImageHeader($this->width, $this->height, $palette->isValid ?
+            PngEncoder::INDEXED_COLOR : PngEncoder::TRUE_COLOR_WITH_ALPHA);
+
+        $png->writeImageGamma();
+        
+        foreach ($keywords as $key => $value) {
+            $png->writeTextualData($key, $value);
+        }
+        
+        if ($palette && $palette->isValid) {
+            $png->writePalette($palette);
+            $png->writeTransparency($palette);
+            $png->writeIndexed($colorRanges, $palette, 
+                $this->width, $this->height);
+        } else {
+            $png->writeTrueColorWithAlpha($colorRanges, 
+                $this->width, $this->height);
+        }
+
+        $png->writeImageEnd();
+        return $png->getBuffer();
+    }
+}

+ 408 - 0
vendor/jdenticon/jdenticon/src/Canvas/CanvasContext.php

@@ -0,0 +1,408 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Canvas;
+
+use Jdenticon\Canvas\ColorUtils;
+use Jdenticon\Canvas\CanvasState;
+use Jdenticon\Canvas\Rasterization\EdgeTable;
+use Jdenticon\Canvas\Rasterization\Edge;
+use Jdenticon\Canvas\Matrix;
+
+class CanvasContext
+{
+    private $savedStates = array();
+    private $edges;
+    private $transform;
+    private $paths;
+    private $canvas;
+
+    /**
+     * Creates a new canvas with the specified dimensions given in pixels.
+     *
+     * @param \Jdenticon\Canvas\Canvas $canvas The owner canvas.
+     * @param array $edges The owner canvas' edge buffer.
+     */
+    public function __construct($canvas, &$edges) 
+    {
+        $this->edges = $edges;
+        $this->canvas = $canvas;
+        $this->beginPath();
+        $this->resetTransform();
+    }
+
+    /**
+     * Specifies the fill color that is used when the fill method is called. Allowed values are:
+     * - 32 bit integers on the format 0xRRGGBBAA
+     * - strings on the format #RGB
+     * - strings on the format #RRGGBB
+     * - strings on the format #RRGGBBAA
+     *
+     * @var integer|string
+     */
+    public $fillStyle = 0x000000ff;
+
+    /**
+     * Saves the current state to the state stack.
+     */
+    public function save() 
+    {
+        array_push($this->savedStates, array(
+            'transform' => $this->transform,
+            'fillStyle' => $this->fillStyle
+        ));
+    }
+
+    /**
+     * Restores the last saved state of the CanvasContext.
+     */
+    public function restore() 
+    {
+        $state = array_pop($this->savedStates);
+        if ($state != NULL) {
+            $this->transform = $state['transform'];
+            $this->fillStyle = $state['fillStyle'];
+        }
+    }
+
+    /**
+     * Resets the internal path buffer and begins a new path.
+     */
+    public function resetTransform() 
+    {
+        $this->transform = new Matrix(1, 0, 0, 1, 0, 0);
+    }
+
+    /**
+     * Multiplies the current transformation matrix with the specified values.
+     */
+    public function transform($a, $b, $c, $d, $e, $f) 
+    {
+        if (gettype($a) != 'integer' ||
+            gettype($b) != 'integer' ||
+            gettype($c) != 'integer' ||
+            gettype($d) != 'integer' ||
+            gettype($e) != 'integer' ||
+            gettype($f) != 'integer'
+        ) {
+            return;
+        }
+
+        $this->transform = $this->transform->multiply($a, $b, $c, $d, $e, $f);
+    }
+
+    /**
+     * Sets the transformation matrix to the specified matrix.
+     */
+    public function setTransform($a, $b, $c, $d, $e, $f) 
+    {
+        if (gettype($a) != 'integer' ||
+            gettype($b) != 'integer' ||
+            gettype($c) != 'integer' ||
+            gettype($d) != 'integer' ||
+            gettype($e) != 'integer' ||
+            gettype($f) != 'integer'
+        ) {
+            return;
+        }
+
+        $this->transform = new Matrix($a, $b, $c, $d, $e, $f);
+    }
+
+    /**
+     * Applies a translation transformation to the CanvasContext.
+     *
+     * @param float $x Distance to move in the horizontal direction in pixels.
+     * @param float $y Distance to move in the vertical direction in pixels.
+     */
+    public function translate($x, $y) 
+    {
+        $this->transform = $this->transform->translate($x, $y);
+    }
+
+    /**
+     * Applies a scale transformation to the CanvasContext.
+     *
+     * @param float $x Scale in the horizontal direction. 1 means no scale.
+     * @param float $y Scale in the vertical direction. 1 means no scale.
+     */
+    public function scale($x, $y) 
+    {
+        $this->transform = $this->transform->scale($x, $y);
+    }
+
+    /**
+     * Applies a rotation transformation to the canvas around its current origo.
+     *
+     * @param float $angle  Angle in radians measured clockwise from the 
+     *      positive x axis.
+     */
+    public function rotate($angle) 
+    {
+        $this->transform = $this->transform->rotate($angle);
+    }
+
+    /**
+     * Removes all existing subpaths and begins a new path.
+     */
+    public function beginPath() 
+    {
+        $this->paths = array();
+    }
+
+    /**
+     * Starts a new subpath that begins in the same point as the start and end 
+     * point of the previous one.
+     */
+    public function closePath() 
+    {
+        $pathsCount = count($this->paths);
+        if ($pathsCount > 0) {
+            $path = $this->paths[$pathsCount - 1];
+            $pathCount = count($path);
+            
+            if ($pathCount > 2) {
+                // Close path
+                if ($path[0] != $path[$pathCount - 2] ||
+                    $path[1] != $path[$pathCount - 1]
+                ) {
+                    $path[] = $path[0];
+                    $path[] = $path[1];
+                }
+
+                // Begin a new path
+                $this->paths[] = array($path[0], $path[1]);
+            }
+        }
+    }
+
+    /**
+     * Begins a new subpath by moving the cursor to the specified position.
+     *
+     * @param float $x  X coordinate.
+     * @param float $y  Y coordinate.
+     */
+    public function moveTo($x, $y) 
+    {
+        $p = $this->transform->multiplyPoint($x, $y);
+        $this->paths[] = array($p->x, $p->y);
+    }
+
+    /**
+     * Inserts an edge between the last and specified position.
+     *
+     * @param float $x  Target X coordinate.
+     * @param float $y  Target Y coordinate.
+     * @public
+     */
+    public function lineTo($x, $y) 
+    {
+        $pathsCount = count($this->paths);
+        if ($pathsCount == 0) {
+            $this->paths[] = array();
+            $pathsCount++;
+        }
+
+        $p = $this->transform->multiplyPoint($x, $y);
+        $path = &$this->paths[$pathsCount - 1];
+        $path[] = $p->x;
+        $path[] = $p->y;
+    }
+
+    /**
+     * Adds an arc to the current path.
+     *
+     * @param float $x  X coordinate of the center of the arc.
+     * @param float $y  Y coordinate of the center of the arc.
+     * @param float $radius  Radius of the arc.
+     * @param float $startAngle  The angle in radians at which the arc starts, 
+     *       measured clockwise from the positive x axis.
+     * @param float $endAngle  The angle in radians at which the arc end, 
+     *       measured clockwise from the positive x axis.
+     * @param boolean $anticlockwise  Specifies whether the arc will be drawn 
+     *       counter clockwise. Default is clockwise.
+     */
+    public function arc($x, $y, $radius, $startAngle, $endAngle, $anticlockwise) 
+    {
+        $TARGET_CHORD_LENGTH_PIXELS = 3;
+        
+        $sectors = floor((M_PI * $radius * 2) / $TARGET_CHORD_LENGTH_PIXELS);
+        if ($sectors < 9) {
+            $sectors = 9;
+        }
+        
+        $sectorAngle = M_PI * 2 / $sectors;
+
+        if ($startAngle == $endAngle) {
+            return;
+        }
+
+        if ($anticlockwise) {
+            $sectorAngle = -$sectorAngle;
+
+            if ($startAngle - $endAngle >= M_PI * 2) {
+                $endAngle = $startAngle - M_PI * 2;
+            } else {
+                // Normalize end angle so that the sweep angle is in the range 
+                // (0, -2PI]
+                $endAngle += 
+                    M_PI * 2 * ceil(($startAngle - $endAngle) / 
+                    (M_PI * 2) - 1);
+            }
+        } else {
+            if ($endAngle - $startAngle >= M_PI * 2) {
+                $endAngle = $startAngle + M_PI * 2;
+            } else {
+                // Normalize end angle so that the sweep angle is in the range 
+                // (0, 2PI]
+                $endAngle -= 
+                    M_PI * 2 * ceil(($endAngle - $startAngle) / 
+                    (M_PI * 2) - 1);
+            }
+        }
+        
+        $dx;
+        $dy;
+        $sectors = ($endAngle - $startAngle) / $sectorAngle;
+
+        $angle = $startAngle;
+        
+        for ($i = 0; $i < $sectors; $i++) {
+            $dx = cos($angle) * $radius;
+            $dy = sin($angle) * $radius;
+            $this->lineTo($x + $dx, $y + $dy);
+            $angle += $sectorAngle;
+        }
+
+        $dx = cos($endAngle) * $radius;
+        $dy = sin($endAngle) * $radius;
+        $this->lineTo($x + $dx, $y + $dy);
+    }
+
+    /**
+     * Fills the specified rectangle with fully transparent black without 
+     * affecting the current paths.
+     *
+     * @param float $x X coordinate of the left side of the rectangle.
+     * @param float $y Y coordinate of the top of the rectangle.
+     * @param float $width Width of the rectangle.
+     * @param float $height Height of the rectangle.
+     */
+    public function clearRect($x, $y, $width, $height) 
+    {
+        $fullCanvas = false;
+
+        if (!$this->transform->hasSkewing()) {
+            // Check if the whole canvas is cleared
+            $topLeft = $this->transform->multiplyPoint($x, $y);
+            if ($topLeft->x <= 0 && $topLeft->y <= 0) {
+                $bottomRight = $this->transform->multiplyPoint(
+                    $x + $width, $y + $height);
+                if ($bottomRight->x >= $this->canvas->width &&
+                    $bottomRight->y >= $this->canvas->height
+                ) {
+                    $fullCanvas = true;
+                }
+            }
+        }
+
+        if ($fullCanvas) {
+            $this->edges->clear();
+        } else {
+            $this->_fillRect(ColorUtils::FORCE_TRANSPARENT, 
+                $x, $y, $width, $height);
+        }
+    }
+
+    /**
+     * Fills the specified rectangle without affecting the current paths.
+     *
+     * @param float $x X coordinate of the left side of the rectangle.
+     * @param float $y Y coordinate of the top of the rectangle.
+     * @param float $width Width of the rectangle.
+     * @param float $height Height of the rectangle.
+     */
+    public function fillRect($x, $y, $width, $height) 
+    {
+        $fillColor = ColorUtils::parse($this->fillStyle);
+        $this->_fillRect($fillColor, $x, $y, $width, $height);
+    }
+
+    private function _fillRect($fillColor, $x, $y, $width, $height) 
+    {
+        $polygonId = $this->edges->getNextPolygonId();
+        
+        $points = array(
+            $this->transform->multiplyPoint($x, $y),
+            $this->transform->multiplyPoint($x + $width, $y),
+            $this->transform->multiplyPoint($x + $width, $y + $height),
+            $this->transform->multiplyPoint($x, $y + $height),
+            $this->transform->multiplyPoint($x, $y)
+        );
+
+        $pointsCount = count($points);
+        for ($i = 1; $i < $pointsCount; $i++) {
+            $this->edges->add(new Edge(
+                $polygonId,
+                $points[$i - 1]->x,
+                $points[$i - 1]->y,
+                $points[$i]->x,
+                $points[$i]->y,
+                $fillColor));
+        }
+    }
+
+    /**
+     * Fills the defined paths.
+     *
+     * @param string $windingRule  The winding rule to be used for determining
+     *      which areas are covered by the current path. Valid values are 
+     *      "evenodd" and "nonzero". Default is "nonzero".
+     */
+    public function fill($windingRule = "nonzero") 
+    {
+        $polygonId = $this->edges->getNextPolygonId();
+        $fillColor = ColorUtils::parse($this->fillStyle);
+        
+        foreach ($this->paths as $points) {
+            $pointsCount = count($points);
+            if ($pointsCount <= 2) {
+                // Nothing to fill
+                continue;
+            }
+
+            for ($i = 2; $i < $pointsCount; $i += 2) {
+                $this->edges->add(new Edge(
+                    $polygonId,
+                    $points[$i - 2],
+                    $points[$i - 1],
+                    $points[$i],
+                    $points[$i + 1],
+                    $fillColor,
+                    $windingRule));
+            }
+            
+            // Close path
+            if ($points[0] != $points[$pointsCount - 2] ||
+                $points[1] != $points[$pointsCount - 1]
+            ) {
+                $this->edges->add(new Edge(
+                    $polygonId,
+                    $points[$pointsCount - 2],
+                    $points[$pointsCount - 1],
+                    $points[0],
+                    $points[1],
+                    $fillColor,
+                    $windingRule));
+            }
+        }
+    }
+}

+ 228 - 0
vendor/jdenticon/jdenticon/src/Canvas/ColorUtils.php

@@ -0,0 +1,228 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Canvas;
+
+class ColorUtils
+{
+    /**
+     * Transparent color.
+     * @var integer
+     */
+    const TRANSPARENT = 0;
+
+    /**
+     * Specifies a transparent color that will not blend with layers below the 
+     * current layer.
+     *
+     * @var float
+     */
+    const FORCE_TRANSPARENT = INF;
+
+    /**
+     * Creates a color on the format 0xRRGGBBAA from the specified
+     * color components.
+     *
+     * @return integer
+     */
+    public static function from($a, $r, $g, $b) 
+    {
+        return ($r << 24) | ($g << 16) | ($b << 8) | $a;
+    }
+
+    /**
+     * Gets the alpha component of a color.
+     *
+     * @param integer $color  32-bit color value on the format 0xRRGGBBAA.
+     * @return integer Alpha in the range [0, 255].
+     */
+    public static function alpha($color) 
+    {
+        return $color & 0xff;
+    }
+
+    /**
+     * Gets the red component of a color.
+     *
+     * @param integer $color  32-bit color value on the format 0xRRGGBBAA.
+     * @return integer Red component in the range [0, 255].
+     */
+    public static function red($color) 
+    {
+        return ($color >> 24) & 0xff;
+    }
+
+    /**
+     * Gets the green component of a color.
+     *
+     * @param integer $color  32-bit color value on the format 0xRRGGBBAA.
+     * @return integer Green component in the range [0, 255].
+     */
+    public static function green($color) 
+    {
+        return ($color >> 16) & 0xff;
+    }
+
+    /**
+     * Gets the blue component of a color.
+     *
+     * @param integer $color  32-bit color value on the format 0xRRGGBBAA.
+     * @return integer Blue component in the range [0, 255].
+     */
+    public static function blue($color) 
+    {
+        return ($color >> 8) & 0xff;
+    }
+    
+    /**
+     * Formats a color as a string.
+     *
+     * @param integer $color Color to format.
+     * @return string
+     */
+    public static function format($color) 
+    {
+        return bin2hex(pack('N', $color));
+    }
+    
+    /**
+     * Computes a mix of the two specified colors, with the proportion given 
+     * by the specified weight.
+     *
+     * @param integer $color1  First color to mix.
+     * @param integer $color2  Second color to mix.
+     * @param float $weight  Weight in the range [0,1]. 
+     *      0 gives $color1, 1 gives $color2.
+     * @return integer Mixed color.
+     */
+    public static function mix($color1, $color2, $weight) 
+    {
+        if ($weight < 0) {
+            $weight = 0;
+        } elseif ($weight > 1) {
+            $weight = 1;
+        }
+        
+        $a = ($color1 & 0xff) * (1 - $weight) + ($color2 & 0xff) * $weight;
+        if ($a <= 0.1) {
+            return 0;
+        }
+        
+        $r = (
+            ($color1 >> 24) * ($color1 & 0xff) * (1 - $weight) +
+            ($color2 >> 24) * ($color2 & 0xff) * $weight
+            ) / $a;
+            
+        $g = (
+            (($color1 >> 16) & 0xff) * ($color1 & 0xff) * (1 - $weight) +
+            (($color2 >> 16) & 0xff) * ($color2 & 0xff) * $weight
+            ) / $a;
+            
+        $b = (
+            (($color1 >> 8) & 0xff) * ($color1 & 0xff) * (1 - $weight) +
+            (($color2 >> 8) & 0xff) * ($color2 & 0xff) * $weight
+            ) / $a;
+        
+        if ($a > 255) $a = 255;    
+        if ($r > 255) $r = 255;
+        if ($g > 255) $g = 255;
+        if ($b > 255) $b = 255;
+        
+        return ((int)$r << 24) | ((int)$g << 16) | ((int)$b << 8) | (int)$a;
+    }
+
+    /**
+     * Parses a value to a 32-bit color on the format 0xRRGGBBAA.
+     *
+     * @param integer|string $color  The value to parse.
+     * @return integer
+     */
+    public static function parse($color) 
+    {
+        if (gettype($color) == "integer") {
+            return $color & 0xffffffff;
+        }
+
+        $color = "$color";
+
+        if (preg_match('/^#?[0-9a-fA-F]+$/', $color)) {
+            $hexColor = $color; 
+            if ($hexColor[0] == '#') {
+                $hexColor = substr($hexColor, 1);
+            }
+
+            switch (strlen($hexColor)) {
+                case 3:
+                    $numeric = intval($hexColor, 16);
+                    return (
+                        (($numeric & 0xf00) << 20) |
+                        (($numeric & 0xf00) << 16) |
+                        (($numeric & 0x0f0) << 16) |
+                        (($numeric & 0x0f0) << 12) |
+                        (($numeric & 0x00f) << 12) |
+                        (($numeric & 0x00f) << 8) | 
+                        0xff);
+                case 6:
+                    return (intval($hexColor, 16) << 8) | 0xff;
+                case 8:
+                    // Workaround to cope with PHP limitation of intval
+                    $numeric = 
+                        (intval(substr($hexColor, 0, 4), 16) << 16) |
+                        (intval(substr($hexColor, 4, 4), 16));
+                    return $numeric;
+            }
+        }
+
+        throw new \InvalidArgumentException("Invalid color '$color'.");
+    }
+
+    /**
+     * Blends this color with another color using the over blending operation.
+     *
+     * @param integer $fore  The foreground color.
+     * @param integer $back  The background color.
+     * @return integer
+     */
+    public static function over($fore, $back) 
+    {
+        $foreA = ($fore & 0xff);
+        $backA = ($back & 0xff);
+
+        if ($foreA < 1) {
+            return $back;
+        } elseif ($foreA > 254 || $backA < 1) {
+            return $fore;
+        }
+
+        // Source: 
+        // https://en.wikipedia.org/wiki/Alpha_compositing#Description
+        $forePA = $foreA * 255;
+        $backPA = $backA * (255 - $foreA);
+        $pa = ($forePA + $backPA);
+
+        $b = (int) (
+            ($forePA * (($fore >> 8) & 0xff) + $backPA * (($back >> 8) & 0xff)) /
+            $pa);
+
+        $g = (int) (
+            ($forePA * (($fore >> 16) & 0xff) + $backPA * (($back >> 16) & 0xff)) /
+            $pa);
+
+        $r = (int) (
+            ($forePA * (($fore >> 24) & 0xff) + $backPA * (($back >> 24) & 0xff)) /
+            $pa);
+
+        $a = (int) ($pa / 255);
+
+        return ($r << 24) | ($g << 16) | ($b << 8) | $a;
+    }
+};
+

+ 141 - 0
vendor/jdenticon/jdenticon/src/Canvas/Matrix.php

@@ -0,0 +1,141 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Canvas;
+
+use Jdenticon\Canvas\Point;
+
+class Matrix
+{
+    private $a;
+    private $b;
+    private $c;
+    private $d;
+    private $e;
+    private $f;
+
+    /**
+     * Creates a new transformation matrix.
+     */
+    public function __construct($a, $b, $c, $d, $e, $f) 
+    {
+        $this->a = $a;
+        $this->b = $b;
+        $this->c = $c;
+        $this->d = $d;
+        $this->e = $e;
+        $this->f = $f;
+    }
+
+    /**
+     * Gets a value determining if this matrix has skewing values.
+     *
+     * @return boolean
+     */
+    public function hasSkewing() 
+    {
+        return $this->b || $this->c;
+    }
+
+    /**
+     * Gets a value determining if this matrix has translation values.
+     *
+     * @return boolean
+     */
+    public function hasTranslation() 
+    {
+        return $this->e || $this->f;
+    }
+
+    /**
+     * Gets a value determining if this matrix has scaling values.
+     *
+     * @return boolean
+     */
+    public function hasScaling() 
+    {
+        return $this->a != 1 || $this->d != 1;
+    }
+
+    /**
+     * Returns a new matrix based on the current matrix multiplied with the 
+     * specified matrix values.
+     *
+     * @return \Jdenticon\Canvas\Matrix
+     */
+    public function multiply($a, $b, $c, $d, $e, $f) 
+    {
+        return new Matrix(
+            $this->a * $a + $this->c * $b,
+            $this->b * $a + $this->d * $b,
+            $this->a * $c + $this->c * $d,
+            $this->b * $c + $this->d * $d,
+            $this->a * $e + $this->c * $f + $this->e,
+            $this->b * $e + $this->d * $f + $this->f
+        );
+    }
+
+    /**
+     * Multiplies the specified point with the current matrix and returns the 
+     * resulting point.
+     *
+     * @param float $x  X coordinate.
+     * @param float $y  Y coordinate.
+     * @return \Jdenticon\Canvas\Point
+     */
+    public function multiplyPoint($x, $y) 
+    {
+        return new Point(
+            $this->a * $x + $this->c * $y + $this->e,
+            $this->b * $x + $this->d * $y + $this->f
+        );
+    }
+
+    /**
+     * Returns a new matrix based on the current matrix with a rotation 
+     * transformation applied.
+     *
+     * @param float $angle  Rotation angle in radians.
+     * @return \Jdenticon\Canvas\Matrix
+     */
+    public function rotate($angle) 
+    {
+        $sin = sin($angle);
+        $cos = cos($angle);
+        return $this->multiply($cos, $sin, -$sin, $cos, 0, 0);
+    }
+
+    /**
+     * Returns a new matrix based on the current matrix with a translation 
+     * transformation applied.
+     *
+     * @param float $x  Horizontal move distance.
+     * @param float $y  Vertical move distance.
+     * @return \Jdenticon\Canvas\Matrix
+     */
+    public function translate($x, $y) 
+    {
+        return $this->multiply(1, 0, 0, 1, $x, $y);
+    }
+
+    /**
+     * Returns a new matrix based on the current matrix with a scaling 
+     * transformation applied.
+     *
+     * @param float $x  Horizontal scale.
+     * @param float $y  Vertical scale.
+     * @return \Jdenticon\Canvas\Matrix
+     */
+    public function scale($x, $y) 
+    {
+        return $this->multiply($x, 0, 0, $y, 0, 0);
+    }
+}

+ 92 - 0
vendor/jdenticon/jdenticon/src/Canvas/Png/PngBuffer.php

@@ -0,0 +1,92 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Canvas\Png;
+
+class PngBuffer
+{
+    private $buffer = '';
+    private $chunkPreviousBuffer = '';
+
+    /**
+     * Writes a string to the buffer.
+     *
+     * @param string $str  String to write.
+     */
+    public function writeString($str) 
+    {
+        $this->buffer .= $str;
+    }
+    
+    /**
+     * Writes a 32 bit unsigned int to the buffer in Big Endian format.
+     *
+     * @param integer $value  Value to write.
+     */
+    public function writeUInt32BE($value) 
+    {
+        $this->buffer .= pack('N', $value);
+    }
+
+    /**
+     * Writes an 8 bit unsigned int to the buffer.
+     *
+     * @param integer $value  Value to write.
+     */
+    public function writeUInt8($value) 
+    {
+        $this->buffer .= pack('C', $value);
+    }
+
+    /**
+     * Starts a new PNG chunk.
+     *
+     * @param string $type  Name of the chunk. Must contain exactly 4 
+     *      ASCII characters.
+     */
+    public function startChunk($type) 
+    {
+        $this->chunkPreviousBuffer = $this->buffer;
+        $this->buffer = $type;
+    }
+
+    /**
+     * Closes the current PNG chunk.
+     */
+    public function endChunk() 
+    {
+        // Compute Crc32 for type + data
+        $data = $this->buffer;
+        $crc = crc32($data);
+
+        $this->buffer = 
+            $this->chunkPreviousBuffer .
+            
+            // Length
+            pack('N', strlen($data) - 4) .
+
+            // Content 
+            $data .
+
+            // Crc32
+            pack('N', $crc);
+    }
+
+    /**
+     * Gets a string containing the PNG encoded data.
+     *
+     * @return string
+     */
+    public function getBuffer()
+    {
+        return $this->buffer;
+    }
+}

+ 238 - 0
vendor/jdenticon/jdenticon/src/Canvas/Png/PngEncoder.php

@@ -0,0 +1,238 @@
+<?php 
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Canvas\Png;
+
+use Jdenticon\Canvas\Png\PngPalette;
+use Jdenticon\Canvas\Png\PngBuffer;
+use Jdenticon\Canvas\ColorUtils;
+
+class PngEncoder
+{
+    const GRAYSCALE = 0;
+    const TRUE_COLOR = 2;
+    const INDEXED_COLOR = 3;
+    const GRAYSCALE_WITH_ALPHA = 4;
+    const TRUE_COLOR_WITH_ALPHA = 6;
+
+    private $buffer;
+    
+    public function __construct()
+    {
+        $this->buffer = new PngBuffer();
+        $this->buffer->writeString("\x89\x50\x4e\x47\xd\xa\x1a\xa");
+    }
+
+    /**
+     * Writes an IHDR chunk to the png data stream.
+     *
+     * @param int $width Image width in pixels.
+     * @param int $height Image height in pixels.
+     * @param int $colorType Color depth, speocfy one of the constants in 
+     *      PngEncoder.
+     */
+    public function writeImageHeader($width, $height, $colorType) 
+    {
+        $this->buffer->startChunk("IHDR");
+        $this->buffer->writeUInt32BE($width);
+        $this->buffer->writeUInt32BE($height);
+        $this->buffer->writeUInt8(8); // Bit depth
+        $this->buffer->writeUInt8($colorType);
+        $this->buffer->writeUInt8(0); // Compression
+        $this->buffer->writeUInt8(0); // Filter
+        $this->buffer->writeUInt8(0); // Interlace
+        $this->buffer->endChunk();
+    }
+
+    /**
+     * Writes a gAMA chunk to the png data stream.
+     *
+     * @param int $gamma Gamma value.
+     */
+    public function writeImageGamma($gamma = 45455) 
+    {
+        $this->buffer->startChunk("gAMA");
+        $this->buffer->writeUInt32BE($gamma);
+        $this->buffer->endChunk();
+    }
+
+    /**
+     * Writes an IDAT chunk of truecolor encoded image data.
+     *
+     * @param array $colorRanges Image data on the format 
+     *      array(count0, color0, count1, color1, ...)
+     * @param int $width Image width in pixels.
+     * @param int $height Image height in pixels.
+     */
+    public function writeTrueColorWithAlpha(
+        array & $colorRanges, $width, $height) 
+    {
+        $this->buffer->startChunk("IDAT");
+
+        $uncompressed = '';
+        $count = -1;
+        $x = 0;
+        
+        foreach ($colorRanges as $value) {
+            if ($count === -1) {
+                $count = $value;
+            } else {
+                if ($count !== 0) {
+                    if ($x === $width) {
+                        $x = 0;
+                    }
+                    if ($x === 0) {
+                        $uncompressed .= pack('C', 0); // No filtering
+                    }
+                    
+                    $uncompressed .= str_repeat(pack('N', $value), $count);
+                    $x += $count;
+                }
+                
+                $count = -1;
+            }
+        }
+
+        $compressed = gzcompress($uncompressed, 2);
+        $this->buffer->writeString($compressed);
+
+        $this->buffer->endChunk();
+    }
+
+    /**
+     * Writes an IDAT chunk of indexed image data.
+     *
+     * @param array $colorRanges Image data on the format 
+     *      array(count0, color0, count1, color1, ...)
+     * @param \Jdenticon\Canvas\Png\PngPalette $palette Palette containing the 
+     *      indexed colors.
+     * @param int $width Image width in pixels.
+     * @param int $height Image height in pixels.
+     */
+    public function writeIndexed(
+        array & $colorRanges, 
+        PngPalette $palette, 
+        $width, $height) 
+    {
+        $this->buffer->startChunk("IDAT");
+
+        $uncompressed = '';
+
+        $count = -1;
+        $x = 0;
+
+        foreach ($colorRanges as $value) {
+            if ($count === -1) {
+                $count = $value;
+            } else {
+                if ($count !== 0) {
+                    if ($x === $width) {
+                        $x = 0;
+                    }
+                    if ($x === 0) {
+                        $uncompressed .= pack('C', 0); // No filtering
+                    }
+                    
+                    $colorIndex = $palette->lookup[$value];
+                    $uncompressed .= str_repeat(pack('C', $colorIndex), $count);
+                    $x += $count;
+                }
+                
+                $count = -1;
+            }
+        }
+
+        $compressed = gzcompress($uncompressed, 2);
+        $this->buffer->writeString($compressed);
+
+        $this->buffer->endChunk();
+    }
+
+    /**
+     * Writes a PLTE chunk containing the indexed colors.
+     *
+     * @param \Jdenticon\Canvas\Png\PngPalette $palette Palette containing the 
+     *      indexed colors.
+     */
+    public function writePalette(PngPalette $palette) 
+    {
+        if ($palette && $palette->isValid) {
+            $this->buffer->startChunk("PLTE");
+
+            foreach ($palette->colors as $color) {
+                $this->buffer->writeString(
+                    pack('C', ($color >> 24) & 0xff) .
+                    pack('C', ($color >> 16) & 0xff) .
+                    pack('C', ($color >> 8) & 0xff));
+            }
+
+            $this->buffer->endChunk();
+        }
+    }
+
+    /**
+     * Writes a tRNS chunk containing the alpha values of indexed colors.
+     *
+     * @param \Jdenticon\Canvas\Png\PngPalette $palette Palette containing the 
+     *      indexed colors.
+     */
+    public function writeTransparency(PngPalette $palette) 
+    {
+        if ($palette && $palette->isValid && $palette->hasAlphaChannel) {
+            $this->buffer->startChunk("tRNS");
+            
+            $alpha = '';
+
+            foreach ($palette->colors as $color) {
+                $alpha .= pack('C', $color & 0xff);
+            }
+            
+            $this->buffer->writeString($alpha);
+
+            $this->buffer->endChunk();
+        }
+    }
+
+    /**
+     * Writes a tEXt chunk containing the specified strings.
+     *
+     * @param string $key Key, one of 
+     *      {@link https://www.w3.org/TR/2003/REC-PNG-20031110/#11keywords}
+     * @param string $value Value.
+     */
+    public function writeTextualData($key, $value) 
+    {
+        $this->buffer->startChunk("tEXt");
+        $this->buffer->writeString($key);
+        $this->buffer->writeUInt8(0);
+        $this->buffer->writeString($value);
+        $this->buffer->endChunk();
+    }
+
+    /**
+     * Writes an IEND chunk to the png data stream.
+     */
+    public function writeImageEnd() 
+    {
+        $this->buffer->startChunk("IEND");
+        $this->buffer->endChunk();
+    }
+
+    /**
+     * Gets a binary string containing the PNG data.
+     *
+     * @return string
+     */
+    public function getBuffer() 
+    {
+        return $this->buffer->getBuffer();
+    }
+}

+ 92 - 0
vendor/jdenticon/jdenticon/src/Canvas/Png/PngPalette.php

@@ -0,0 +1,92 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Canvas\Png;
+
+use Jdenticon\Canvas\ColorUtils;
+
+/**
+ * Contains the colors of a PNG color palette.
+ */
+class PngPalette
+{
+    /**
+     * Creates a PNG color palette for the specified bitmap data.
+     *
+     * @param array(integer) $colorRanges Array of interleaved values on the 
+     *      format array(count0, color0, count1, color1, ...).
+     */
+    function __construct(& $colorRanges) 
+    {
+        $lookup = array();
+        $colors = array();
+        $hasAlphaChannel = false;
+        $colorsCount = 0;
+
+        $count = -1;
+
+        foreach ($colorRanges as $value) {
+            if ($count === -1) {
+                $count = $value;
+            } else {
+                // Ignore empty ranges and already indexed colors
+                if ($count > 0 && !isset($lookup[$value])) {
+                    if (!$hasAlphaChannel && ($value & 0xff) < 255) {
+                        $hasAlphaChannel = true;
+                    }
+
+                    $lookup[$value] = $colorsCount++;
+                    $colors[] = $value;
+                    
+                    if ($colorsCount > 256) {
+                        break;
+                    }
+                }
+                
+                $count = -1;
+            }
+        }
+
+        $this->hasAlphaChannel = $hasAlphaChannel;
+        $this->colors = & $colors;
+        $this->lookup = & $lookup;
+        $this->isValid = $colorsCount <= 256;
+    }
+
+    /**
+     * Specifies if the palette is valid to be used for encoding a PNG image.
+     *
+     * @var boolean
+     */
+    public $isValid;
+
+    /**
+     * Specifies if the palette has any partial or fully transparent
+     * colors.
+     *
+     * @var boolean
+     */
+    public $hasAlphaChannel;
+
+    /**
+     * Array of colors in the palette.
+     *
+     * @var array
+     */
+    public $colors;
+
+    /**
+     * Lookup table from 32-bit color value to color index.
+     *
+     * @var array
+     */
+    public $lookup;
+}

+ 41 - 0
vendor/jdenticon/jdenticon/src/Canvas/Point.php

@@ -0,0 +1,41 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Canvas;
+
+class Point
+{
+    /**
+     * X coordinate.
+     *
+     * @var float
+     */
+    public $x;
+    
+    /**
+     * Y coordinate.
+     *
+     * @var float
+     */
+    public $y;
+
+    /**
+     * Creates a new 2D point.
+     *
+     * @param float $x X coordinate.
+     * @param float $y Y coordinate.
+     */
+    public function __construct($x, $y) 
+    {
+        $this->x = $x;
+        $this->y = $y;
+    }
+}

+ 44 - 0
vendor/jdenticon/jdenticon/src/Canvas/Rasterization/Edge.php

@@ -0,0 +1,44 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Canvas\Rasterization;
+
+class Edge
+{
+    public $polygonId;
+    public $x0;
+    public $x1;
+    public $y0;
+    public $y1;
+    public $color;
+    public $windingRule;
+ 
+    public function __construct(
+        $polygonId, $x0, $y0, $x1, $y1, $color, $windingRule = null) 
+    {
+        $this->polygonId = $polygonId;
+        $this->x0 = $x0;
+        $this->x1 = $x1;
+        $this->y0 = $y0;
+        $this->y1 = $y1;
+        $this->color = $color;
+        $this->windingRule = $windingRule;
+    }
+
+    public function intersection($y)
+    {
+        $dx = 
+            ($this->x1 - $this->x0) * ($this->y0 - $y) / 
+            ($this->y0 - $this->y1);
+        return $this->x0 + $dx;
+    }
+}
+

+ 27 - 0
vendor/jdenticon/jdenticon/src/Canvas/Rasterization/EdgeIntersection.php

@@ -0,0 +1,27 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Canvas\Rasterization;
+
+class EdgeIntersection
+{
+    public $fromX;
+    public $width;
+    public $edge;
+ 
+    public function __construct($fromX, $width, $edge) 
+    {
+        $this->fromX = $fromX;
+        $this->width = $width;
+        $this->edge = $edge;
+    }
+}
+

+ 24 - 0
vendor/jdenticon/jdenticon/src/Canvas/Rasterization/EdgeSuperSampleIntersection.php

@@ -0,0 +1,24 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Canvas\Rasterization;
+
+class EdgeSuperSampleIntersection
+{
+    public $x;
+    public $edge;
+    
+    public function __construct($x, $edge) 
+    {
+        $this->x = $x;
+        $this->edge = $edge;
+    }
+}

+ 158 - 0
vendor/jdenticon/jdenticon/src/Canvas/Rasterization/EdgeTable.php

@@ -0,0 +1,158 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Canvas\Rasterization;
+
+class EdgeTable
+{
+    private $scanlines;
+    private $nextPolygonId;
+    private $width;
+    private $height;
+
+    /**
+     * Keeps a list of edges per scanline.
+     *
+     * @param integer $width  Clipping width.
+     * @param integer $height  Clipping height.
+     */
+    public function __construct($width, $height) 
+    {
+        $this->width = $width;
+        $this->height = $height;
+        $this->clear();
+    }
+
+    /**
+     * Sorts the edges of each scanline in ascending x coordinates.
+     */
+    public function clear() 
+    {
+        $this->scanlines = array();
+        $this->nextPolygonId = 1;
+    }
+
+    /**
+     * Gets an id for the next polygon.
+     *
+     * @return int
+     */
+    public function getNextPolygonId() 
+    {
+        return $this->nextPolygonId++;
+    }
+
+    /**
+     * Gets the scaline for the specified Y coordinate, or NULL if there are 
+     * no edges for the specified Y coordinate.
+     *
+     * @return array|null.
+     */
+    public function getScanline($y)
+    {
+        return isset($this->scanlines[$y]) ? $this->scanlines[$y] : null;
+    }
+
+    /**
+     * Adds an edge to the table.
+     *
+     * @param \Jdenticon\Canvas\Rasterization\Edge $edge
+     */
+    public function add(\Jdenticon\Canvas\Rasterization\Edge $edge) 
+    {
+        $minY = 0;
+        $maxY = 0;
+
+        if ($edge->y0 == $edge->y1) {
+            // Skip horizontal lines
+            return;
+        } elseif ($edge->y0 < $edge->y1) {
+            $minY = (int)($edge->y0);
+            $maxY = (int)($edge->y1 + 0.996 /* 1/255 */);
+        } else {
+            $minY = (int)($edge->y1);
+            $maxY = (int)($edge->y0 + 0.996 /* 1/255 */);
+        }
+
+        if ($maxY < 0 || $minY >= $this->height) {
+            return;
+        }
+
+        if ($minY < 0) {
+            $minY = 0;
+        }
+        if ($maxY > $this->height) {
+            $maxY = $this->height;
+        }
+        
+        if ($minY < $maxY) {
+            $y = $minY;
+            $x1 = $edge->intersection($y);
+
+            while ($y < $maxY) {
+                $x2 = $edge->intersection($y + 1);
+
+                $fromX;
+                $width;
+                if ($x1 < $x2) {
+                    $fromX = (int)($x1);
+                    $width = (int)($x2 + 0.9999) - $fromX;
+                } else {
+                    $fromX = (int)($x2);
+                    $width = (int)($x1 + 0.9999) - $fromX;
+                }
+
+                if ($fromX < 0) {
+                    $width += $fromX;
+                    $fromX = 0;
+
+                    if ($width < 0) {
+                        $width = 0;
+                    }
+                }
+
+                if ($fromX < $this->width) {
+                    if (!isset($this->scanlines[$y])) {
+                        $this->scanlines[$y] = array();
+                    }
+                    
+                    $this->scanlines[$y][] = new EdgeIntersection(
+                        $fromX, $width, $edge);
+                }
+
+                $x1 = $x2;
+                $y++;
+            }
+        }
+    }
+
+    private static function edge_cmp($x, $y)
+    {
+        if ($x->fromX < $y->fromX) {
+            return -1;
+        }
+        if ($x->fromX > $y->fromX) {
+            return 1;
+        }
+        return 0;
+    }
+
+    /**
+     * Sorts the edges of each scanline in ascending x coordinates.
+     */
+    public function sort() 
+    {
+        foreach ($this->scanlines as $i => &$scanline) {
+            usort($scanline, array(
+                'Jdenticon\\Canvas\\Rasterization\\EdgeTable', 'edge_cmp'));
+        }
+    }
+}

+ 30 - 0
vendor/jdenticon/jdenticon/src/Canvas/Rasterization/Layer.php

@@ -0,0 +1,30 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Canvas\Rasterization;
+
+class Layer
+{
+    public $polygonId;
+    public $color;
+    public $winding;
+    public $windingRule;
+    
+    public $nextLayer;
+    
+    public function __construct($polygonId, $color, $winding, $windingRule)
+    {
+        $this->polygonId = $polygonId;
+        $this->color = $color;
+        $this->winding = $winding;
+        $this->windingRule = $windingRule;
+    }
+}

+ 150 - 0
vendor/jdenticon/jdenticon/src/Canvas/Rasterization/LayerManager.php

@@ -0,0 +1,150 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Canvas\Rasterization;
+
+use Jdenticon\Canvas\ColorUtils;
+use Jdenticon\Canvas\Rasterization\Layer;
+use Jdenticon\Canvas\Rasterization\Edge;
+
+/**
+ * Keeps track of the z-order of the currently rendered polygons,
+ * and computes the final color from the stack of layers.
+ */
+class LayerManager
+{
+    public $topLayer;
+
+    /**
+     * The current visible color.
+     * @var integer
+     */
+    public $color;
+
+    public function __construct() 
+    {
+        $this->color = ColorUtils::TRANSPARENT;
+    }
+
+    /**
+     * Copies all layers in this manager to another LayerManager.
+     *
+     * @param \Jdenticon\Canvas\Rasterization\LayerManager $other The 
+     *      LayerManager to copy all layers to.
+     */
+    public function copyTo(LayerManager $other) 
+    {
+        $other->color = $this->color;
+        
+        $layer = $this->topLayer;
+        $previousCopy = null;
+        
+        while ($layer !== null) {
+            $copy = new Layer(
+                $layer->polygonId,
+                $layer->color,
+                $layer->winding,
+                $layer->windingRule
+                );
+            
+            if ($previousCopy === null) {
+                $other->topLayer = $copy;
+            }
+            else {
+                $previousCopy->nextLayer = $copy;
+            }
+            
+            $previousCopy = $copy;
+            $layer = $layer->nextLayer;
+        }
+    }
+
+    /**
+     * Adds a layer for the specified edge. The z-order is defined by its id.
+     *
+     * @param \Jdenticon\Canvas\Rasterization\Edge edge
+     */
+    public function add(Edge $edge) 
+    {
+        $dwinding = $edge->y0 < $edge->y1 ? 1 : -1;
+        
+        $layer = $this->topLayer;
+        $previousLayer = null;
+        
+        while ($layer !== null) {
+            if ($layer->polygonId === $edge->polygonId) {
+                $layer->winding += $dwinding;
+
+                $inPath = $layer->windingRule == 'evenodd' ? 
+                    ($layer->winding % 2 === 1) : ($layer->winding !== 0);
+
+                if (!$inPath) {
+                    // Remove layer
+                    if ($previousLayer === null) {
+                        $this->topLayer = $layer->nextLayer;
+                    }
+                    else {
+                        $previousLayer->nextLayer = $layer->nextLayer;
+                    }
+                }
+                break;
+            } elseif ($layer->polygonId < $edge->polygonId) {
+                // Insert here
+                $newLayer = new Layer(
+                    $edge->polygonId,
+                    $edge->color,
+                    $dwinding,
+                    $edge->windingRule
+                );
+                $newLayer->nextLayer = $layer;
+                
+                if ($previousLayer === null) {
+                    $this->topLayer = $newLayer;
+                } else {
+                    $previousLayer->nextLayer = $newLayer;
+                }
+                break;
+            }
+            
+            $previousLayer = $layer;
+            $layer = $layer->nextLayer;
+        }
+        
+        if ($layer === null) {
+            $newLayer = new Layer(
+                $edge->polygonId,
+                $edge->color,
+                $dwinding,
+                $edge->windingRule
+            );
+            
+            if ($previousLayer === null) {
+                $this->topLayer = $newLayer;
+            } else {
+                $previousLayer->nextLayer = $newLayer;
+            }
+        }
+
+        // Update current color
+        $color = ColorUtils::TRANSPARENT;
+        $layer = $this->topLayer;
+        
+        while ($layer !== null && ($color & 0xff) < 255) {
+            if ($layer->color === ColorUtils::FORCE_TRANSPARENT) {
+                break;
+            }
+            
+            $color = ColorUtils::over($layer->color, $color);
+        }
+        
+        $this->color = $color;
+    }
+}

+ 379 - 0
vendor/jdenticon/jdenticon/src/Canvas/Rasterization/Rasterizer.php

@@ -0,0 +1,379 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Canvas\Rasterization;
+
+use Jdenticon\Canvas\ColorUtils;
+use Jdenticon\Canvas\Rasterization\LayerManager;
+use Jdenticon\Canvas\Rasterization\SuperSampleBuffer;
+use Jdenticon\Canvas\Rasterization\SuperSampleRange;
+use Jdenticon\Canvas\Rasterization\EdgeSuperSampleIntersection;
+
+class Rasterizer
+{
+    /**
+     * A higher number of samples per pixel horizontally does not affect the
+     * performance in the same way as SAMPLES_PER_PIXEL_Y, since the rasterizer
+     * does not scan every subpixel horizontally.
+     */
+    const SAMPLES_PER_PIXEL_X = 10;
+
+    /**
+     * A higher number of samples vertically means lower performance, since
+     * the rasterizer does a scan for every subpixel vertically.
+     */
+    const SAMPLES_PER_PIXEL_Y = 3;
+
+    const SAMPLE_HEIGHT = 0.33333; //  1 / self::SAMPLES_PER_PIXEL_Y
+    
+    /**
+     * Rasterizes the edges in the edge table to a list of color ranges. No 
+     * range will span multiple scanlines.
+     */
+    public static function rasterize(& $colorData, $edgeTable, $width, $height) 
+    {
+        $edgeTable->sort();
+
+        $superSampleBuffer = new SuperSampleBuffer(
+            $width, self::SAMPLES_PER_PIXEL_X);
+
+        $layers = array();
+        $color = 0;
+        
+        // Keeps track of how many of the subpixellayers that are used for 
+        // the currently rendered scanline. Until a range requiring 
+        // supersampling is encountered only a single layer is needed.
+        $usedLayers = 0;
+
+        for ($i = 0; $i < self::SAMPLES_PER_PIXEL_Y; $i++) {
+            $layers[] = new LayerManager();
+        }
+
+        for ($ey = 0; $ey < $height; $ey++) {
+            $scanline = $edgeTable->getScanline($ey);
+            if ($scanline === null) {
+                $colorData[] = $width;
+                $colorData[] = 0;
+                continue;
+            }
+            
+            $superSampleRanges = self::getSuperSampleRanges($scanline, $width);
+            $superSampleRangeCount = count($superSampleRanges);
+            
+            foreach ($layers as $layer) {
+                $layer->topLayer = null;
+                $layer->color = ColorUtils::TRANSPARENT;
+            }
+            
+            $usedLayers = 1;
+            
+            if ($superSampleRanges[0]->fromX) {
+                $colorData[] = $superSampleRanges[0]->fromX;
+                $colorData[] = 0;
+            }
+ 
+            for (
+                $rangeIndex = 0; 
+                $rangeIndex < $superSampleRangeCount; 
+                $rangeIndex++
+            ) {
+                $superSampleRange = $superSampleRanges[$rangeIndex];
+                $edge = $superSampleRange->edges[0];
+                
+                // If there is exactly one edge in the supersample range, and it
+                // is crossing the entire scanline, we can perform the 
+                // antialiasing by integrating the edge function.
+                if (!isset($superSampleRange->edges[1]) && (
+                        $edge->y0 <= $ey     && $edge->y1 >= $ey + 1 ||
+                        $edge->y0 >= $ey + 1 && $edge->y1 <= $ey
+                    )) {
+                    // Determine the lower and upper x value where the edge 
+                    // intersects the scanline.
+                    $xey = $edge->intersection($ey);
+                    $xey1 = $edge->intersection($ey + 1);
+                    
+                    if ($xey < $xey1) {
+                        $x0 = $xey;
+                        $x1 = $xey1;
+                    } else {
+                        $x0 = $xey1;
+                        $x1 = $xey;
+                    }
+                    
+                    $rangeWidth = $x1 - $x0;
+                    
+                    if ($usedLayers === 1) {
+                        $subScanlineLayers = $layers[0];
+                        $fromColor = $subScanlineLayers->color;
+                        $subScanlineLayers->add($edge);
+                        $toColor = $subScanlineLayers->color;
+                    } else {
+                        $fromColorR = 0; 
+                        $fromColorG = 0; 
+                        $fromColorB = 0; 
+                        $fromColorA = 0;
+                        $toColorR = 0; 
+                        $toColorG = 0; 
+                        $toColorB = 0; 
+                        $toColorA = 0;
+
+                        // Compute the average color of all subpixel layers 
+                        // before and after the edge intersection.
+                        // The calculation is inlined for increased performance.
+                        for ($i = 0; $i < $usedLayers; $i++) {
+                            $subScanlineLayers = $layers[$i];
+                            
+                            // Add to average from-color
+                            $color = $subScanlineLayers->color;
+                            $alpha = $color & 0xff;
+                            if ($alpha > 0) {
+                                $fromColorA += $alpha;
+                                $fromColorR += (($color >> 24) & 0xff) * $alpha;
+                                $fromColorG += (($color >> 16) & 0xff) * $alpha;
+                                $fromColorB += (($color >> 8) & 0xff) * $alpha;
+                            }
+                            
+                            // Add the new layer
+                            $subScanlineLayers->add($edge);
+                            
+                            // Add to average to-color
+                            $color = $subScanlineLayers->color;
+                            $alpha = $color & 0xff;
+                            if ($alpha > 0) {
+                                $toColorA += $alpha;
+                                $toColorR += (($color >> 24) & 0xff) * $alpha;
+                                $toColorG += (($color >> 16) & 0xff) * $alpha;
+                                $toColorB += (($color >> 8) & 0xff) * $alpha;
+                            }
+                        }
+
+                        $fromColor = $fromColorA === 0 ? 0 : ColorUtils::from(
+                            (int)($fromColorA / $usedLayers),
+                            (int)($fromColorR / $fromColorA),
+                            (int)($fromColorG / $fromColorA),
+                            (int)($fromColorB / $fromColorA));
+                            
+                        $toColor = $toColorA === 0 ? 0 : ColorUtils::from(
+                            (int)($toColorA / $usedLayers),
+                            (int)($toColorR / $toColorA),
+                            (int)($toColorG / $toColorA),
+                            (int)($toColorB / $toColorA));
+                    }
+                    
+                    // Render pixels
+                    for (
+                        $x = $superSampleRange->fromX; 
+                        $x < $superSampleRange->toXExcl; 
+                        $x++
+                    ) {
+                        if ($x0 >= $x + 1) {
+                            // Pixel not covered
+                            $colorData[] = 1;
+                            $colorData[] = $fromColor;
+                            continue;
+                        }
+
+                        if ($x1 <= $x) {
+                            // Pixel fully covered
+                            $colorData[] = 1;
+                            $colorData[] = $toColor;
+                            continue;
+                        }
+                        
+                        // toColor coverage in the range [0.0, 1.0]
+                        // Initialize to the fully covered range of the pixel.
+                        $coverage = $x1 < $x + 1 ? $x + 1 - $x1 : 0;
+
+                        // Compute integral for non-vertical edges
+                        if ($rangeWidth > 0.001) {
+                            // Range to integrate
+                            $integralFrom = $x0 > $x ? $x0 : $x;;
+                            $integralTo = $x1 < $x + 1 ? $x1 : $x + 1;
+
+                            $coverage += 
+                                (
+                                    (
+                                        $integralTo * $integralTo - 
+                                        $integralFrom * $integralFrom
+                                    ) / 2 +
+                                    $x0 * ($integralFrom - $integralTo)
+                                ) / $rangeWidth;
+                        }
+                        
+                        $colorData[] = 1;
+                        $colorData[] = ColorUtils::mix(
+                            $fromColor, $toColor, $coverage);
+                    }
+                    
+                    $color = $toColor;
+                    
+                } // /simplified antialiasing
+                else {
+                    // Super sampling 
+                    $y = $ey + self::SAMPLE_HEIGHT / 2;
+                    
+                    // Ensure all subpixel layers are initialized
+                    while ($usedLayers < self::SAMPLES_PER_PIXEL_Y) {
+                        $layers[0]->copyTo($layers[$usedLayers]);
+                        $usedLayers++;
+                    }
+                    
+                    foreach ($layers as $subScanlineLayers) {
+                        $color = $subScanlineLayers->color;
+
+                        $intersections = self::getIntersections(
+                            $superSampleRange->edges, $y);
+
+                        foreach ($intersections as $intersection) {
+                            $superSampleBuffer->add($color, 
+                                $intersection->x - $superSampleRange->fromX);
+                            $subScanlineLayers->add($intersection->edge);
+                            $color = $subScanlineLayers->color;
+                        }
+
+                        $superSampleBuffer->add(
+                            $color, $superSampleRange->width + 1);
+                        $superSampleBuffer->rewind();
+
+                        $y += self::SAMPLE_HEIGHT;
+                    } // /subpixel
+
+                    // Blend subpixels
+                    $color = $superSampleBuffer->colorAt(
+                        $superSampleRange->width);
+                    $superSampleBuffer->emptyTo(
+                        $colorData, $superSampleRange->width);
+                    
+                    //$color = end($colorData);
+                } // /super sampling
+                
+                // Forward last color
+                if ($rangeIndex + 1 < $superSampleRangeCount) {
+                    $count = 
+                        $superSampleRanges[$rangeIndex + 1]->fromX - 
+                        $superSampleRange->toXExcl;
+                        
+                    if ($count > 0) {
+                        $colorData[] = $count;
+                        $colorData[] = $color;
+                    }
+                } else {
+                    $count = $width - $superSampleRange->toXExcl;
+                    if ($count > 0) {
+                        $colorData[] = $count;
+                        $colorData[] = $color;
+                    }
+                }
+            } // /range
+        }
+
+        return $colorData;
+    }
+    
+    private static function intersection_cmp($a, $b) 
+    {
+        if ($a->x < $b->x) {
+            return -1;
+        }
+        
+        if ($a->x > $b->x) {
+            return 1;
+        }
+         
+        return 0;
+    }
+
+    /**
+     * Determines what edges that intersect a horizontal line with the specified 
+     * y coordinate. For each intersecting edge the intersecting x coordinate is 
+     * returned.
+     *
+     * @param array $edges Array of edges in the current scanline.
+     * @param int $y Y coordinate of the current scanline.
+     * @return array Array containing EdgeSuperSampleIntersection. Objects
+     * are sorted ascending by x coordinate.
+     */
+    private static function getIntersections($edges, $y) 
+    {
+        $intersections = array();
+
+        foreach ($edges as $edge) {
+            if ($edge->y0 < $y && $edge->y1 >= $y ||
+                $edge->y0 >= $y && $edge->y1 < $y
+            ) {
+                $x = $edge->x0 +
+                    ($edge->x1 - $edge->x0) * ($y - $edge->y0) /
+                    ($edge->y1 - $edge->y0);
+
+                $intersections[] = new EdgeSuperSampleIntersection($x, $edge);
+            }
+        }
+
+        usort($intersections, array(
+            'Jdenticon\\Canvas\\Rasterization\\Rasterizer', 
+            'intersection_cmp'));
+        
+        return $intersections;
+    }
+
+    /**
+     * Determines what ranges of a scanline that needs to be supersampled.
+     *
+     * @param array $scanline  Array of edges in the current scanline.
+     * @return array  Array of SuperSampleRange.
+     */
+    private static function getSuperSampleRanges(&$scanline, $width) 
+    {
+        $superSampleRanges = array();
+
+        $rangeIndex = 0;
+        $scanlineCount = count($scanline);
+
+        while ($rangeIndex < $scanlineCount) {
+            $range = $scanline[$rangeIndex];
+            
+            if ($range->fromX >= $width) {
+                break;
+            }
+            
+            $superSampleRange = new SuperSampleRange(
+                $range->fromX,
+                $range->fromX + $range->width
+            );
+            $superSampleRange->edges[] = $range->edge;
+            
+            $rangeIndex++;
+
+            for ($i = $rangeIndex; $i < $scanlineCount; $i++) {
+                $range = $scanline[$i];
+                
+                if ($range->fromX < $superSampleRange->toXExcl) {
+                    $superSampleRange->toXExcl = max(
+                        $superSampleRange->toXExcl, 
+                        $range->fromX + $range->width);
+                    $superSampleRange->edges[] = $range->edge;
+                    $rangeIndex++;
+                } else {
+                    break;
+                }
+            }
+
+            $superSampleRange->toXExcl = min($superSampleRange->toXExcl, $width);
+            $superSampleRange->width = 
+                $superSampleRange->toXExcl - $superSampleRange->fromX;
+            
+            $superSampleRanges[] = $superSampleRange;
+        }
+
+        return $superSampleRanges;
+    }
+}
+

+ 198 - 0
vendor/jdenticon/jdenticon/src/Canvas/Rasterization/SuperSampleBuffer.php

@@ -0,0 +1,198 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Canvas\Rasterization;
+
+use Jdenticon\Canvas\ColorUtils;
+
+class SuperSampleBuffer
+{
+    const IDX_COUNT = 0;
+    const IDX_A = 1;
+    const IDX_R = 2;
+    const IDX_G = 3;
+    const IDX_B = 4;
+
+    /**
+     * Creates a color buffer keeping an average color out of several
+     * color samples per pixel.
+     *
+     * @param integer $width  Width of the buffer in pixels.
+     * @param integer $samplesPerPixel  Number of samples to keep per pixel.
+     */
+    public function __construct($width, $samplesPerPixel) 
+    {
+        $this->samples = array();
+        $this->samplesPerPixel = $samplesPerPixel;
+
+        $this->pixelOffset = 0;
+        $this->subPixelOffset = 0;
+        
+        $this->width = $width;
+        $this->used = -1;
+    }
+
+    /**
+     * Rewinds the cursor to the beginning of the buffer.
+     */
+    public function rewind() 
+    {
+        $this->pixelOffset = 0;
+        $this->subPixelOffset = 0;
+    }
+
+    /**
+     * Clears the samples in this buffer.
+     */
+    public function clear() 
+    {
+        $this->pixelOffset = 0;
+        $this->subPixelOffset = 0;
+        $this->used = -1;
+    }
+
+    /**
+     * Writes the average color of each pixel to a specified color array.
+     *
+     * @param array $colorData The average colors will be written to this 
+     *      color array.
+     * @param integer $count  Number of pixels to write.
+     */
+    public function emptyTo(& $colorData, $count) 
+    {
+        for ($i = 0; $i < $count; $i++) {
+            $sampleCount = $this->samples[$i * 5 + self::IDX_COUNT];
+            $a = $this->samples[$i * 5 + self::IDX_A];
+            $color = $sampleCount == 0 || $a == 0 ? 0 :
+                ColorUtils::from(
+                    (int)($a / $sampleCount),
+                    (int)($this->samples[$i * 5 + self::IDX_R] / $a),
+                    (int)($this->samples[$i * 5 + self::IDX_G] / $a),
+                    (int)($this->samples[$i * 5 + self::IDX_B] / $a)
+                );
+            
+            $colorData[] = 1;
+            $colorData[] = $color;
+        }
+        
+        $this->pixelOffset = 0;
+        $this->subPixelOffset = 0;
+        $this->used = -1;
+    }
+    
+    /**
+     * Gets the average color of the pixel at a specified index.
+     *
+     * @param integer $index The index of the pixel.
+     * @return integer
+     */
+    public function colorAt($index) 
+    {
+        $sampleCount = $this->samples[$index * 5 + self::IDX_COUNT];
+        $alphaSum = $this->samples[$index * 5 + self::IDX_A];
+        return $sampleCount == 0 || $alphaSum == 0 ? 0 :
+            ColorUtils::from(
+                (int)($alphaSum / $sampleCount),
+                (int)($this->samples[$index * 5 + self::IDX_R] / $alphaSum),
+                (int)($this->samples[$index * 5 + self::IDX_G] / $alphaSum),
+                (int)($this->samples[$index * 5 + self::IDX_B] / $alphaSum)
+            );
+    }
+
+    /**
+     * Adds a color to the current pixel in the buffer.
+     *
+     * @param integer $count Number of samples of the color to be added to 
+     *      the buffer.
+     */
+    private function _add($count, $a, $r, $g, $b) 
+    {
+        if ($this->used < $this->pixelOffset) {
+            $this->used = $this->pixelOffset;
+            
+            $this->samples[$this->pixelOffset * 5 + self::IDX_COUNT] = $count;
+            $this->samples[$this->pixelOffset * 5 + self::IDX_A] = $a * $count;
+            $this->samples[$this->pixelOffset * 5 + self::IDX_R] = $a * $r * $count;
+            $this->samples[$this->pixelOffset * 5 + self::IDX_G] = $a * $g * $count;
+            $this->samples[$this->pixelOffset * 5 + self::IDX_B] = $a * $b * $count;
+        } else {
+            $this->samples[$this->pixelOffset * 5 + self::IDX_COUNT] += $count;
+
+            if ($a > 0) {
+                $this->samples[$this->pixelOffset * 5 + self::IDX_A] += $a * $count;
+                $this->samples[$this->pixelOffset * 5 + self::IDX_R] += $a * $r * $count;
+                $this->samples[$this->pixelOffset * 5 + self::IDX_G] += $a * $g * $count;
+                $this->samples[$this->pixelOffset * 5 + self::IDX_B] += $a * $b * $count;
+            }
+        }
+    }
+    
+    /**
+     * Adds a color to the buffer up until the specified x index.
+     *
+     * @param integer $color Color to write.
+     * @param float $untilX Samples of the color will be added the buffer until 
+     *      the cursor reaches this coordinate.
+     */
+    public function add($color, $untilX) 
+    {
+        $samplesLeft = 
+            (int)($untilX * $this->samplesPerPixel) - 
+            $this->subPixelOffset - 
+            $this->pixelOffset * $this->samplesPerPixel;
+
+        // ColorUtils methods inlined for performance reasons
+        $a = ($color) & 0xff;
+        $r = ($color >> 24) & 0xff;
+        $g = ($color >> 16) & 0xff;
+        $b = ($color >> 8) & 0xff;
+        
+        // First partial pixel
+        if ($this->subPixelOffset > 0) {
+            $samples = $this->samplesPerPixel - $this->subPixelOffset;
+            if ($samples > $samplesLeft) {
+                $samples = $samplesLeft;
+            }
+            $samplesLeft -= $samples;
+
+            $this->_add($samples, $a, $r, $g, $b);
+
+            $this->subPixelOffset += $samples;
+            if ($this->subPixelOffset == $this->samplesPerPixel) {
+                $this->subPixelOffset = 0;
+                $this->pixelOffset++;
+            }
+        }
+
+        // Full pixels
+        $fullPixels = (int)($samplesLeft / $this->samplesPerPixel);
+        if ($fullPixels > 0) {
+            for ($i = 0; $i < $fullPixels; $i++) {
+                $this->_add($this->samplesPerPixel, $a, $r, $g, $b);
+                $this->pixelOffset++;
+            }
+
+            $samplesLeft -= $fullPixels * $this->samplesPerPixel;
+        }
+
+        // Last partial pixel
+        if ($samplesLeft > 0) {
+            $this->_add($samplesLeft, $a, $r, $g, $b);
+
+            $this->subPixelOffset += $samplesLeft;
+
+            if ($this->subPixelOffset == $this->samplesPerPixel) {
+                $this->subPixelOffset = 0;
+                $this->pixelOffset++;
+            }
+        }
+    }
+}

+ 27 - 0
vendor/jdenticon/jdenticon/src/Canvas/Rasterization/SuperSampleRange.php

@@ -0,0 +1,27 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Canvas\Rasterization;
+
+class SuperSampleRange
+{
+    public $fromX;
+    public $toXExcl;
+    public $edges;
+    public $width;
+    
+    public function __construct($fromX, $toXExcl) 
+    {
+        $this->fromX = $fromX;
+        $this->toXExcl = $toXExcl;
+        $this->edges = array();
+    }
+}

+ 605 - 0
vendor/jdenticon/jdenticon/src/Color.php

@@ -0,0 +1,605 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon;
+
+/**
+ * Represents a 24-bit color with a 8-bit alpha channel.
+ */
+class Color
+{
+    private static $lightnessCompensations = array(
+        0.55, 0.5, 0.5, 0.46, 0.6, 0.55, 0.55);
+    
+    /**
+     * The red component of the color in the range [0, 255].
+     * @var int
+     */
+    public $r;
+    
+    /**
+     * The green component of the color in the range [0, 255].
+     * @var int
+     */
+    public $g;
+    
+    /**
+     * The blue component of the color in the range [0, 255].
+     * @var int
+     */
+    public $b;
+    
+    /**
+     * The alpha component of the color in the range [0, 255].
+     * @var int
+     */
+    public $a;
+
+    // Users of the struct should use the static factory methods 
+    // to create Color value.
+    private function __construct()
+    {
+    }
+
+    /**
+     * Creates a Color from an RGB value.
+     *
+     * @param int $alpha Alpha channel value in the range [0, 255].
+     * @param int $red Red component in the range [0, 255].
+     * @param int $green GReen component in the range [0, 255].
+     * @param int $blue Blue component in the range [0, 255].
+     */
+    public static function fromRgb($red, $green, $blue, $alpha = 255)
+    {
+        $color = new Color();
+        $color->r = $red;
+        $color->g = $green;
+        $color->b = $blue;
+        $color->a = $alpha;
+        return $color;
+    }
+
+    /**
+     * Creates a Color instance from HSL color parameters.
+     *
+     * @param float $hue Hue in the range [0, 1]
+     * @param float $saturation Saturation in the range [0, 1]
+     * @param float $lightness Lightness in the range [0, 1]
+     * @param float $alpha Alpha channel value in the range [0, 1].
+     */
+    public static function fromHsl($hue, $saturation, $lightness, $alpha = 1.0)
+    {
+        if ($hue < 0) $hue = 0;
+        if ($hue > 1) $hue = 1;
+        
+        if ($saturation < 0) $saturation = 0;
+        if ($saturation > 1) $saturation = 1;
+        
+        if ($lightness < 0) $lightness = 0;
+        if ($lightness > 1) $lightness = 1;
+        
+        if ($alpha < 0) $alpha = 0;
+        if ($alpha > 1) $alpha = 1;
+
+        // Based on http://www.w3.org/TR/2011/REC-css3-color-20110607/#hsl-color
+        if ($saturation == 0) {
+            $value = (int)($lightness * 255);
+            return self::fromRgb($value, $value, $value, (int)($alpha * 255));
+        } else {
+            if ($lightness <= 0.5) {
+                $m2 = $lightness * ($saturation + 1);
+            } else {
+                $m2 = $lightness + $saturation - $lightness * $saturation;
+            }
+            
+            $m1 = $lightness * 2 - $m2;
+
+            return self::fromRgb(
+                self::hueToRgb($m1, $m2, $hue * 6 + 2),
+                self::hueToRgb($m1, $m2, $hue * 6),
+                self::hueToRgb($m1, $m2, $hue * 6 - 2),
+                (int)($alpha * 255));
+        }
+    }
+    
+    /**
+     * Creates a Color> instance from HSL color parameters and will compensate 
+     * the lightness for hues that appear to be darker than others.
+     *
+     * @param float $hue Hue in the range [0, 1].
+     * @param float $saturation Saturation in the range [0, 1].
+     * @param float $lightness Lightness in the range [0, 1].
+     * @param float $alpha Alpha channel value in the range [0, 1].
+     */
+    public static function fromHslCompensated($hue, $saturation, $lightness, $alpha = 1.0)
+    {
+        if ($hue < 0) $hue = 0;
+        if ($hue > 1) $hue = 1;
+        
+        $lightnessCompensation = self::$lightnessCompensations[(int)($hue * 6 + 0.5)];
+        
+        // Adjust the input lightness relative to the compensation
+        $lightness = $lightness < 0.5 ?
+            $lightness * $lightnessCompensation * 2 : 
+            $lightnessCompensation + ($lightness - 0.5) * (1 - $lightnessCompensation) * 2;
+
+        return self::fromHsl($hue, $saturation, $lightness, $alpha);
+    }
+
+    // Helper method for FromHsl
+    private static function hueToRgb($m1, $m2, $h)
+    {
+        if ($h < 0) {
+            $h = $h + 6;
+        } elseif ($h > 6) {
+            $h = $h - 6;
+        }
+        
+        if ($h < 1) {
+            $r = $m1 + ($m2 - $m1) * $h;
+        } elseif ($h < 3) {
+            $r = $m2;
+        } elseif ($h < 4) {
+            $r = $m1 + ($m2 - $m1) * (4 - $h);
+        } else {
+            $r = $m1;
+        }
+        
+        return (int)(255 * $r);
+    }
+
+    /**
+     * Gets the argb value of this color.
+     *
+     * @return int
+     */
+    public function toRgba()
+    {
+        return 
+            ($this->r << 24) |
+            ($this->g << 16) |
+            ($this->b << 8) |
+            ($this->a);
+    }
+
+    /**
+     * Gets a hexadecimal representation of this color on the format #rrggbbaa.
+     *
+     * @return string
+     */
+    public function __toString()
+    {
+        return '#' . bin2hex(pack('N', $this->toRgba()));
+    }
+    
+    /**
+     * Gets a hexadecimal representation of this color on the format #rrggbbaa.
+     *
+     * @return string
+     */
+    public function toHexString($length = 8)
+    {
+        if ($length === 8) {
+            return $this->__toString();
+        }
+        return '#' . substr(bin2hex(pack('N', $this->toRgba())), 0, 6);
+    }
+    
+    /**
+     * Tries to parse a value as a Color.
+     * 
+     * @param mixed $value Value to parse.
+     * @throws InvalidArgumentException
+     * @return \Jdenticon\Color
+     */
+    public static function parse($value) {
+        if ($value instanceof Color) {
+            return $value;
+        }
+        
+        $value = strtolower("$value");
+        
+        if (preg_match('/^#?[0-9a-f]{3,8}$/', $value) &&
+            self::parseHexColor($value, $result)
+        ) {
+            return $result;
+        }
+        
+        if (preg_match(
+                '/^rgba?\\(([^,]+),([^,]+),([^,]+)(?:,([^,]+))?\\)$/', 
+                $value, $matches) &&
+            self::parseRgbComponent($matches[1], $r) &&
+            self::parseRgbComponent($matches[2], $g) &&
+            self::parseRgbComponent($matches[3], $b) &&
+            self::parseAlpha(isset($matches[4]) ? $matches[4] : null, $a)
+        ) {
+            return self::fromRgb($r, $g, $b, (int)(255 * $a));
+        }
+        
+        if (preg_match(
+                '/^hsla?\\(([^,]+),([^,]+),([^,]+)(?:,([^,]+))?\\)$/', 
+                $value, $matches) &&
+            self::parseHue($matches[1], $h) &&
+            self::parsePercent($matches[2], $s) &&
+            self::parsePercent($matches[3], $l) &&
+            self::parseAlpha(isset($matches[4]) ? $matches[4] : null, $a)
+        ) {
+            return self::fromHsl($h, $s, $l, $a);
+        }
+        
+        $result = self::parseNamedColor($value);
+        if ($result !== null) {
+            return $result;
+        }
+        
+        throw new \InvalidArgumentException(
+            "Cannot parse '$value' as a color.");
+    }
+    
+    /**
+     * Parses a percent value.
+     * 
+     * @param string $input Input string.
+     * @param float $result Resulting value in range [0, 1].
+     * 
+     * @return boolean
+     */
+    private static function parsePercent($input, &$result)
+    {
+        // Detect and remove percent sign
+        if (preg_match('/^\\s*(\\d*(?:\\.\\d*)?)%\\s*$/', $input, $matches)) {
+            $result = floatval($matches[1]) / 100;
+            
+            if ($result < 0) $result = 0;
+            if ($result > 1) $result = 1;
+            
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Parses an alpha value.
+     * 
+     * @param string $input Input string.
+     * @param float $result Resulting alpha in range [0, 1].
+     * 
+     * @return boolean
+     */
+    private static function parseAlpha($input, &$result)
+    {
+        if ($input === null ||
+            $input === ''
+        ) {
+            $result = 1;
+            return true;
+        }
+        
+        if (preg_match('/^\\s*(\\d*(?:\\.\\d*)?)(%?)\\s*$/', $input, $matches)) {
+            $result = floatval($matches[1]);
+            
+            // Percentage
+            if ($matches[2] !== '') {
+                $result = $result / 100;
+            }
+            
+            if ($result < 0) $result = 0;
+            if ($result > 1) $result = 1;
+            
+            return true;
+        }
+        
+        return false;
+    }
+
+    /**
+     * Parses an RGB component.
+     * 
+     * @param string $input  Input string.
+     * @param float  $result Hue in range [0, 255].
+     * 
+     * @return boolean
+     */
+    private static function parseRgbComponent($input, &$result)
+    {
+        if (preg_match('/^\\s*(\\d*(?:\\.\\d*)?)(%?)\\s*$/', $input, $matches)) {
+            $result = floatval($matches[1]);
+            
+            if ($matches[2] === '%') {
+                $result = 255 * $result / 100;
+            }
+            
+            $result = (int)$result;
+            
+            if ($result < 0) $result = 0;
+            if ($result > 255) $result = 255;
+            
+            return true;
+        }
+        return false;
+    }
+
+    /**
+     * Parses a hue component.
+     * 
+     * @param string $input Input string.
+     * @param float $result Hue in range [0, 1].
+     * 
+     * @return boolean
+     */
+    private static function parseHue($input, &$result)
+    {
+        if (preg_match(
+            '/^\s*(\d*(?:\.\d*)?)(deg|grad|rad|turn|)\s*$/', 
+            $input, $matches)
+        ) {
+            $result = floatval($matches[1]);
+            
+            // Percentage
+            switch ($matches[2]) {
+                case "grad":
+                    // Gradians: range 0 - 400
+                    $result = $result / 400;
+                    break;
+                case "rad":
+                    // Radians: range 0 - 2pi
+                    $result = $result / M_PI / 2;
+                    break;
+                case "turn":
+                    // Turns: range 0 - 1
+                    $result = $result;
+                    break;
+                default:
+                    // Degree: range 0 - 360
+                    $result = $result / 360;
+                    break;
+            }
+            
+            $result = fmod($result, 1);
+            
+            if ($result < 0) {
+                $result += 1;
+            }
+            
+            return true;
+        }
+        return false;
+    }
+    
+    /**
+     * Parses a hex color string.
+     * 
+     * @param string $input Input string.
+     * @param float $result Hue in range [0, 1].
+     * 
+     * @return boolean
+     */
+    private static function parseHexColor($input, &$result) 
+    {
+        if ($input[0] === '#') {
+            $input = substr($input, 1);
+        }
+
+        // intval does not support unsigned 32-bit integers
+        // so we need to parse large numbers stepwise
+        $numeric24bit = intval(substr($input, 0, 6), 16);
+        $alpha8bit = intval(substr($input, 6, 2), 16);
+
+        switch (strlen($input)) {
+            case 3:
+                $result = self::fromRgb(
+                    (($numeric24bit & 0xf00) >> 8) |
+                    (($numeric24bit & 0xf00) >> 4),
+                    (($numeric24bit & 0x0f0) >> 4) |
+                    (($numeric24bit & 0x0f0)),
+                    (($numeric24bit & 0x00f) << 4) |
+                    (($numeric24bit & 0x00f))
+                    );
+                return true;
+                
+            case 4:
+                $result = self::fromRgb(
+                    (($numeric24bit & 0xf000) >> 12) |
+                    (($numeric24bit & 0xf000) >> 8),
+                    (($numeric24bit & 0x0f00) >> 8) |
+                    (($numeric24bit & 0x0f00) >> 4),
+                    (($numeric24bit & 0x00f0) >> 4) |
+                    (($numeric24bit & 0x00f0)),
+                    (($numeric24bit & 0x000f) << 4) |
+                    (($numeric24bit & 0x000f))
+                    );
+                return true;
+                
+            case 6:
+                $result = self::fromRgb(
+                    0xff & ($numeric24bit >> 16),
+                    0xff & ($numeric24bit >> 8),
+                    0xff & ($numeric24bit)
+                    );
+                return true;
+                
+            case 8:
+                $result = self::fromRgb(
+                    0xff & ($numeric24bit >> 16),
+                    0xff & ($numeric24bit >> 8),
+                    0xff & ($numeric24bit),
+                    0xff & ($alpha8bit)
+                    );
+                return true;
+        }
+        
+        return false;
+    }
+    
+    /**
+     * Looks up a named color to a Color instance.
+     * 
+     * @param string $input Input string.
+     * 
+     * @return \Jdenticon\Color
+     */
+    private static function parseNamedColor($input) 
+    {
+        // Source: https://www.w3.org/TR/css-color-4/#named-colors
+        switch ($input) {
+            case 'aliceblue': return self::fromRgb(240,248,255);
+            case 'antiquewhite': return self::fromRgb(250,235,215);
+            case 'aqua': return self::fromRgb(0,255,255);
+            case 'aquamarine': return self::fromRgb(127,255,212);
+            case 'azure': return self::fromRgb(240,255,255);
+            case 'beige': return self::fromRgb(245,245,220);
+            case 'bisque': return self::fromRgb(255,228,196);
+            case 'black': return self::fromRgb(0,0,0);
+            case 'blanchedalmond': return self::fromRgb(255,235,205);
+            case 'blue': return self::fromRgb(0,0,255);
+            case 'blueviolet': return self::fromRgb(138,43,226);
+            case 'brown': return self::fromRgb(165,42,42);
+            case 'burlywood': return self::fromRgb(222,184,135);
+            case 'cadetblue': return self::fromRgb(95,158,160);
+            case 'chartreuse': return self::fromRgb(127,255,0);
+            case 'chocolate': return self::fromRgb(210,105,30);
+            case 'coral': return self::fromRgb(255,127,80);
+            case 'cornflowerblue': return self::fromRgb(100,149,237);
+            case 'cornsilk': return self::fromRgb(255,248,220);
+            case 'crimson': return self::fromRgb(220,20,60);
+            case 'cyan': return self::fromRgb(0,255,255);
+            case 'darkblue': return self::fromRgb(0,0,139);
+            case 'darkcyan': return self::fromRgb(0,139,139);
+            case 'darkgoldenrod': return self::fromRgb(184,134,11);
+            case 'darkgray': return self::fromRgb(169,169,169);
+            case 'darkgreen': return self::fromRgb(0,100,0);
+            case 'darkgrey': return self::fromRgb(169,169,169);
+            case 'darkkhaki': return self::fromRgb(189,183,107);
+            case 'darkmagenta': return self::fromRgb(139,0,139);
+            case 'darkolivegreen': return self::fromRgb(85,107,47);
+            case 'darkorange': return self::fromRgb(255,140,0);
+            case 'darkorchid': return self::fromRgb(153,50,204);
+            case 'darkred': return self::fromRgb(139,0,0);
+            case 'darksalmon': return self::fromRgb(233,150,122);
+            case 'darkseagreen': return self::fromRgb(143,188,143);
+            case 'darkslateblue': return self::fromRgb(72,61,139);
+            case 'darkslategray': return self::fromRgb(47,79,79);
+            case 'darkslategrey': return self::fromRgb(47,79,79);
+            case 'darkturquoise': return self::fromRgb(0,206,209);
+            case 'darkviolet': return self::fromRgb(148,0,211);
+            case 'deeppink': return self::fromRgb(255,20,147);
+            case 'deepskyblue': return self::fromRgb(0,191,255);
+            case 'dimgray': return self::fromRgb(105,105,105);
+            case 'dimgrey': return self::fromRgb(105,105,105);
+            case 'dodgerblue': return self::fromRgb(30,144,255);
+            case 'firebrick': return self::fromRgb(178,34,34);
+            case 'floralwhite': return self::fromRgb(255,250,240);
+            case 'forestgreen': return self::fromRgb(34,139,34);
+            case 'fuchsia': return self::fromRgb(255,0,255);
+            case 'gainsboro': return self::fromRgb(220,220,220);
+            case 'ghostwhite': return self::fromRgb(248,248,255);
+            case 'gold': return self::fromRgb(255,215,0);
+            case 'goldenrod': return self::fromRgb(218,165,32);
+            case 'gray': return self::fromRgb(128,128,128);
+            case 'green': return self::fromRgb(0,128,0);
+            case 'greenyellow': return self::fromRgb(173,255,47);
+            case 'grey': return self::fromRgb(128,128,128);
+            case 'honeydew': return self::fromRgb(240,255,240);
+            case 'hotpink': return self::fromRgb(255,105,180);
+            case 'indianred': return self::fromRgb(205,92,92);
+            case 'indigo': return self::fromRgb(75,0,130);
+            case 'ivory': return self::fromRgb(255,255,240);
+            case 'khaki': return self::fromRgb(240,230,140);
+            case 'lavender': return self::fromRgb(230,230,250);
+            case 'lavenderblush': return self::fromRgb(255,240,245);
+            case 'lawngreen': return self::fromRgb(124,252,0);
+            case 'lemonchiffon': return self::fromRgb(255,250,205);
+            case 'lightblue': return self::fromRgb(173,216,230);
+            case 'lightcoral': return self::fromRgb(240,128,128);
+            case 'lightcyan': return self::fromRgb(224,255,255);
+            case 'lightgoldenrodyellow': return self::fromRgb(250,250,210);
+            case 'lightgray': return self::fromRgb(211,211,211);
+            case 'lightgreen': return self::fromRgb(144,238,144);
+            case 'lightgrey': return self::fromRgb(211,211,211);
+            case 'lightpink': return self::fromRgb(255,182,193);
+            case 'lightsalmon': return self::fromRgb(255,160,122);
+            case 'lightseagreen': return self::fromRgb(32,178,170);
+            case 'lightskyblue': return self::fromRgb(135,206,250);
+            case 'lightslategray': return self::fromRgb(119,136,153);
+            case 'lightslategrey': return self::fromRgb(119,136,153);
+            case 'lightsteelblue': return self::fromRgb(176,196,222);
+            case 'lightyellow': return self::fromRgb(255,255,224);
+            case 'lime': return self::fromRgb(0,255,0);
+            case 'limegreen': return self::fromRgb(50,205,50);
+            case 'linen': return self::fromRgb(250,240,230);
+            case 'magenta': return self::fromRgb(255,0,255);
+            case 'maroon': return self::fromRgb(128,0,0);
+            case 'mediumaquamarine': return self::fromRgb(102,205,170);
+            case 'mediumblue': return self::fromRgb(0,0,205);
+            case 'mediumorchid': return self::fromRgb(186,85,211);
+            case 'mediumpurple': return self::fromRgb(147,112,219);
+            case 'mediumseagreen': return self::fromRgb(60,179,113);
+            case 'mediumslateblue': return self::fromRgb(123,104,238);
+            case 'mediumspringgreen': return self::fromRgb(0,250,154);
+            case 'mediumturquoise': return self::fromRgb(72,209,204);
+            case 'mediumvioletred': return self::fromRgb(199,21,133);
+            case 'midnightblue': return self::fromRgb(25,25,112);
+            case 'mintcream': return self::fromRgb(245,255,250);
+            case 'mistyrose': return self::fromRgb(255,228,225);
+            case 'moccasin': return self::fromRgb(255,228,181);
+            case 'navajowhite': return self::fromRgb(255,222,173);
+            case 'navy': return self::fromRgb(0,0,128);
+            case 'oldlace': return self::fromRgb(253,245,230);
+            case 'olive': return self::fromRgb(128,128,0);
+            case 'olivedrab': return self::fromRgb(107,142,35);
+            case 'orange': return self::fromRgb(255,165,0);
+            case 'orangered': return self::fromRgb(255,69,0);
+            case 'orchid': return self::fromRgb(218,112,214);
+            case 'palegoldenrod': return self::fromRgb(238,232,170);
+            case 'palegreen': return self::fromRgb(152,251,152);
+            case 'paleturquoise': return self::fromRgb(175,238,238);
+            case 'palevioletred': return self::fromRgb(219,112,147);
+            case 'papayawhip': return self::fromRgb(255,239,213);
+            case 'peachpuff': return self::fromRgb(255,218,185);
+            case 'peru': return self::fromRgb(205,133,63);
+            case 'pink': return self::fromRgb(255,192,203);
+            case 'plum': return self::fromRgb(221,160,221);
+            case 'powderblue': return self::fromRgb(176,224,230);
+            case 'purple': return self::fromRgb(128,0,128);
+            case 'rebeccapurple': return self::fromRgb(102,51,153);
+            case 'red': return self::fromRgb(255,0,0);
+            case 'rosybrown': return self::fromRgb(188,143,143);
+            case 'royalblue': return self::fromRgb(65,105,225);
+            case 'saddlebrown': return self::fromRgb(139,69,19);
+            case 'salmon': return self::fromRgb(250,128,114);
+            case 'sandybrown': return self::fromRgb(244,164,96);
+            case 'seagreen': return self::fromRgb(46,139,87);
+            case 'seashell': return self::fromRgb(255,245,238);
+            case 'sienna': return self::fromRgb(160,82,45);
+            case 'silver': return self::fromRgb(192,192,192);
+            case 'skyblue': return self::fromRgb(135,206,235);
+            case 'slateblue': return self::fromRgb(106,90,205);
+            case 'slategray': return self::fromRgb(112,128,144);
+            case 'slategrey': return self::fromRgb(112,128,144);
+            case 'snow': return self::fromRgb(255,250,250);
+            case 'springgreen': return self::fromRgb(0,255,127);
+            case 'steelblue': return self::fromRgb(70,130,180);
+            case 'tan': return self::fromRgb(210,180,140);
+            case 'teal': return self::fromRgb(0,128,128);
+            case 'thistle': return self::fromRgb(216,191,216);
+            case 'tomato': return self::fromRgb(255,99,71);
+            case 'transparent': return self::fromRgb(0,0,0,0);
+            case 'turquoise': return self::fromRgb(64,224,208);
+            case 'violet': return self::fromRgb(238,130,238);
+            case 'wheat': return self::fromRgb(245,222,179);
+            case 'white': return self::fromRgb(255,255,255);
+            case 'whitesmoke': return self::fromRgb(245,245,245);
+            case 'yellow': return self::fromRgb(255,255,0);
+            case 'yellowgreen': return self::fromRgb(154,205,50);
+            default: return null;
+        }
+    }
+}

+ 492 - 0
vendor/jdenticon/jdenticon/src/Identicon.php

@@ -0,0 +1,492 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon;
+
+use Jdenticon\IdenticonStyle;
+use Jdenticon\Rendering\Rectangle;
+use Jdenticon\Rendering\RendererInterface;
+use Jdenticon\Rendering\IconGenerator;
+use Jdenticon\Rendering\InternalPngRenderer;
+use Jdenticon\Rendering\ImagickRenderer;
+use Jdenticon\Rendering\SvgRenderer;
+
+/**
+ * Represents an identicon and its style. This is the entry class to Jdenticon.
+ */
+class Identicon
+{
+    /**
+     * @var mixed
+     */
+    private $value;
+    
+    /**
+     * @var boolean
+     */
+    private $valueSet = false;
+    
+    /**
+     * Defaults to hash of an empty string.
+     * 
+     * @var string
+     */
+    private $hash = 'da39a3ee5e6b4b0d3255bfef95601890afd80709';
+    
+    /**
+     * @var integer
+     */
+    private $size = 100;
+    
+    /**
+     * @var Jdenticon\Rendering\IconGenerator
+     */
+    private $iconGenerator;
+    
+    /**
+     * @var Jdenticon\IdenticonStyle
+     */
+    private $style;
+
+    /**
+     * @var bool
+     */
+    private $enableImageMagick;
+
+    /**
+     * Creates an Identicon instance with the specified hash.
+     *
+     * @param string $hash A binary string containing the hash that will be used 
+     *      as base for this icon. The hash must contain at least 6 bytes.
+     * @param int|float|double $size The size of the icon in pixels (the icon 
+     *      is quadratic).
+     */
+    public function __construct($options = null)
+    {
+        $this->iconGenerator = IconGenerator::getDefaultGenerator();
+        
+        if ($options !== null) {
+            $this->setOptions($options);
+        }
+
+        if ($this->style === null) {
+            $this->style = new IdenticonStyle();
+        }
+    }
+    
+    /**
+     * Creates an Identicon instance from a specified hash.
+     *
+     * @param string $hash A binary string containing the hash that will be used 
+     *      as base for this icon. The hash must contain at least 6 bytes.
+     * @param int $size The size of the icon in pixels (the icon is quadratic).
+     * @return \Jdenticon\Identicon
+     */
+    public static function fromHash($hash, $size)
+    {
+        return new Identicon(array('hash' => $hash, 'size' => $size));
+    }
+
+    /**
+     * Creates an Identicon instance from a specified value.
+     *
+     * @param mixed $value The value that will be used as base for this icon. 
+     *      The value will be converted to a UTF8 encoded string and then hashed 
+     *      using SHA1.
+     * @param int $size The size of the icon in pixels (the icon is quadratic).
+     * @return \Jdenticon\Identicon
+     */
+    public static function fromValue($value, $size)
+    {
+        return new Identicon(array('value' => $value, 'size' => $size));
+    }
+    
+    /**
+     * Gets an associative array of all options of this identicon.
+     *
+     * @return array
+     */
+    public function getOptions() 
+    {
+        $options = array();
+        
+        if ($this->valueSet) {
+            $options['value'] = $this->getValue();
+        } elseif ($this->hash !== null) {
+            $options['hash'] = $this->getHash();
+        }
+        
+        $options['size'] = $this->getSize();
+        $options['style'] = $this->getStyle()->getOptions();
+
+        if ($this->enableImageMagick !== null) {
+            $options['enableImageMagick'] = $this->getEnableImageMagick();
+        }
+        
+        if ($this->iconGenerator !== IconGenerator::getDefaultGenerator()) {
+            $options['iconGenerator'] = $this->getIconGenerator();
+        }
+        
+        return $options;
+    }
+    
+    /**
+     * Sets options in this identicon by specifying an associative array of 
+     * option values.
+     *
+     * @param array $options Options to set.
+     * @return self
+     */
+    public function setOptions(array $options)
+    {
+        foreach ($options as $key => $value) {
+            $this->__set($key, $value);
+        }
+        return $this;
+    }
+    
+    public function __get($name)
+    {
+        switch (strtolower($name)) {
+            case 'size':
+                return $this->getSize();
+            case 'hash':
+                return $this->getHash();
+            case 'value':
+                return $this->getValue();
+            case 'style':
+                return $this->getStyle();
+            case 'icongenerator':
+                return $this->getIconGenerator();
+            case 'enableimagemagick':
+                return $this->getEnableImageMagick();
+            default:
+                throw new \InvalidArgumentException(
+                    "Unknown Identicon option '$name'.");
+        }
+    }
+    
+    public function __set($name, $value)
+    {
+        switch (strtolower($name)) {
+            case 'size':
+                $this->setSize($value);
+                break;
+            case 'hash':
+                $this->setHash($value);
+                break;
+            case 'value':
+                $this->setValue($value);
+                break;
+            case 'style':
+                $this->setStyle($value);
+                break;
+            case 'icongenerator':
+                $this->setIconGenerator($value);
+                break;
+            case 'enableimagemagick':
+                $this->setEnableImageMagick($value);
+                break;
+            default:
+                throw new \InvalidArgumentException(
+                    "Unknown Identicon option '$name'.");
+        }
+    }
+    
+    /** 
+     * Gets the size of the icon in pixels.
+     */
+    public function getSize()
+    {
+        return $this->size;
+    }
+    
+    /**
+     * Sets the size of this icon in pixels.
+     *
+     * @param int|float|double $size The width and height of the icon.
+     */
+    public function setSize($size)
+    {
+        if (!is_numeric($size) || $size < 1) {
+            throw new \InvalidArgumentException(
+                "An invalid identicon size was specified. ".
+                "A numeric value >= 1 was expected. Specified value: $size.");
+        }
+        
+        $this->size = (int)$size;
+    }
+
+    /** 
+     * Gets the size of the icon in pixels.
+     */
+    public function getEnableImageMagick()
+    {
+        // Enable ImageMagick on PHP < 7. On PHP 7 the performance increase
+        // is not as obvious as on PHP 5. Since the ImageMagick renderer has a 
+        // lot of quirks, we don't want to use it unless really needed.
+        if ($this->enableImageMagick === null) {
+            return PHP_MAJOR_VERSION < 7 && extension_loaded('imagick');
+        }
+        
+        return $this->enableImageMagick;
+    }
+    
+    /**
+     * Sets whether ImageMagick should be used to generate PNG icons.
+     *
+     * @param bool $enable true to enable ImageMagick.
+     */
+    public function setEnableImageMagick($enable)
+    {
+        if (!is_bool($enable)) {
+            throw new \InvalidArgumentException(
+                "enableImageMagick can only assume boolean values. Specified value: $enable.");
+        }
+
+        // Verify that the Imagick extension is installed
+        if ($enable && !extension_loaded('imagick')) {
+            throw new \Exception(
+                'Failed to enable ImageMagick. '.
+                'The Imagick PHP extension was not found on this system.');
+        }
+        
+        $this->enableImageMagick = $enable;
+    }
+    
+    /**
+     * Gets the {@see IconGenerator} used to generate icons.
+     *
+     * @return \Jdenticon\Rendering\IconGenerator
+     */
+    public function getIconGenerator()
+    {
+        return $this->iconGenerator;
+    }
+    
+    /**
+     * Sets the {@see IconGenerator} used to generate icons.
+     *
+     * @param \Jdenticon\Rendering\IconGenerator $iconGenerator Icon generator 
+     *      that will render the shapes of the identicon.
+     * @return \Jdenticon\Identicon
+     */
+    public function setIconGenerator(IconGenerator $iconGenerator)
+    {
+        if ($iconGenerator === null) {
+            $iconGenerator = IconGenerator::getDefaultGenerator();
+        }
+        $this->iconGenerator = $iconGenerator;
+        return $this;
+    }
+    
+    /**
+     * Gets or sets the style of the icon.
+     *
+     * @return \Jdenticon\IdenticonStyle
+     */
+    public function getStyle()
+    {
+        return $this->style;
+    }
+    
+    /**
+     * Gets or sets the style of the icon.
+     *
+     * @param array|\Jdenticon\IdenticonStyle $style The new style of the icon. 
+     *      NULL will revert the identicon to use the default style.
+     * @return self
+     */
+    public function setStyle($style)
+    {
+        if ($style == null) {
+            $this->style = new IdenticonStyle();
+        } elseif ($style instanceof IdenticonStyle) {
+            $this->style = $style;
+        } elseif (is_array($style)) {
+            $this->style = new IdenticonStyle($style);
+        } else {
+            throw new \InvalidArgumentException(
+                "Invalid indenticon style was specified. ".
+                "Allowed values are IdenticonStyle instances and associative ".
+                "arrays containing IdenticonStyle options.");
+        }
+        
+        return $this;
+    }
+
+    /**
+     * Gets a binary string containing the hash that is used as base for this 
+     * icon.
+     */
+    public function getHash()
+    {
+        return $this->hash;
+    }
+    
+    /**
+     * Sets a binary string containing the hash that is used as base for this 
+     * icon. The string should contain at least 6 bytes.
+     *
+     * @param string $hash Binary string containing the hash.
+     */
+    public function setHash($hash)
+    {
+        if (!is_string($hash)) {
+            throw new \InvalidArgumentException(
+                'An invalid $hash was passed to Identicon. ' .
+                'A binary string was expected.');
+        }
+        if (strlen($hash) < 6) {
+            throw new \InvalidArgumentException(
+                'An invalid $hash was passed to Identicon. ' . 
+                'The hash was expected to contain at least 6 bytes.');
+        }
+        
+        $this->hash = $hash;
+        $this->value = null;
+        $this->valueSet = false;
+        return $this;
+    }
+    
+    /**
+     * Gets a binary string containing the hash that is used as base for this 
+     * icon.
+     */
+    public function getValue()
+    {
+        return $this->value;
+    }
+    
+    /**
+     * Sets a value that will be used as base for this icon. The value will
+     * be converted to a string and then hashed using SHA1.
+     *
+     * @param mixed $value Value that will be hashed.
+     */
+    public function setValue($value)
+    {
+        $this->hash = sha1("$value");
+        $this->value = $value;
+        $this->valueSet = true;
+        return $this;
+    }
+    
+    /**
+     * Gets the bounds of the icon excluding its padding.
+     *
+     * @return \Jdenticon\Rendering\Rectangle
+     */
+    public function getIconBounds() 
+    {
+        // Round padding to nearest integer
+        $padding = (int)($this->style->getPadding() * $this->size + 0.5);
+        
+        return new Rectangle(
+            $padding, $padding,
+            $this->size - $padding * 2,
+            $this->size - $padding * 2);
+    }
+    
+    private function getRenderer($imageFormat) 
+    {
+        switch (strtolower($imageFormat)) {
+            case 'svg':
+                return new SvgRenderer($this->size, $this->size);
+
+            default:
+                return $this->getEnableImageMagick() ?
+                    new ImagickRenderer($this->size, $this->size) :
+                    new InternalPngRenderer($this->size, $this->size);
+        }
+    }
+    
+    /**
+     * Draws this icon using a specified renderer.
+     *
+     * This method is only intended for usage with custom renderers. A custom 
+     * renderer could as an example render an Identicon in a file format not 
+     * natively supported by Jdenticon. To implement a new file format, 
+     * implement {@see \Jdenticon\Rendering\RendererInterface}.
+     *
+     * @param \Jdenticon\Rendering\RendererInterface $renderer The renderer used 
+     *      to render this icon.
+     * @param \Jdenticon\Rendering\Rectangle $rect The bounds of the rendered 
+     *      icon. No padding will be applied to the rectangle. If the parameter
+     *      is omitted, the rectangle is calculated from the current icon
+     *      size and padding.
+     */
+    public function draw(
+        \Jdenticon\Rendering\RendererInterface $renderer, 
+        \Jdenticon\Rendering\Rectangle $rect = null)
+    {
+        if ($rect === null) {
+            $rect = $this->getIconBounds();
+        }
+        $this->iconGenerator->generate(
+            $renderer, $rect, $this->style, $this->hash);
+    }
+
+    /**
+     * Renders the icon directly to the page output.
+     *
+     * The method will set the 'Content-Type' HTTP header. You are recommended 
+     * to set an appropriate 'Cache-Control' header before calling this method 
+     * to ensure the icon is  cached client side.
+     *
+     * @param string $imageFormat The image format of the output. 
+     *      Supported values are 'png' and 'svg'.
+     */
+    public function displayImage($imageFormat = 'png')
+    {
+        $renderer = $this->getRenderer($imageFormat);
+        $this->draw($renderer, $this->getIconBounds());
+        $mimeType = $renderer->getMimeType();
+        $data = $renderer->getData();
+        header("Content-Type: $mimeType");
+        echo $data;
+    }
+    
+    /**
+     * Renders the icon to a binary string.
+     *
+     * @param string $imageFormat The image format of the output string. 
+     *      Supported values are 'png' and 'svg'.
+     * @return string
+     */
+    public function getImageData($imageFormat = 'png')
+    {
+        $renderer = $this->getRenderer($imageFormat);
+        $this->draw($renderer, $this->getIconBounds());
+        return $renderer->getData();
+    }
+    
+    /**
+     * Renders the icon as a data URI. It is recommended to avoid using this 
+     * method unless really necessary, since it will effectively disable client 
+     * caching of generated icons, and will also cause the same icon to be 
+     * rendered multiple times, when used multiple times on a single page.
+     *
+     * @param string $imageFormat The image format of the data URI. 
+     *      Supported values are 'png' and 'svg'.
+     * @return string
+     */
+    public function getImageDataUri($imageFormat = 'png')
+    {
+        $renderer = $this->getRenderer($imageFormat);
+        $this->draw($renderer, $this->getIconBounds());
+        $mimeType = $renderer->getMimeType();
+        $base64 = base64_encode($renderer->getData());
+        return "data:$mimeType;base64,$base64";
+    }
+}
+
+

+ 460 - 0
vendor/jdenticon/jdenticon/src/IdenticonStyle.php

@@ -0,0 +1,460 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon;
+
+use Jdenticon\Color;
+
+/**
+ * Specifies the color style of an identicon.
+ */
+class IdenticonStyle
+{
+    /** 
+     * @var \Jdenticon\Color
+     */
+    private $backgroundColor;
+    
+    /** 
+     * @var float
+     */
+    private $padding;
+    
+    /** 
+     * @var float
+     */
+    private $colorSaturation;
+    
+    /** 
+     * @var float
+     */
+    private $grayscaleSaturation;
+    
+    /** 
+     * @var array(float)
+     */
+    private $colorLightness;
+    
+    /** 
+     * @var array(float)
+     */
+    private $grayscaleLightness;
+
+    /** 
+     * @var array(integer)
+     */
+    private $hues;
+    
+    public function __construct(array $options = null)
+    {
+        $this->backgroundColor = self::getDefaultBackgroundColor();
+        $this->padding = self::getDefaultPadding();
+        $this->colorSaturation = self::getDefaultColorSaturation();
+        $this->grayscaleSaturation = self::getDefaultGrayscaleSaturation();
+        $this->colorLightness = self::getDefaultColorLightness();
+        $this->grayscaleLightness = self::getDefaultGrayscaleLightness();
+        
+        if ($options !== null) {
+            $this->setOptions($options);
+        }
+    }
+    
+    /**
+     * Gets an associative array of all options of this style.
+     *
+     * @return array
+     */
+    public function getOptions()
+    {
+        $options = array();
+
+        $options['backgroundColor'] = $this->getBackgroundColor()->__toString();
+        $options['padding'] = $this->getPadding();
+        $options['colorSaturation'] = $this->getColorSaturation();
+        $options['grayscaleSaturation'] = $this->getGrayscaleSaturation();
+        $options['colorLightness'] = $this->getColorLightness();
+        $options['grayscaleLightness'] = $this->getGrayscaleLightness();
+
+        if ($this->hues !== null) {
+            $options['hues'] = $this->getHues();
+        }
+
+        return $options;
+    }
+    
+    /**
+     * Sets options in this style by specifying an associative array of option
+     * values.
+     *
+     * @param array $options Options to set.
+     * @return self
+     */
+    public function setOptions(array $options)
+    {
+        foreach ($options as $key => $value) {
+            $this->__set($key, $value);
+        }
+        return $this;
+    }
+    
+    public function __get($name)
+    {
+        switch (strtolower($name)) {
+            case 'backgroundcolor':
+                return $this->getBackgroundColor();
+            case 'padding':
+                return $this->getPadding();
+            case 'colorsaturation':
+                return $this->getColorSaturation();
+            case 'grayscalesaturation':
+                return $this->getGrayscaleSaturation();
+            case 'colorlightness':
+                return $this->getColorLightness();
+            case 'grayscalelightness':
+                return $this->getGrayscaleLightness();
+            case 'hues':
+                return $this->getHues();
+            default:
+                throw new \InvalidArgumentException(
+                    "Unknown IdenticonStyle option '$name'.");
+        }
+    }
+    
+    public function __set($name, $value)
+    {
+        switch (strtolower($name)) {
+            case 'backgroundcolor':
+                $this->setBackgroundColor($value);
+                break;
+            case 'padding':
+                $this->setPadding($value);
+                break;
+            case 'colorsaturation':
+                $this->setColorSaturation($value);
+                break;
+            case 'grayscalesaturation':
+                $this->setGrayscaleSaturation($value);
+                break;
+            case 'colorlightness':
+                $this->setColorLightness($value);
+                break;
+            case 'grayscalelightness':
+                $this->setGrayscaleLightness($value);
+                break;
+            case 'hues':
+                $this->setHues($value);
+                break;
+            default:
+                throw new \InvalidArgumentException(
+                    "Unknown IdenticonStyle option '$name'.");
+        }
+    }
+    
+    /**
+     * Normalizes a hue to the first turn [0, 360).
+     * 
+     * @param mixed $hue
+     * @return integer
+     */
+    private static function normalizeHue($hue) 
+    {
+        if (!is_numeric($hue)) {
+            throw new \InvalidArgumentException(
+                "'$hue' is not a valid hue.");
+        }
+
+        $hue = $hue % 360;
+        if ($hue < 0) {
+            $hue += 360;
+        }
+
+        return $hue;
+    }
+    
+    /**
+     * Gets an array of allowed hues, or null if there are no restrictions.
+     *
+     * @return array(int)|null
+     */
+    public function getHues()
+    {
+        return $this->hues;
+    }
+
+    /**
+     * Sets the allowed hues of generated icons.
+     *
+     * @param array(integer)|integer|null $value A hue specified in degrees,
+     *      or an array of hues specified in degrees. If set to null, the hue
+     *      list is cleared.
+     * @return self
+     */
+    public function setHues($value)
+    {
+        $hues = array();
+
+        if ($value !== null) {
+            if (is_array($value)) {
+                foreach ($value as $hue) {
+                    $hues[] = self::normalizeHue($hue);
+                }
+            } else {
+                $hues[] = self::normalizeHue($value);
+            }
+        }
+
+        $this->hues = empty($hues) ? null : $hues;
+        return $this;
+    }
+    
+    /**
+     * Gets the padding of an icon in percents in the range [0.0, 0.4].
+     *
+     * @return float
+     */
+    public function getPadding()
+    {
+        return $this->padding;
+    }
+    
+    /**
+     * Sets the padding of an icon in percents.
+     *
+     * @param float $value New padding in the range [0.0, 0.4].
+     * @return self
+     */
+    public function setPadding($value)
+    {
+        if (!is_numeric($value) || $value < 0 || $value > 0.4) {
+            throw new \InvalidArgumentException(
+                "Padding '$value' out of range. ".
+                "Values in the range [0.0, 0.4] are allowed.");
+        }
+        $this->padding = (float)$value;
+        return $this;
+    }
+    
+    /**
+     * Gets the color of the identicon background.
+     *
+     * @return \Jdenticon\Color
+     */
+    public function getBackgroundColor()
+    {
+        return $this->backgroundColor;
+    }
+    
+    /**
+     * Sets the color of the identicon background.
+     *
+     * @param \Jdenticon\Color|string $value New background color.
+     * @return \Jdenticon\IdenticonStyle
+     */
+    public function setBackgroundColor($value)
+    {
+        if ($value instanceof Color) {
+            $this->backgroundColor = $value;
+        } else {
+            $this->backgroundColor = Color::parse($value);
+        }
+        
+        return $this;
+    }
+    
+    /**
+     * Gets the saturation of the originally grayscale identicon shapes.
+     *
+     * @return float  Saturation in the range [0.0, 1.0].
+     */
+    public function getGrayscaleSaturation()
+    {
+        return $this->grayscaleSaturation;
+    }
+    
+    /**
+     * Sets the saturation of the originally grayscale identicon shapes.
+     *
+     * @param $value float  Saturation in the range [0.0, 1.0].
+     * @return self
+     */
+    public function setGrayscaleSaturation($value)
+    {
+        if (!is_numeric($value) ||
+            $value < 0 || $value > 1
+        ) {
+            throw new \InvalidArgumentException(
+                "The grayscale saturation was invalid. ".
+                "Only values in the range [0.0, 1.0] are allowed.");
+        }
+        $this->grayscaleSaturation = (float)$value;
+        return $this;
+    }
+    
+    /**
+     * Gets the saturation of the colored identicon shapes.
+     *
+     * @return float Saturation in the range [0.0, 1.0].
+     */
+    public function getColorSaturation()
+    {
+        return $this->colorSaturation;
+    }
+    
+    /**
+     * Sets the saturation of the colored identicon shapes.
+     *
+     * @param $value float  Saturation in the range [0.0, 1.0].
+     * @return self
+     */
+    public function setColorSaturation($value)
+    {
+        if (!is_numeric($value) ||
+            $value < 0 || $value > 1
+        ) {
+            throw new \InvalidArgumentException(
+                "The color saturation was invalid. ".
+                "Only values in the range [0.0, 1.0] are allowed.");
+        }
+        $this->colorSaturation = (float)$value;
+        return $this;
+    }
+    
+    /**
+     * Gets the value of the ColorLightness property.
+     *
+     * @return array(float, float)
+     */
+    public function getColorLightness()
+    {
+        return $this->colorLightness;
+    }
+    
+    /**
+     * Sets the value of the ColorLightness property.
+     *
+     * @param $value array(float, float)  Lightness range.
+     * @return self
+     */
+    public function setColorLightness($value)
+    {
+        if (!is_array($value) ||
+            !array_key_exists(0, $value) ||
+            !array_key_exists(1, $value) ||
+            !is_numeric($value[0]) ||
+            !is_numeric($value[1]) ||
+            $value[0] < 0 || $value[0] > 1 ||
+            $value[1] < 0 || $value[1] > 1
+        ) {
+            throw new \InvalidArgumentException(
+                "The value passed to setColorLightness was invalid. ".
+                "Please check the documentation.");
+        }
+        
+        $this->colorLightness = array((float)$value[0], (float)$value[1]);
+        return $this;
+    }
+    
+    /**
+     * Gets the value of the GrayscaleLightness property.
+     *
+     * @return array(float, float)
+     */
+    public function getGrayscaleLightness()
+    {
+        return $this->grayscaleLightness;
+    }
+    
+    /**
+     * Sets the value of the GrayscaleLightness property.
+     *
+     * @param $value array(float, float)  Lightness range.
+     * @return self
+     */
+    public function setGrayscaleLightness($value)
+    {
+        if (!is_array($value) ||
+            !array_key_exists(0, $value) ||
+            !array_key_exists(1, $value) ||
+            !is_numeric($value[0]) ||
+            !is_numeric($value[1]) ||
+            $value[0] < 0 || $value[0] > 1 ||
+            $value[1] < 0 || $value[1] > 1
+        ) {
+            throw new \InvalidArgumentException(
+                "The value passed to setGrayscaleLightness was invalid. ".
+                "Please check the documentation.");
+        }
+        $this->grayscaleLightness = array((float)$value[0], (float)$value[1]);
+        return $this;
+    }
+    
+    
+    
+    /**
+     * Gets the default value of the BackgroundColor property. Resolves to transparent.
+     *
+     * @return \Jdenticon\Color
+     */
+    public static function getDefaultBackgroundColor()
+    {
+        return Color::fromRgb(255, 255, 255, 255);
+    }
+    
+    /**
+     * Gets the default value of the Padding property. Resolves to 0.08.
+     *
+     * @return float
+     */
+    public static function getDefaultPadding()
+    {
+        return 0.08;
+    }
+    
+    /**
+     * Gets the default value of the ColorSaturation property. Resolves to 0.5.
+     *
+     * @return float
+     */
+    public static function getDefaultColorSaturation()
+    {
+        return 0.5;
+    }
+    
+    /**
+     * Gets the default value of the GrayscaleSaturation property. Resolves to 0.
+     *
+     * @return float
+     */
+    public static function getDefaultGrayscaleSaturation()
+    {
+        return 0;
+    }
+    
+    /**
+     * Gets the default value of the ColorLightness property. Resolves to [0.4, 0.8].
+     *
+     * @return array
+     */
+    public static function getDefaultColorLightness()
+    {
+        return array(0.4, 0.8);
+    }
+    
+    /**
+     * Gets the default value of the GrayscaleLightness property. Resolves to [0.3, 0.9].
+     *
+     * @return array
+     */
+    public static function getDefaultGrayscaleLightness()
+    {
+        return array(0.3, 0.9);
+    }
+}

+ 209 - 0
vendor/jdenticon/jdenticon/src/Rendering/AbstractRenderer.php

@@ -0,0 +1,209 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Rendering;
+
+/**
+ * Base class for rendering shapes in an identicon. Implement this class to e.g. 
+ * support a new file format that is not natively supported by Jdenticon. To 
+ * invoke the new Renderer, pass the renderer as an argument to the 
+ * {@see \Jdenticon\Identicon::Draw} method.
+ */
+abstract class AbstractRenderer implements RendererInterface
+{
+    private $transform;
+    protected $backgroundColor;
+    
+    public function __construct()
+    {
+        $this->transform = Transform::getEmpty();
+    }
+    
+    /**
+     * Sets the current transform that will be applied on all coordinates before 
+     * being rendered to the target image.
+     *
+     * @param  \Jdenticon\Rendering\Transform $transform  The transform to set. 
+     *      If NULL is specified any existing transform is removed.
+     */
+    public function setTransform(\Jdenticon\Rendering\Transform $transform) 
+    {
+        $this->transform = $transform === null ? 
+            Transform::getEmpty() : $transform;
+    }
+    
+    /**
+     * Gets the current transform that will be applied on all coordinates before 
+     * being rendered to the target image.
+     *
+     * @return \Jdenticon\Rendering\Transform
+     */
+    public function getTransform() 
+    {
+        return $this->transform;
+    }
+    
+    /**
+     * Adds a polygon without translating its coordinates.
+     *
+     * @param  array $points An array of the points that the polygon consists of.
+     */
+    abstract protected function addPolygonNoTransform($points);
+
+    /**
+     * Adds a circle without translating its coordinates.
+     *
+     * @param float $x The x-coordinate of the bounding rectangle 
+     *      upper-left corner.
+     * @param float $y The y-coordinate of the bounding rectangle 
+     *      upper-left corner.
+     * @param float $size The size of the bounding rectangle.
+     * @param bool $counterClockwise If true the circle will be drawn 
+     *      counter clockwise.
+     */
+    abstract protected function addCircleNoTransform($x, $y, $size, $counterClockwise);
+
+    /**
+     * Sets the background color of the image.
+     *
+     * @param \Jdenticon\Color $color  The image background color.
+     */
+    public function setBackgroundColor(\Jdenticon\Color $color)
+    {
+        $this->backgroundColor = $color;
+    }
+    
+    /**
+     * Gets the background color of the image.
+     *
+     * @return \Jdenticon\Color
+     */
+    public function getBackgroundColor()
+    {
+        return $this->backgroundColor;
+    }
+    
+    private function addPolygonCore(array $points, $invert)
+    {
+        $transformedPoints = array();
+        foreach ($points as $point) {
+            $transformedPoints[] = 
+                $this->transform->transformPoint($point->x, $point->y);
+        }
+        
+        if ($invert) {
+            $transformedPoints = array_reverse($transformedPoints);
+        }
+        
+        //var_dump($transformedPoints);
+        
+        $this->addPolygonNoTransform($transformedPoints);
+    }
+
+    /**
+     * Adds a rectangle to the image.
+     *
+     * @param float $x The x-coordinate of the rectangle upper-left corner.
+     * @param float $y The y-coordinate of the rectangle upper-left corner.
+     * @param float $width The width of the rectangle.
+     * @param float $height The height of the rectangle.
+     * @param bool $invert If true the area of the rectangle will be removed 
+     *      from the filled area.
+     */
+    public function addRectangle($x, $y, $width, $height, $invert = false)
+    {
+        $this->addPolygonCore(array(
+            new Point($x, $y),
+            new Point($x + $width, $y),
+            new Point($x + $width, $y + $height),
+            new Point($x, $y + $height),
+        ), $invert);
+    }
+
+    /**
+     * Adds a circle to the image.
+     *
+     * @param float $x The x-coordinate of the bounding rectangle 
+     *      upper-left corner.
+     * @param float $y The y-coordinate of the bounding rectangle 
+     *      upper-left corner.
+     * @param float $size The size of the bounding rectangle.
+     * @param bool $invert If true the area of the circle will be removed 
+     *      from the filled area.
+     */
+    public function addCircle($x, $y, $size, $invert = false)
+    {
+        $northWest = $this->transform->transformPoint($x, $y, $size, $size);
+        $this->addCircleNoTransform($northWest->x, $northWest->y, $size, $invert);
+    }
+
+    /**
+     * Adds a polygon to the image.
+     *
+     * @param array $points Array of points that the polygon consists of.
+     * @param bool $invert If true the area of the polygon will be removed 
+     *      from the filled area.
+     */
+    public function addPolygon($points, $invert = false)
+    {
+        $this->addPolygonCore($points, $invert);
+    }
+
+    /**
+     * Adds a triangle to the image.
+     *
+     * @param float $x The x-coordinate of the bounding rectangle 
+     *      upper-left corner.
+     * @param float $y The y-coordinate of the bounding rectangle 
+     *      upper-left corner.
+     * @param float $width The width of the bounding rectangle.
+     * @param float $height The height of the bounding rectangle.
+     * @param float $direction The direction of the 90 degree corner of the 
+     *      triangle.
+     * @param bool $invert If true the area of the triangle will be removed 
+     *      from the filled area.
+     */
+    public function addTriangle($x, $y, $width, $height, $direction, $invert = false)
+    {
+        $points = array(
+            new Point($x + $width, $y),
+            new Point($x + $width, $y + $height),
+            new Point($x, $y + $height),
+            new Point($x, $y)
+        );
+
+        array_splice($points, $direction, 1);
+        
+        $this->addPolygonCore($points, $invert);
+    }
+
+    /**
+     * Adds a rhombus to the image.
+     *
+     * @param float $x The x-coordinate of the bounding rectangle 
+     *      upper-left corner.
+     * @param float $y The y-coordinate of the bounding rectangle 
+     *      upper-left corner.
+     * @param float $width The width of the bounding rectangle.
+     * @param float $height The height of the bounding rectangle.
+     * @param bool $invert If true the area of the rhombus will be removed 
+     *      from the filled area.
+     */
+    public function addRhombus($x, $y, $width, $height, $invert = false)
+    {
+        $this->addPolygonCore(array(
+            new Point($x + $width / 2, $y),
+            new Point($x + $width, $y + $height / 2),
+            new Point($x + $width / 2, $y + $height),
+            new Point($x, $y + $height / 2),
+        ), $invert);
+    }
+}

+ 84 - 0
vendor/jdenticon/jdenticon/src/Rendering/ColorTheme.php

@@ -0,0 +1,84 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Rendering;
+
+use Jdenticon\Color;
+
+/**
+ * Specifies the colors to be used in an identicon.
+ */
+class ColorTheme
+{
+    private $darkGray;
+    private $midColor;
+    private $lightGray;
+    private $lightColor;
+    private $darkColor;
+    
+    /**
+     * Creates a new ColorTheme.
+     *
+     * @param float $hue The hue of the colored shapes in the range [0, 1].
+     * @param \Jdenticon\IdenticonStyle $style The style that specifies the 
+     *      lightness and saturation of the icon.
+     */
+    public function __construct($hue, \Jdenticon\IdenticonStyle $style)
+    {
+        $grayscaleLightness = $style->getGrayscaleLightness();
+        $colorLightness = $style->getColorLightness();
+        $hues = $style->getHues();
+
+        if ($hues !== null) {
+            // $hue is in the range [0, 1]
+            // Multiply with 0.999 to change the range to [0, 1)
+            $hueIndex = (int)($hue * 0.999 * count($hues));
+            $hue = (float)$hues[$hueIndex] / 360;
+        }
+        
+        $this->darkGray = Color::fromHslCompensated(
+            $hue, $style->getGrayscaleSaturation(), $grayscaleLightness[0]);
+        $this->midColor = Color::fromHslCompensated(
+            $hue, $style->getColorSaturation(), ($colorLightness[0] + $colorLightness[1]) / 2);
+        $this->lightGray = Color::fromHslCompensated(
+            $hue, $style->getGrayscaleSaturation(), $grayscaleLightness[1]);
+        $this->lightColor = Color::fromHslCompensated(
+            $hue, $style->getColorSaturation(), $colorLightness[1]);
+        $this->darkColor = Color::fromHslCompensated(
+            $hue, $style->getColorSaturation(), $colorLightness[0]);
+    }
+
+    /**
+     * Gets a color from this color theme by index.
+     *
+     * @param int $index Color index in the range [0, getCount()).
+     * @return Jdenticon\Color
+     */
+    public function getByIndex($index)
+    {
+        if ($index === 0) return $this->darkGray;
+        if ($index === 1) return $this->midColor;
+        if ($index === 2) return $this->lightGray;
+        if ($index === 3) return $this->lightColor;
+        if ($index === 4) return $this->darkColor;
+        return null;
+    }
+    
+    /**
+     * Gets the number of available colors in this theme.
+     *
+     * @return int
+     */
+    public function getCount() 
+    {
+        return 5;
+    }
+}

+ 290 - 0
vendor/jdenticon/jdenticon/src/Rendering/IconGenerator.php

@@ -0,0 +1,290 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Rendering;
+
+use Jdenticon\Shapes\Shape;
+use Jdenticon\Shapes\ShapeCategory;
+use Jdenticon\Shapes\ShapeDefinitions;
+
+/**
+ * Generates identicons and render them to a 
+ * {@link \Jdenticon\Rendering\RendererInterface}. This class dictates what 
+ * shapes will be used in the generated icons. If you intend to customize the 
+ * appearance of generated icons you probably wants to either subclass or modify 
+ * this class.
+ */
+class IconGenerator
+{
+    private $defaultShapes;
+    private static $instance;
+    
+    protected function __construct()
+    {
+        $this->defaultShapes = array(
+            // Sides
+            new ShapeCategory(
+                /*$colorIndex=*/ 8,
+                /*$shapes=*/ ShapeDefinitions::getOuterShapes(),
+                /*$shapeIndex=*/ 2,
+                /*$rotationIndex=*/ 3,
+                /*$positions=*/ array(1,0, 2,0, 2,3, 1,3, 0,1, 3,1, 3,2, 0,2)
+            ),
+            
+            // Corners
+            new ShapeCategory(
+                /*$colorIndex=*/ 9,
+                /*$shapes=*/ ShapeDefinitions::getOuterShapes(),
+                /*$shapeIndex=*/ 4,
+                /*$rotationIndex=*/ 5,
+                /*$positions=*/ array(0,0, 3,0, 3,3, 0,3)
+            ),
+            
+            // Center
+            new ShapeCategory(
+                /*$colorIndex=*/ 10,
+                /*$shapes=*/ ShapeDefinitions::getCenterShapes(),
+                /*$shapeIndex=*/ 1,
+                /*$rotationIndex=*/ null,
+                /*$positions=*/ array(1,1, 2,1, 2,2, 1,2)
+            )
+        );
+    }
+    
+    public static function getDefaultGenerator()
+    {
+        if (self::$instance === null) {
+            self::$instance = new IconGenerator();
+        }
+        return self::$instance;
+    }
+    
+    /**
+     * Gets the number of cells in each direction of the icons generated by 
+     * this IconGenerator.
+     *
+     * @return int
+     */
+    public function getCellCount()
+    {
+        return 4;
+    }
+
+    /**
+     * Determines the hue to be used in an icon for the specified hash.
+     *
+     * @return float Hue in the range [0, 1].
+     */
+    protected static function getHue($hash)
+    {
+        $value = hexdec(substr($hash, -7));
+        return $value / 0xfffffff;
+    }
+
+    /**
+     * Determines whether $newValue is duplicated in $source if all values 
+     * in $duplicateValues are determined to be equal.
+     *
+     * @return bool
+     */
+    private static function isDuplicate(
+        array $source, $newValue, 
+        array $duplicateValues)
+    {
+        if (in_array($newValue, $duplicateValues, true)) {
+            foreach ($duplicateValues as $value) {
+                if (in_array($value, $source, true)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Gets the specified octet from a byte array.
+     *
+     * @param string $hash The hexstring from which the octet will be retrieved.
+     * @param int $index The zero-based index of the octet to be returned.
+     * @return int
+     */
+    protected static function getOctet($hash, $index)
+    {
+        return hexdec($hash[$index]);
+    }
+
+    /**
+     * Gets an array of the shape categories to be rendered in icons generated 
+     * by this IconGenerator.
+     *
+     * @return array
+     */
+    protected function getCategories()
+    {
+        return $this->defaultShapes;
+    }
+
+    /**
+     * Gets an enumeration of individual shapes to be rendered in an icon for a 
+     * specific hash.
+     * 
+     * @param \Jdenticon\Rendering\ColorTheme $colorTheme A color theme 
+     *      specifying the colors to be used in the icon.
+     * @param string $hash The hash for which the shapes will be returned.
+     * @return array(Jdenticon\Shapes\Shape)
+     */
+    protected function getShapes($colorTheme, $hash)
+    {
+        $usedColorThemeIndexes = array();
+        $categories = self::getCategories();
+        $shapes = array();
+        $colorCount = $colorTheme->getCount();
+        
+        foreach ($categories as $category) {
+            $colorThemeIndex = 
+                self::getOctet($hash, $category->colorIndex) % $colorCount;
+
+            if (self::isDuplicate(
+                    // Disallow dark gray and dark color combo
+                    $usedColorThemeIndexes, $colorThemeIndex, array(0, 4)) || 
+                self::isDuplicate(
+                    // Disallow light gray and light color combo
+                    $usedColorThemeIndexes, $colorThemeIndex, array(2, 3))
+            ) {
+                $colorThemeIndex = 1;
+            }
+
+            $usedColorThemeIndexes[] = $colorThemeIndex;
+
+            $startRotationIndex = $category->rotationIndex === null ? 
+                0 : self::getOctet($hash, $category->rotationIndex);
+            $shapeIndex = 
+                self::getOctet($hash, $category->shapeIndex) % 
+                count($category->shapes);
+            $shape = $category->shapes[$shapeIndex];
+            
+            $shapes[] = new Shape(
+                /*$definition=*/ $shape,
+                /*$color=*/ $colorTheme->getByIndex($colorThemeIndex),
+                /*$positions=*/ $category->positions,
+                /*$startRotationIndex=*/ $startRotationIndex
+            );
+        }
+        
+        return $shapes;
+    }
+
+    /**
+     * Creates a quadratic copy of the specified 
+     * {@link \Jdenticon\Rendering\Rectangle} with a multiple of the cell count 
+     * as size.
+     *
+     * @param \Jdenticon\Rendering\Rectangle $rect The rectangle to be 
+     *      normalized.
+     */
+    protected function normalizeRectangle(\Jdenticon\Rendering\Rectangle $rect)
+    {
+        $size = (int)min($rect->width, $rect->height);
+        
+        // Make size a multiple of the cell count
+        $size -= $size % $this->getCellCount();
+        
+        return new Rectangle(
+            (int)($rect->x + ($rect->width - $size) / 2),
+            (int)($rect->y + ($rect->height - $size) / 2),
+            $size,
+            $size);
+    }
+
+    /**
+     * Renders the background of an icon.
+     *
+     * @param \Jdenticon\Rendering\RendererInterface $renderer The renderer to 
+     *      be used for rendering the icon on the target surface.
+     * @param \Jdenticon\Rendering\Rectangle $rect The outer bounds of the icon.
+     * @param \Jdenticon\IdenticonStyle $style The style of the icon.
+     * @param \Jdenticon\Rendering\ColorTheme $colorTheme A color theme 
+     *      specifying the colors to be used in the icon.
+     * @param string $hash The hash to be used as basis for the generated icon.
+     */
+    protected function renderBackground(
+        \Jdenticon\Rendering\RendererInterface $renderer, 
+        \Jdenticon\Rendering\Rectangle $rect,
+        \Jdenticon\IdenticonStyle $style, 
+        \Jdenticon\Rendering\ColorTheme $colorTheme, 
+        $hash)
+    {
+        $renderer->setBackgroundColor($style->getBackgroundColor());
+    }
+    
+    /**
+     * Renders the foreground of an icon.
+     *
+     * @param \Jdenticon\Rendering\RendererInterface $renderer The renderer to 
+     *      be used for rendering the icon on the target surface.
+     * @param \Jdenticon\Rendering\Rectangle $rect The outer bounds of the icon.
+     * @param \Jdenticon\IdenticonStyle $style The style of the icon.
+     * @param \Jdenticon\Rendering\ColorTheme $colorTheme A color theme 
+     *      specifying the colors to be used in the icon.
+     * @param string $hash The hash to be used as basis for the generated icon.
+     */
+    protected function renderForeground(
+        \Jdenticon\Rendering\RendererInterface $renderer, 
+        \Jdenticon\Rendering\Rectangle $rect,
+        \Jdenticon\IdenticonStyle $style, 
+        \Jdenticon\Rendering\ColorTheme $colorTheme, 
+        $hash)
+    {
+        // Ensure rect is quadratic and a multiple of the cell count
+        $normalizedRect = $this->normalizeRectangle($rect);
+        $cellSize = $normalizedRect->width / $this->getCellCount();
+
+        foreach ($this->getShapes($colorTheme, $hash) as $shape) {
+            $rotation = $shape->startRotationIndex;
+            
+            $renderer->beginShape($shape->color);
+            
+            $positionCount = count($shape->positions);
+            for ($i = 0; $i + 1 < $positionCount; $i += 2) {
+                $renderer->setTransform(new Transform(
+                    $normalizedRect->x + $shape->positions[$i + 0] * $cellSize,
+                    $normalizedRect->y + $shape->positions[$i + 1] * $cellSize,
+                    $cellSize, $rotation++ % 4));
+
+                $shape->definition->__invoke($renderer, $cellSize, $i / 2);
+            }
+            
+            $renderer->endShape();
+        }
+    }
+
+    /**
+     * Generates an identicon for the specified hash.
+     *
+     * @param \Jdenticon\Rendering\RendererInterface $renderer The renderer to 
+     *      be used for rendering the icon on the target surface.
+     * @param \Jdenticon\Rendering\Rectangle $rect The outer bounds of the icon.
+     * @param \Jdenticon\IdenticonStyle $style The style of the icon.
+     * @param string $hash The hash to be used as basis for the generated icon.
+     */
+    public function generate(
+        \Jdenticon\Rendering\RendererInterface $renderer, 
+        \Jdenticon\Rendering\Rectangle $rect,
+        \Jdenticon\IdenticonStyle $style, 
+        $hash)
+    {
+        $hue = self::getHue($hash);
+        $colorTheme = new ColorTheme($hue, $style);
+
+        $this->renderBackground($renderer, $rect, $style, $colorTheme, $hash);
+        $this->renderForeground($renderer, $rect, $style, $colorTheme, $hash);
+    }
+}

+ 296 - 0
vendor/jdenticon/jdenticon/src/Rendering/ImagickRenderer.php

@@ -0,0 +1,296 @@
+<?php
+namespace Jdenticon\Rendering;
+
+class ImagickRendererLine
+{
+    /**
+     * Creates a new line from a vector.
+     * 
+     * @param float $x0 Vector start x coordinate.
+     * @param float $y0 Vector start y coordinate.
+     * @param float $x1 Vector end x coordinate.
+     * @param float $y1 Vector end y coordinate.
+     */
+    public static function fromVector($x0, $y0, $x1, $y1) 
+    {
+        $line = new ImagickRendererLine();
+
+        $line->Px = $x0;
+        $line->Py = $y0;
+        
+        $line->rx = $x1 - $x0;
+        $line->ry = $y1 - $y0;
+
+        return $line;
+    }
+
+    /**
+     * Moves the line to the right relative the direction vector.
+     * 
+     * @param float $distance  The number of pixels to move the line.
+     */
+    public function moveRight($distance) 
+    {
+        // Ortogonal direction vector
+        $rx = -$this->ry;
+        $ry = $this->rx;
+        
+        $multiplier = $distance / sqrt($rx * $rx + $ry * $ry);
+
+        $this->Px += $rx * $multiplier;
+        $this->Py += $ry * $multiplier;
+    }
+
+    /**
+     * Computes the point at which two lines intersect.
+     * 
+     * @return Point|null
+     */
+    public static function intersection(
+        ImagickRendererLine $l1,
+        ImagickRendererLine $l2
+    ) {
+        $rs = $l1->rx * $l2->ry - $l1->ry * $l2->rx;
+
+        if ($rs == 0) {
+            return null;
+        }
+        
+        $u = (($l2->Px - $l1->Px) * $l1->ry - ($l2->Py - $l1->Py) * $l1->rx) / $rs;
+
+        return new Point(
+            $l2->Px + $u * $l2->rx,
+            $l2->Py + $u * $l2->ry
+        );
+    }
+
+    /**
+     * X coordiate of a point on the line.
+     * @var float 
+     */
+    public $Px;
+    /**
+     * Y coordiate of a point on the line.
+     * @var float 
+     */
+    public $Py;
+
+    /**
+     * X component of the direction vector.
+     * @var float 
+     */
+    public $rx;
+    /**
+     * Y component of the direction vector.
+     * @var float 
+     */
+    public $ry;
+}
+
+/**
+ * Renders icons as PNG using ImageMagick.
+ * 
+ * Unfortunately the ImageMagick vector renderer has a lot of quirks that
+ * we need to accomodate. The most obvious is that the renderer always render
+ * all polygons 1/2 pixel too large. This behavior is documented here:
+ * http://www.imagemagick.org/Usage/draw/#bounds
+ * 
+ * To prevent this we shrink all polygons with 1/2 pixels before passing them
+ * to ImageMagick. 
+ * 
+ * Another quirk is that if the polygon including the 1/2 pixel invisible border
+ * align perfectly to the pixel grid, white pixels will appear near edge
+ * intersections. Paths containing arcs will sometimes appear with horizontal 
+ * lines drawn to the right side of the image.
+ * 
+ * To prevent this (in most cases) we add 0.00013 to all coordinates.
+ * 
+ */
+class ImagickRenderer extends AbstractRenderer
+{
+    private $draw;
+    private $polygon;
+    private $width;
+    private $height;
+
+    /**
+     * This constant is added to all coordinates to avoid white pixels
+     * that sometimes appear near edge intersections when a polygon including
+     * its 1/2 invisible border is perfectly aligned to the pixel grid. 
+     */
+    const PREVENT_WHITE_PIXELS_OFFSET = -0.00013;
+
+    /**
+     * Creates an instance of the class ImagickRenderer.
+     *
+     * @param int $width The width of the icon in pixels.
+     * @param int $height The height of the icon in pixels.
+     */
+    public function __construct($width, $height)
+    {
+        parent::__construct();
+        $this->draw = new \ImagickDraw();
+        $this->draw->setStrokeWidth(1);
+        
+        $this->width = $width;
+        $this->height = $height;
+    }
+
+    /**
+     * Gets the MIME type of the renderer output.
+     *
+     * @return string
+     */
+    public function getMimeType()
+    {
+        return 'image/png';
+    }
+
+    /**
+     * Adds a circle without translating its coordinates.
+     *
+     * @param  float $x  The x-coordinate of the bounding rectangle 
+     *      upper-left corner.
+     * @param  float $y  The y-coordinate of the bounding rectangle 
+     *      upper-left corner.
+     * @param  float $size  The size of the bounding rectangle.
+     * @param  bool $counterClockwise  If true the circle will be drawn 
+     *      counter clockwise.
+     */
+    protected function addCircleNoTransform($x, $y, $size, $counterClockwise)
+    {
+        if ($counterClockwise) {
+            $x -= $size + 0.5;
+            $y -= 1;
+        } else {
+            $size -= 1;
+            $y -= 0.5;
+        }
+
+        $radius = $size / 2;
+        $this->draw->pathMoveToAbsolute(
+            $x + $size + self::PREVENT_WHITE_PIXELS_OFFSET, 
+            $y + $radius + self::PREVENT_WHITE_PIXELS_OFFSET);
+        $this->draw->pathEllipticArcRelative($radius, $radius, 
+            M_PI * 2, true, $counterClockwise, 0, 1);
+        $this->draw->pathClose();
+    }
+
+    /**
+     * Adds a polygon without translating its coordinates.
+     *
+     * @param  array $points  An array of the points that the polygon consists of.
+     */
+    protected function addPolygonNoTransform($points)
+    {
+        $firstPoint = $points[0];
+        $lastPoint = end($points);
+
+        // Ensure polygon is closed
+        if ($firstPoint->x != $lastPoint->x ||
+            $firstPoint->y != $lastPoint->y
+        ) {
+            $points[] = $firstPoint;
+        }
+
+        // Determine if polygon is an outer ring
+        // (source: https://stackoverflow.com/a/1165943)
+        $sum = 0;
+        $previousPoint = null;
+
+        foreach ($points as $point) {
+            if ($previousPoint !== null) {
+                $sum += ($point->x - $previousPoint->x) * ($point->y + $previousPoint->y);
+            }
+            $previousPoint = $point;
+        }
+
+        $isOuterRing = $sum < 0;
+
+        // ImageMagick draws all polygons 1 pixel too large. To prevent this,
+        // shrink polygons by 1 pixel.
+        $lines = array();
+        $previousPoint = null;
+
+        // Transform all edges to lines.
+        foreach ($points as $point) {
+            if ($previousPoint !== null) {
+                $lines[] = $line = ImagickRendererLine::fromVector(
+                    $previousPoint->x, $previousPoint->y,
+                    $point->x, $point->y
+                );
+
+                // ImageMagick draws a 1px border along the outer ring. To 
+                // prevent the border overlaps inner rings too close to the 
+                // outer ring, only inflate inner rings by 1/4 pixel.
+                $line->moveRight($isOuterRing ? 0.5 : 0.25);
+            }
+            $previousPoint = $point;
+        }
+
+        $first = true;
+        $previousLine = end($lines);
+
+        // Reconstruct point array from line intersections and draw the polygon
+        foreach ($lines as $line) {
+            $points[] = $point = ImagickRendererLine::intersection($previousLine, $line);
+
+            // Subtract 1/2 pixel to align the shapes to the pixel grid.
+            if ($first) {
+                $this->draw->pathMoveToAbsolute(
+                    $point->x - 0.5 + self::PREVENT_WHITE_PIXELS_OFFSET,
+                    $point->y - 0.5 + self::PREVENT_WHITE_PIXELS_OFFSET);
+                    
+                $first = false;
+            } else {
+                $this->draw->pathLineToAbsolute(
+                    $point->x - 0.5 + self::PREVENT_WHITE_PIXELS_OFFSET,
+                    $point->y - 0.5 + self::PREVENT_WHITE_PIXELS_OFFSET);
+            }
+
+            $previousLine = $line;
+        }
+
+        $this->draw->pathClose();
+    }
+
+    /**
+     * Begins a new shape. The shape should be ended with a call to endShape.
+     *
+     * @param \Jdenticon\Color $color The color of the shape.
+     */
+    public function beginShape(\Jdenticon\Color $color)
+    {
+        $this->draw->setFillColor($color->__toString());
+        $this->draw->pathStart();
+    }
+    
+    /**
+     * Ends the currently drawn shape.
+     */
+    public function endShape()
+    {
+        $this->draw->pathFinish();
+    }
+
+    /**
+     * Renders this image as a PNG data stream.
+     * 
+     * @return string
+     */
+    public function getData()
+    {
+        $imagick = new \Imagick();
+        $imagick->newImage($this->width, $this->height, $this->backgroundColor->__toString());
+        $imagick->setImageFormat('png');
+
+        if (method_exists($imagick, 'setImageProperty')) {
+            $imagick->setImageProperty('Software', 'Jdenticon');
+        } else {
+            $imagick->setImageAttribute('Software', 'Jdenticon');
+        }
+        
+        $imagick->drawImage($this->draw);
+        return $imagick->getImageBlob();
+    }
+}

+ 123 - 0
vendor/jdenticon/jdenticon/src/Rendering/InternalPngRenderer.php

@@ -0,0 +1,123 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Rendering;
+
+use Jdenticon\Canvas\Canvas;
+
+/**
+ * Renders icons as PNG using the internal vector rasterizer.
+ */
+class InternalPngRenderer extends AbstractRenderer
+{
+    private $canvas;
+    private $ctx;
+
+    /**
+     * Creates an instance of the class ImagickRenderer.
+     *
+     * @param int $width  The width of the icon in pixels.
+     * @param int $height  The height of the icon in pixels.
+     */
+    public function __construct($width, $height)
+    {
+        parent::__construct();
+        $this->canvas = new Canvas($width, $height);
+        $this->ctx = $this->canvas->getContext();
+    }
+    
+    /**
+     * Gets the MIME type of the renderer output.
+     *
+     * @return string
+     */
+    public function getMimeType()
+    {
+        return 'image/png';
+    }
+
+    /**
+     * Adds a circle without translating its coordinates.
+     *
+     * @param float $x  The x-coordinate of the bounding rectangle 
+     *      upper-left corner.
+     * @param float $y  The y-coordinate of the bounding rectangle 
+     *      upper-left corner.
+     * @param float $size  The size of the bounding rectangle.
+     * @param bool $counterClockwise  If true the circle will be drawn 
+     *      counter clockwise.
+     */
+    protected function addCircleNoTransform($x, $y, $size, $counterClockwise)
+    {
+        $radius = $size / 2;
+        $this->ctx->moveTo($x + $size, $y + $radius);
+        $this->ctx->arc(
+            $x + $radius, $y + $radius, 
+            $radius, 0, M_PI * 2, 
+            $counterClockwise);
+        $this->ctx->closePath();
+    }
+
+    /**
+     * Adds a polygon without translating its coordinates.
+     *
+     * @param array $points  An array of the points that the polygon consists of.
+     */
+    protected function addPolygonNoTransform($points)
+    {
+        $pointCount = count($points);
+        $this->ctx->moveTo($points[0]->x, $points[0]->y);
+        for ($i = 1; $i < $pointCount; $i++) {
+            $this->ctx->lineTo($points[$i]->x, $points[$i]->y);
+        }
+        $this->ctx->closePath();
+    }
+
+    /**
+     * Sets the background color of the icon.
+     *
+     * @param \Jdenticon\Color $color  The background color.
+     */
+    public function setBackgroundColor(\Jdenticon\Color $color)
+    {
+        parent::setBackgroundColor($color);
+        $this->canvas->backColor = $this->backgroundColor->toRgba();
+    }
+
+    /**
+     * Begins a new shape. The shape should be ended with a call to endShape.
+     *
+     * @param \Jdenticon\Color $color  The color of the shape.
+     */
+    public function beginShape(\Jdenticon\Color $color)
+    {
+        $this->ctx->fillStyle = $color->toRgba();
+        $this->ctx->beginPath();
+    }
+    
+    /**
+     * Ends the currently drawn shape.
+     */
+    public function endShape()
+    {
+        $this->ctx->fill();
+    }
+
+    /**
+     * Gets the output from the renderer.
+     *
+     * @return string
+     */
+    public function getData()
+    {
+        return $this->canvas->toPng(array('Software' => 'Jdenticon'));
+    }
+}

+ 54 - 0
vendor/jdenticon/jdenticon/src/Rendering/Point.php

@@ -0,0 +1,54 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+ namespace Jdenticon\Rendering;
+
+/**
+ * A 2D coordinate.
+ */
+class Point
+{
+    /**
+     * Creates a new Point.
+     *
+     * @param float $x X coordinate.
+     * @param float $y Y coordinate.
+     */
+    public function __construct($x, $y)
+    {
+        $this->x = $x;
+        $this->y = $y;
+    }
+
+    /**
+     * The X coordinate of this point.
+     *
+     * @var float
+     */
+    public $x;
+
+    /**
+     * The Y coordinate of this point.
+     *
+     * @var float
+     */
+    public $y;
+
+    /**
+     * Gets a string representation of the point.
+     *
+     * @return string
+     */
+    public function __toString()
+    {
+        return $this->x + ", " + $this->y;
+    }
+}

+ 60 - 0
vendor/jdenticon/jdenticon/src/Rendering/Rectangle.php

@@ -0,0 +1,60 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Rendering;
+
+/**
+ * Specifies the bounds of a 2D rectangle.
+ */
+class Rectangle
+{
+    /**
+     * The X coordinate of the left side of the rectangle.
+     *
+     * @var float
+     */
+    public $x;
+    
+    /**
+     * The Y coordinate of the top side of the rectangle.
+     *
+     * @var float
+     */
+    public $y;
+    
+    /**
+     * The width of the rectangle.
+     * @var float
+     */
+    public $width;
+    
+    /**
+     * The height of the rectangle.
+     * @var float
+     */
+    public $height;
+
+    /**
+     * Creates a new Rectangle.
+     *
+     * @param float $x The X coordinate of the left edge of the rectangle.
+     * @param float $y The Y coordinate of the top edge of the rectangle.
+     * @param float $width The width of the rectangle.
+     * @param float $height The height of the rectangle.
+     */
+    public function __construct($x, $y, $width, $height)
+    {
+        $this->x = $x;
+        $this->y = $y;
+        $this->width = $width;
+        $this->height = $height;
+    }
+}

+ 141 - 0
vendor/jdenticon/jdenticon/src/Rendering/RendererInterface.php

@@ -0,0 +1,141 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Rendering;
+
+use Jdenticon\Color;
+
+/**
+ * Interface for an identicon renderer.
+ */
+interface RendererInterface
+{
+    /**
+     * Sets the current transform that will be applied on all coordinates before
+     * being rendered to the target image.
+     *
+     * @param \Jdenticon\Rendering\Transform $transform The transform to set. 
+     *      If NULL is specified any existing transform is removed.
+     */
+    public function setTransform(\Jdenticon\Rendering\Transform $transform);
+    
+    /**
+     * Gets the current transform that will be applied on all coordinates before 
+     * being rendered to the target image.
+     *
+     * @return \Jdenticon\Rendering\Transform
+     */
+    public function getTransform();
+    
+    /**
+     * Sets the background color of the image.
+     *
+     * @param \Jdenticon\Color $color The image background color.
+     */
+    public function setBackgroundColor(Color $color);
+    
+    /**
+     * Gets the background color of the image.
+     *
+     * @return \Jdenticon\Color
+     */
+    public function getBackgroundColor();
+    
+    /**
+     * Gets the MIME type of the renderer output.
+     *
+     * @return string
+     */
+    public function getMimeType();
+    
+    /**
+     * Begins a new shape. The shape should be ended with a call to endShape.
+     *
+     * @param \Jdenticon\Color $color The color of the shape.
+     */
+    public function beginShape(Color $color);
+    
+    /**
+     * Ends the currently drawn shape.
+     */
+    public function endShape();
+
+    /**
+     * Adds a rectangle to the image.
+     *
+     * @param float $x The x-coordinate of the rectangle upper-left corner.
+     * @param float $y The y-coordinate of the rectangle upper-left corner.
+     * @param float $width The width of the rectangle.
+     * @param float $height The height of the rectangle.
+     * @param bool $invert If true the area of the rectangle will be removed 
+     *      from the filled area.
+     */
+    public function addRectangle($x, $y, $width, $height, $invert = false);
+
+    /**
+     * Adds a circle to the image.
+     *
+     * @param float $x The x-coordinate of the bounding rectangle 
+     *      upper-left corner.
+     * @param float $y The y-coordinate of the bounding rectangle 
+     *      upper-left corner.
+     * @param float $size The size of the bounding rectangle.
+     * @param bool $invert If true the area of the circle will be removed from 
+     *      the filled area.
+     */
+    public function addCircle($x, $y, $size, $invert = false);
+
+    /**
+     * Adds a polygon to the image.
+     *
+     * @param array $points Array of points that the polygon consists of.
+     * @param bool $invert If true the area of the polygon will be removed from
+     *      the filled area.
+     */
+    public function addPolygon($points, $invert = false);
+
+    /**
+     * Adds a triangle to the image.
+     *
+     * @param float $x The x-coordinate of the bounding rectangle 
+     *      upper-left corner.
+     * @param float $y The y-coordinate of the bounding rectangle 
+     *      upper-left corner.
+     * @param float $width The width of the bounding rectangle.
+     * @param float $height The height of the bounding rectangle.
+     * @param float $direction The direction of the 90 degree corner of 
+     *      the triangle.
+     * @param bool $invert If true the area of the triangle will be removed 
+     *      from the filled area.
+     */
+    public function addTriangle($x, $y, $width, $height, $direction, $invert = false);
+
+    /**
+     * Adds a rhombus to the image.
+     *
+     * @param float $x The x-coordinate of the bounding rectangle 
+     *      upper-left corner.
+     * @param float $y The y-coordinate of the bounding rectangle 
+     *      upper-left corner.
+     * @param float $width The width of the bounding rectangle.
+     * @param float $height The height of the bounding rectangle.
+     * @param bool $invert If true the area of the rhombus will be removed 
+     *      from the filled area.
+     */
+    public function addRhombus($x, $y, $width, $height, $invert = false);
+    
+    /**
+     * Gets the output from the renderer.
+     *
+     * @return string
+     */
+    public function getData();
+}

+ 83 - 0
vendor/jdenticon/jdenticon/src/Rendering/SvgPath.php

@@ -0,0 +1,83 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Rendering;
+
+/**
+ * Represents a SVG path element.
+ */
+class SvgPath
+{
+    private $dataString;
+    
+    public function __construct() 
+    {
+        $this->dataString = '';
+    }
+
+    /**
+     * Adds a circle to the SVG.
+     *
+     * @param float $x X coordinate of the left side of the containing rectangle.
+     * @param float $y Y coordinate of the top side of the containing rectangle.
+     * @param float $size The diameter of the circle.
+     * @param bool $counterClockwise If true the circle will be drawn counter 
+     *      clockwise. This affects the rendering since the evenodd filling rule 
+     *      is used by Jdenticon.
+     */
+    public function addCircle($x, $y, $size, $counterClockwise)
+    {
+        $sweepFlag = $counterClockwise ? '0' : '1';
+        $radiusAsString = number_format($size / 2, 2, '.', '');
+
+        $this->dataString .=
+            'M'. number_format($x, 2, '.', '') .' '. 
+                number_format($y + $size / 2, 2, '.', '').
+            'a'. $radiusAsString .','. $radiusAsString .' 0 1,'. 
+                $sweepFlag .' '. number_format($size, 2, '.', '') .',0'.
+            'a'. $radiusAsString .','. $radiusAsString .' 0 1,'. 
+                $sweepFlag .' '. number_format(-$size, 2, '.', '') .',0';
+    }
+
+    /**
+     * Adds a polygon to the SVG.
+     *
+     * @param array(\Jdenticon\Rendering\Point) $points The corners of the 
+     *      polygon.
+     */
+    public function addPolygon($points)
+    {
+        $pointCount = count($points);
+
+        $this->dataString .= 'M'. 
+            number_format($points[0]->x, 2, '.', '') .' '. 
+            number_format($points[0]->y, 2, '.', '');
+
+        for ($i = 1; $i < $pointCount; $i++) {
+            $this->dataString .= 'L'. 
+                number_format($points[$i]->x, 2, '.', '') .' '. 
+                number_format($points[$i]->y, 2, '.', '');
+        }
+
+        $this->dataString .= 'Z';
+    }
+
+    /**
+     * Gets the path as a SVG path string.
+     *
+     * @return string
+     */
+    public function __toString()
+    {
+        return $this->dataString;
+    }
+}
+

+ 133 - 0
vendor/jdenticon/jdenticon/src/Rendering/SvgRenderer.php

@@ -0,0 +1,133 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Rendering;
+
+/**
+ * Renders icons as SVG paths.
+ */
+class SvgRenderer extends AbstractRenderer
+{
+    private $pathsByColor = array();
+    private $path;
+    private $width;
+    private $height;
+
+    /**
+     * Creates a new SvgRenderer.
+     *
+     * @param int $width The width of the icon in pixels.
+     * @param int $height The height of the icon in pixels.
+     */
+    public function __construct($width, $height)
+    {
+        $this->width = $width;
+        $this->height = $height;
+    }
+    
+    /**
+     * Gets the MIME type of the renderer output.
+     *
+     * @return string
+     */
+    public function getMimeType()
+    {
+        return 'image/svg+xml';
+    }
+
+    /**
+     * Adds a circle without translating its coordinates.
+     *
+     * @param float $x The x-coordinate of the bounding rectangle 
+     *      upper-left corner.
+     * @param float $y The y-coordinate of the bounding rectangle 
+     *      upper-left corner.
+     * @param float $size The size of the bounding rectangle.
+     * @param bool $counterClockwise If true the circle will be drawn 
+     *      counter clockwise.
+     */
+    protected function addCircleNoTransform($x, $y, $size, $counterClockwise)
+    {
+        $this->path->addCircle($x, $y, $size, $counterClockwise);
+    }
+
+    /**
+     * Adds a polygon without translating its coordinates.
+     *
+     * @param array $points An array of the points that the polygon consists of.
+     */
+    protected function addPolygonNoTransform($points)
+    {
+        $this->path->addPolygon($points);
+    }
+
+    /**
+     * Begins a new shape. The shape should be ended with a call to endShape.
+     *
+     * @param \Jdenticon\Color $color The color of the shape.
+     */
+    public function beginShape(\Jdenticon\Color $color)
+    {
+        $colorString = $color->toHexString(6);
+        
+        if (isset($this->pathsByColor[$colorString])) {
+            $this->path = $this->pathsByColor[$colorString];
+        } else {
+            $this->path = new SvgPath();
+            $this->pathsByColor[$colorString] = $this->path;
+        }
+    }
+    
+    /**
+     * Ends the currently drawn shape.
+     */
+    public function endShape()
+    {
+    }
+    
+    /**
+     * Generates an SVG string of the renderer output.
+     *
+     * @param bool $fragment If true an SVG string without the root svg element 
+     *      will be rendered.
+     */
+    public function getData($fragment = false)
+    {
+        $svg = '';
+        $widthAsString = number_format($this->width, 0, '.', '');
+        $heightAsString = number_format($this->height, 0, '.', '');
+        
+        if (!$fragment) {
+            $svg .= '<svg xmlns="http://www.w3.org/2000/svg" width="' .
+                $widthAsString .'" height="'. $heightAsString .'" viewBox="0 0 '.
+                $widthAsString .' '. $heightAsString .
+                '" preserveAspectRatio="xMidYMid meet">';
+        }
+
+        if ($this->backgroundColor->a > 0) {
+            $opacity = (float)$this->backgroundColor->a / 255;
+            $svg .= '<rect fill="'. $this->backgroundColor->toHexString(6) .
+                '" fill-opacity="'. number_format($opacity, 2, '.', '').
+                '" x="0" y="0" width="'. $widthAsString .'" height="'. 
+                $heightAsString .'"/>';
+        }
+        
+        foreach ($this->pathsByColor as $color => $path) {
+            $svg .= "<path fill=\"$color\" d=\"$path\"/>";
+        }
+
+        if (!$fragment) {
+            $svg .= '</svg>';
+        }
+        
+        return $svg;
+    }
+}

+ 79 - 0
vendor/jdenticon/jdenticon/src/Rendering/Transform.php

@@ -0,0 +1,79 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Rendering;
+
+/*
+ * Translates and rotates a point before being rendered.
+ */
+class Transform
+{
+    private $x;
+    private $y;
+    private $size;
+    private $rotation;
+
+    /**
+     * Creates a new Transform.
+     *
+     * @param float $x The x-coordinate of the upper left corner of the 
+     *      transformed rectangle.
+     * @param float $y The y-coordinate of the upper left corner of the 
+     *      transformed rectangle.
+     * @param float $size The size of the transformed rectangle.
+     * @param integer $rotation Rotation specified as 
+     *      0 = 0 rad, 1 = 0.5π rad, 2 = π rad, 3 = 1.5π rad.
+     */
+    public function __construct($x, $y, $size, $rotation)
+    {
+        $this->x = $x;
+        $this->y = $y;
+        $this->size = $size;
+        $this->rotation = $rotation;
+    }
+    
+    /**
+     * Gets a noop transform.
+     *
+     * @return \Jdenticon\Rendering\Transform
+     */
+    public static function getEmpty() 
+    {
+        return new Transform(0, 0, 0, 0);
+    }
+
+    /**
+     * Transforms the specified point based on the translation and rotation 
+     * specification for this Transform.
+     *
+     * @param float $x x-coordinate
+     * @param float $y y-coordinate
+     * @param float $width The width of the transformed rectangle. If greater 
+     *      than 0, this will ensure the returned point is of the upper left 
+     *      corner of the transformed rectangle.
+     * @param float $height The height of the transformed rectangle. If greater 
+     *      than 0, this will ensure the returned point is of the upper left 
+     *      corner of the transformed rectangle.
+     * @return \Jdenticon\Rendering\Point
+     */
+    public function transformPoint($x, $y, $width = 0, $height = 0)
+    {
+        $right = $this->x + $this->size;
+        $bottom = $this->y + $this->size;
+        
+        switch ($this->rotation) {
+            case 1: return new Point($right - $y - $height, $this->y + $x);
+            case 2: return new Point($right - $x - $width, $bottom - $y - $height);
+            case 3: return new Point($this->x + $y, $bottom - $x - $width);
+            default: return new Point($this->x + $x, $this->y + $y);
+        }
+    }
+}

+ 35 - 0
vendor/jdenticon/jdenticon/src/Rendering/TriangleDirection.php

@@ -0,0 +1,35 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Rendering;
+
+/**
+ * Specifies in what direction the 90 degree angle of a triangle is pointing.
+ */
+class TriangleDirection
+{
+    /**
+     * The 90 degree angle is pointing to South West.
+     */
+    const SOUTH_WEST = 0;
+    /**
+     * The 90 degree angle is pointing to North West.
+     */
+    const NORTH_WEST = 1;
+    /**
+     * The 90 degree angle is pointing to North East.
+     */ 
+    const NORTH_EAST = 2;
+    /**
+     * The 90 degree angle is pointing to South East.
+     */
+    const SOUTH_EAST = 3;
+}

+ 62 - 0
vendor/jdenticon/jdenticon/src/Shapes/Shape.php

@@ -0,0 +1,62 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Shapes;
+
+/**
+ * Represents a shape to be rendered in an icon. These instances are 
+ * hash specific.
+ */
+class Shape
+{
+    /**
+     * The shape definition to be used to render the shape.
+     *
+     * @var function(
+     *      \Jdenticon\Rendering\RendererInterface $renderer, 
+     *      \Jdenticon\Shapes\ShapePosition $cell, 
+     *      int $index)
+     */
+    public $definition;
+
+    /**
+     * The fill color of the shape.
+     *
+     * @var Jdenticon\Color
+     */
+    public $color;
+
+    /**
+     * The positions in which the shape will be rendered.
+     * 
+     * @var array(\Jdenticon\Shapes\ShapePosition)
+     */
+    public $positions;
+
+    /**
+     * The rotation index of the icon in the first position.
+     *
+     * @var int
+     */
+    public $startRotationIndex;
+    
+    public function __construct(
+        $definition, 
+        \Jdenticon\Color $color, 
+        array $positions, 
+        $startRotationIndex)
+    {
+        $this->definition = $definition;
+        $this->color = $color;
+        $this->positions = $positions;
+        $this->startRotationIndex = $startRotationIndex;
+    }
+}

+ 71 - 0
vendor/jdenticon/jdenticon/src/Shapes/ShapeCategory.php

@@ -0,0 +1,71 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Shapes;
+
+/**
+ * Represents a category of shapes that can be rendered in an icon. These 
+ * instances are not hash specific.
+ */
+class ShapeCategory
+{
+    /**
+     * The index of the hash octet determining the color of shapes in this 
+     * category.
+     *
+     * @var int
+     */
+    public $colorIndex;
+
+    /**
+     * A list of possible shape definitions in this category.
+     *
+     * @var array(function(
+     *      \Jdenticon\Rendering\RendererInterface $renderer, 
+     *      \Jdenticon\Shapes\ShapePosition $cell, 
+     *      int $index))
+     */
+    public $shapes;
+
+    /**
+     * The index of the hash octet determining which of the shape definitions 
+     * that will be used for a particular hash.
+     *
+     * @var int
+     */
+    public $shapeIndex;
+
+    /**
+     * The index of the hash octet determining the rotation index of the shape 
+     * in the first position.
+     *
+     * @var int
+     */
+    public $rotationIndex;
+
+    /**
+     * The positions in which the shapes of this category will be rendered.
+     *
+     * @var array(int)
+     */
+    public $positions;
+    
+    public function __construct(
+        $colorIndex, array $shapes, $shapeIndex, $rotationIndex, array $positions)
+    {
+        $this->colorIndex = $colorIndex;
+        $this->shapes = $shapes;
+        $this->shapeIndex = $shapeIndex;
+        $this->rotationIndex = $rotationIndex;
+        $this->positions = $positions;
+    }
+}
+

+ 243 - 0
vendor/jdenticon/jdenticon/src/Shapes/ShapeDefinitions.php

@@ -0,0 +1,243 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Shapes;
+
+use Jdenticon\Rendering\Point;
+use Jdenticon\Rendering\TriangleDirection;
+
+/**
+ * Provides definitions for the default shapes used in identicons.
+ */
+class ShapeDefinitions
+{
+    private static $outerShapes;
+    private static $centerShapes;
+    
+    /**
+     * Gets an array of all possible outer shapes. Do not modify the returned
+     * array.
+     *
+     * @return array(\Jdenticon\Rendering\Shape)
+     */
+    public static function getOuterShapes()
+    {
+        if (self::$outerShapes === null) {
+            self::$outerShapes = self::createOuterShapes();
+        }
+        return self::$outerShapes;
+    }
+    
+    /**
+     * Gets an array of all possible center shapes. Do not modify the returned
+     * array.
+     *
+     * @return array(\Jdenticon\Rendering\Shape)
+     */
+    public static function getCenterShapes()
+    {
+        if (self::$centerShapes === null) {
+            self::$centerShapes = self::createCenterShapes();
+        }
+        return self::$centerShapes;
+    }
+    
+    private static function createOuterShapes()
+    {
+        return array(
+            function ($renderer, $cell, $index)
+            {
+                $renderer->addTriangle(0, 0, $cell, $cell, 0);
+            },
+            function ($renderer, $cell, $index)
+            {
+                $renderer->addTriangle(0, $cell / 2, $cell, $cell / 2, 0);
+            },
+            function ($renderer, $cell, $index)
+            {
+                $renderer->addRhombus(0, 0, $cell, $cell);
+            },
+            function ($renderer, $cell, $index)
+            {
+                $m = $cell / 6;
+                $renderer->addCircle($m, $m, $cell - 2 * $m);
+            }
+        );
+    }
+    
+    private static function createCenterShapes()
+    {
+        return array(
+            function ($renderer, $cell, $index)
+            {
+                $k = $cell * 0.42;
+                $renderer->addPolygon(array(
+                    new Point(0, 0),
+                    new Point($cell, 0),
+                    new Point($cell, $cell - $k * 2),
+                    new Point($cell - $k, $cell),
+                    new Point(0, $cell)
+                ));
+            },
+            function ($renderer, $cell, $index)
+            {
+                $w = (int)($cell * 0.5);
+                $h = (int)($cell * 0.8);
+                $renderer->addTriangle(
+                    $cell - $w, 0, $w, $h, 
+                    TriangleDirection::NORTH_EAST);
+            },
+            function ($renderer, $cell, $index)
+            {
+                $s = (int)($cell / 3);
+                $renderer->addRectangle($s, $s, $cell - $s, $cell - $s);
+            },
+            function ($renderer, $cell, $index)
+            {
+                $tmp = $cell * 0.1;
+
+                if ($tmp > 1) {
+                    // large icon => truncate decimals
+                    $inner = (int)$tmp;
+                } elseif ($tmp > 0.5) {
+                    // medium size icon => fixed width
+                    $inner = 1;
+                } else {
+                    // small icon => anti-aliased border
+                    $inner = $tmp;
+                }
+
+                // Use fixed outer border widths in small icons to ensure 
+                // the border is drawn
+                if ($cell < 6) {
+                    $outer = 1;
+                } elseif ($cell < 8) {
+                    $outer = 2;
+                } else {
+                    $outer = (int)($cell / 4);
+                }
+
+                $renderer->addRectangle(
+                    $outer, $outer, 
+                    $cell - $inner - $outer, $cell - $inner - $outer);
+            },
+            function ($renderer, $cell, $index)
+            {
+                $m = (int)($cell * 0.15);
+                $s = (int)($cell * 0.5);
+                $renderer->addCircle($cell - $s - $m, $cell - $s - $m, $s);
+            },
+            function ($renderer, $cell, $index)
+            {
+                $inner = $cell * 0.1;
+                $outer = $inner * 4;
+                
+                // Align edge to nearest pixel in large icons
+                if ($outer > 3) {
+                    $outer = (int)$outer;
+                }
+
+                $renderer->addRectangle(0, 0, $cell, $cell);
+                $renderer->addPolygon(array(
+                    new Point($outer, $outer),
+                    new Point($cell - $inner, $outer),
+                    new Point($outer + ($cell - $outer - $inner) / 2, 
+                        $cell - $inner)
+                ), true);
+            },
+            function ($renderer, $cell, $index)
+            {
+                $renderer->addPolygon(array(
+                    new Point(0, 0),
+                    new Point($cell, 0),
+                    new Point($cell, $cell * 0.7),
+                    new Point($cell * 0.4, $cell * 0.4),
+                    new Point($cell * 0.7, $cell),
+                    new Point(0, $cell)
+                ));
+            },
+            function ($renderer, $cell, $index)
+            {
+                $renderer->addTriangle(
+                    $cell / 2, $cell / 2, $cell / 2, $cell / 2, 
+                    TriangleDirection::SOUTH_EAST);
+            },
+            function ($renderer, $cell, $index)
+            {
+                $renderer->addPolygon(array(
+                    new Point(0, 0),
+                    new Point($cell, 0),
+                    new Point($cell, $cell / 2),
+                    new Point($cell / 2, $cell),
+                    new Point(0, $cell)
+                ));
+            },
+            function ($renderer, $cell, $index)
+            {
+                $tmp = $cell * 0.14;
+                
+                if ($cell < 8) {
+                     // small icon => anti-aliased border
+                     $inner = $tmp;
+                } else {
+                     // large icon => truncate decimals
+                     $inner = (int)$tmp;
+                }
+                
+                // Use fixed outer border widths in small icons to ensure 
+                // the border is drawn
+                if ($cell < 4) {
+                     $outer = 1;
+                } elseif ($cell < 6) {
+                     $outer = 2;
+                } else {
+                     $outer = (int)($cell * 0.35);
+                }
+
+                $renderer->addRectangle(0, 0, $cell, $cell);
+                $renderer->addRectangle(
+                    $outer, $outer, 
+                    $cell - $outer - $inner, $cell - $outer - $inner, true);
+            },
+            function ($renderer, $cell, $index)
+            {
+                $inner = $cell * 0.12;
+                $outer = $inner * 3;
+
+                $renderer->addRectangle(0, 0, $cell, $cell);
+                $renderer->addCircle($outer, $outer, $cell - $inner - $outer, 
+                    true);
+            },
+            function ($renderer, $cell, $index)
+            {
+                $renderer->addTriangle(
+                    $cell / 2, $cell / 2, $cell / 2, $cell / 2, 
+                    TriangleDirection::SOUTH_EAST);
+            },
+            function ($renderer, $cell, $index)
+            {
+                $m = $cell * 0.25;
+
+                $renderer->addRectangle(0, 0, $cell, $cell);
+                $renderer->addRhombus($m, $m, $cell - $m, $cell - $m, true);
+            },
+            function ($renderer, $cell, $index)
+            {
+                $m = $cell * 0.4;
+                $s = $cell * 1.2;
+
+                if ($index != 0) {
+                    $renderer->addCircle($m, $m, $s);
+                }
+            }
+        );
+    }
+}

+ 45 - 0
vendor/jdenticon/jdenticon/src/Shapes/ShapePosition.php

@@ -0,0 +1,45 @@
+<?php
+/**
+ * This file is part of Jdenticon for PHP.
+ * https://github.com/dmester/jdenticon-php/
+ * 
+ * Copyright (c) 2018 Daniel Mester Pirttijärvi
+ * 
+ * For full license information, please see the LICENSE file that was 
+ * distributed with this source code.
+ */
+
+namespace Jdenticon\Shapes;
+
+/**
+ * Specifies in which cell a shape will be rendered.
+ */
+class ShapePosition
+{
+    /**
+     * The horizontal cell index measured left to right.
+     *
+     * @var int
+     */
+    public $x;
+    
+    /**
+     * The vertical cell index measured from the top.
+     *
+     * @var int
+     */
+    public $y;
+
+    /**
+     * Creates a new ShapePosition instance.
+     *
+     * @param int $x The x-coordinate of the containing cell.
+     * @param int $y The y-coordinate of the containing cell.
+     */
+    public function __construct($x, $y)
+    {
+        $this->x = $x;
+        $this->y = $y;
+    }
+}
+

Unele fișiere nu au fost afișate deoarece prea multe fișiere au fost modificate în acest diff