Skip to content

Commit

Permalink
Merge pull request #6 from sowrensen/feat/multiple-gradient
Browse files Browse the repository at this point in the history
Multiple gradient support
  • Loading branch information
sowrensen authored Feb 28, 2023
2 parents e4d6a01 + e04f03d commit 79a4e3e
Show file tree
Hide file tree
Showing 17 changed files with 417 additions and 116 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,17 @@

All notable changes to `svg-avatar-generator` will be documented in this file.

## 1.1.0

- Support for setting multiple sets of gradient colors.
- Random gradient generation from defined presets in config.
- `setGradientColors()` method on `SvgAvatarGenerator` class now accepts multiple integer/array arguments.
- Option to set gradient stops via `setGradientStops()` method.
- New `$gradientSet` attribute—with related getters and setters—which holds the randomly picked gradient set.
- Use blade templates to create SVG instead of PHP heredoc.
- New `render()` method on `Shape` enum class.
- Moved some helper methods to `Tool` trait.

## 1.0.0

- Initial release
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ Well, this package has some subtle advantages over available packages, here's a
- [x] Unlike some other available options, doesn't require heavy-weight image processing libraries like **Intervention**.
🧺
- [x] Doesn't have any binary dependency, so nothing needs to be installed on server. 🗃️
- [x] Support for gradient background. 🦜
- [x] Supports gradient background. 🦜
- [x] Supports random gradients based on defined presets in config. 🦚
- [x] Ability to customize initials. ✍🏼

## Requirements
Expand Down Expand Up @@ -83,8 +84,13 @@ Svg::for('John Doe')
->setSize(64)
->setFontSize(40)
->setFontWeight(FontWeight::SEMIBOLD)
->setForeground('#E6C6A3')
->setGradientColors('#3A1C71', '#FDBB2D')
->setForeground('#FFFFFF')
->setGradientColors( // set of 3 different gradients
['#4158D0', '#C850C0', '#FFCC70'],
['#00DBDE', '#FC00FF'],
['#FF9A8B', '#FF6A88', '#FF99AC']
)
->setGradientStops(0, .5, 1)
->setGradientRotation(120)
->render();
```
Expand All @@ -102,7 +108,7 @@ You can define the second initial using studly case. For example,

## Sample Output

<img src="https://user-images.githubusercontent.com/13097375/219583859-b9b4fcad-2dff-424c-a819-6a39cd5439ca.png" height="128"/>
<img src="https://user-images.githubusercontent.com/13097375/221879852-b8283a4a-f3ff-42a9-b37a-07cbc9bd0afe.png" height="128"/>


## Testing
Expand Down
40 changes: 36 additions & 4 deletions config/svg-avatar.php
Original file line number Diff line number Diff line change
Expand Up @@ -84,16 +84,48 @@
| Gradient Colors
|--------------------------------------------------------------------------
|
| Who doesn't admire a nice gradient for background color? For now,
| mixture of two colors are supported. You can provide same color
| to achieve a flat background.
| Who doesn't admire a nice gradient for background color? You can set
| a single gradient color or multiple sets of gradients to achieve
| more dynamic effect.
|
| Type: array<string>
| A fixed single gradient can be set by configuring list of hex colors.
| For example, setting ['#FF0000', '#00FF00', '#0000FF'] will make a
| gradient consisting red/green/blue color always.
|
| To set multiple gradients, you have to configure sets of colors. For
| example, setting [['#FF0000', '#00FF00'], '#0000FF'] will randomly
| generate a gradient of either red/green or blue background.
|
| You can provide same color to achieve a flat background.
|
| NOTE: Number of colors in a set—regardless of single multiple set—must
| be consistent with gradient offsets.
|
| Type: array<array|string>
| Default: ['#3A1C71', '#FDBB2D']
|
*/
'gradient_colors' => ['#3A1C71', '#FDBB2D'],

/*
|--------------------------------------------------------------------------
| Gradient Stops
|--------------------------------------------------------------------------
|
| Specify the stopping positions of gradient colors. Number of colors
| in a set—regardless of single multiple set—must be consistent
| with number of gradient stops. If you set more colors than
| stops, the extra colors will be omitted, and if you set
| fewer colors then stops, the last color in the set
| will be repeated.
|
| Type: array<int|float>
| Default: [0, 1]
| Allowed: 0 to 1
|
*/
'gradient_stops' => [0, 1],

/*
|--------------------------------------------------------------------------
| Gradient Rotation
Expand Down
1 change: 1 addition & 0 deletions resources/views/elements/circle.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<circle cx='50' cy='50' r='50' fill='url(#{{ $gradientId }})'/>
12 changes: 12 additions & 0 deletions resources/views/elements/linear-gradient.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
@php
/**
* @var Sowren\SvgAvatarGenerator\SvgAvatarGenerator $generator
* @var string $gradientId
*/
@endphp

<linearGradient id="{{ $gradientId }}" gradientTransform="rotate({{ $generator->getGradientRotation() }})">
@foreach($generator->getGradientSet() as $set)
<stop offset="{{ $set['offset'] }}" stop-color="{{ $set['color'] }}"/>
@endforeach
</linearGradient>
1 change: 1 addition & 0 deletions resources/views/elements/rectangle.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<rect width='100%' height='100%' fill='url(#{{ $gradientId }})'/>
29 changes: 29 additions & 0 deletions resources/views/elements/svg.blade.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
@php
/**
* @var Sowren\SvgAvatarGenerator\SvgAvatarGenerator $generator
* @var string $gradientId
*/
@endphp

<svg
width="{{ $generator->getSize() }}"
height="{{ $generator->getSize() }}"
viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<defs>
@include('svg::elements.linear-gradient')
</defs>
@include($generator->getShape()->render())
<text
x="50%" y="50%" style="line-height: 1;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;"
alignment-baseline="middle" text-anchor="middle"
font-size="{{ $generator->getFontSize() }}"
font-weight="{{ $generator->getFontWeight()->value }}"
dy=".1em" dominant-baseline="middle"
fill="{{ $generator->getForeground() }}"
>
{{ $generator->getInitials() }}
</text>
</svg>
97 changes: 97 additions & 0 deletions src/Concerns/Tool.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?php

namespace Sowren\SvgAvatarGenerator\Concerns;

use Arr;
use Exception;
use Sowren\SvgAvatarGenerator\Exceptions\MissingTextException;
use Str;

trait Tool
{
/**
* Extract initials from given text/name. If only one word is given,
* it will look for second capital character in the word, else
* the consecutive second character will be taken.
*
* @throws MissingTextException
*/
protected function extractInitials(): void
{
$name = $this->text;

if (Str::contains($name, ' ')) {
// If name has more than one part then break each part upto each space
$parts = Str::of($name)->explode(' ');
} else {
// If name has only one part then try to find out if there are
// any uppercase letters in the string. Then break the string
// upto each uppercase letters, this allows to pass names in
// studly case, e.g. 'SowrenSen'. If no uppercase letter is
// found, $parts will have only one item in the array.
$parts = Str::of($name)->kebab()->replace('-', ' ')->explode(' ');
}

try {
$firstInitial = $parts->first()[0];

// If only one part is found, take the second letter as second
// initial, else take the first letter of the last part.
$secondInitial = ($parts->count() === 1) ? $parts->first()[1] : $parts->last()[0];
} catch (Exception) {
throw MissingTextException::create();
}

$this->setInitials(strtoupper($firstInitial.$secondInitial));
}

/**
* Creates sets of colors and offsets to form the gradients.
*/
public function zip(): array
{
$colors = $this->getGradientColors();
$offsets = $this->getGradientStops();

$hasMultipleSet = count(Arr::where($colors, fn ($color) => is_array($color))) > 0;

return $hasMultipleSet
? $this->zipMultiple($colors, $offsets)
: $this->zipOne($colors, $offsets);
}

/**
* Zip one set of colors to offsets.
*/
private function zipOne($colors, $offsets): array
{
$set = [];

foreach ($offsets as $key => $offset) {
$set[] = ['color' => $colors[$key] ?? $colors[count($colors) - 1], 'offset' => $offset];
}

return [$set];
}

/**
* Zip multiple sets of colors to offsets.
*/
private function zipMultiple($colors, $offsets): array
{
$gradientSets = [];
foreach ($colors as $gradColors) {
$set = [];
foreach ($offsets as $key => $offset) {
$color = is_array($gradColors)
? $gradColors[$key] ?? $gradColors[count($gradColors) - 1]
: $gradColors;

$set[] = ['color' => $color, 'offset' => $offset];
}
$gradientSets[] = $set;
}

return $gradientSets;
}
}
11 changes: 11 additions & 0 deletions src/Enums/Shape.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,15 @@ enum Shape
{
case CIRCLE;
case RECTANGLE;

/**
* Render corresponding XML element.
*/
public function render(): string
{
return match ($this) {
self::CIRCLE => 'svg::elements.circle',
self::RECTANGLE => 'svg::elements.rectangle',
};
}
}
13 changes: 13 additions & 0 deletions src/Exceptions/InvalidGradientStopException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<?php

namespace Sowren\SvgAvatarGenerator\Exceptions;

class InvalidGradientStopException extends \Exception
{
public static function create(array $offsets): InvalidGradientStopException
{
$values = implode(',', $offsets);

return new self("Invalid value [{$values}] for gradient stop is provided. Gradient stops must be between 0 and 1.");
}
}
2 changes: 2 additions & 0 deletions src/Http/Controllers/SvgController.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Illuminate\Http\Response;
use Sowren\SvgAvatarGenerator\Exceptions\InvalidFontSizeException;
use Sowren\SvgAvatarGenerator\Exceptions\InvalidGradientRotationException;
use Sowren\SvgAvatarGenerator\Exceptions\InvalidGradientStopException;
use Sowren\SvgAvatarGenerator\Exceptions\InvalidSvgSizeException;
use Sowren\SvgAvatarGenerator\Exceptions\MissingTextException;
use Sowren\SvgAvatarGenerator\SvgAvatarGenerator;
Expand All @@ -18,6 +19,7 @@ class SvgController
* @throws MissingTextException
* @throws InvalidSvgSizeException
* @throws InvalidFontSizeException
* @throws InvalidGradientStopException
* @throws InvalidGradientRotationException
*/
public function __invoke(Request $request): Response
Expand Down
71 changes: 7 additions & 64 deletions src/Svg.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

namespace Sowren\SvgAvatarGenerator;

use Sowren\SvgAvatarGenerator\Enums\Shape;
use Str;
use View;

class Svg
{
Expand All @@ -20,74 +20,17 @@ public function __construct(

public function __toString(): string
{
return $this->svgElement();
return $this->render();
}

/**
* Build the output SVG.
*/
protected function svgElement(): string
protected function render(): string
{
try {
$svg = <<<SVG
<svg
width="{$this->generator->getSize()}"
height="{$this->generator->getSize()}"
viewBox="0 0 100 100" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
>
<defs>
<linearGradient id="{$this->gradientId}" gradientTransform="rotate({$this->generator->getGradientRotation()})">
<stop offset="0%" stop-color="{$this->generator->getGradientColors()[0]}"/>
<stop offset="100%" stop-color="{$this->generator->getGradientColors()[1]}"/>
</linearGradient>
</defs>
{$this->getElement($this->generator->getShape())}
<text
x="50%" y="50%" style="line-height: 1;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', sans-serif;"
alignment-baseline="middle" text-anchor="middle"
font-size="{$this->generator->getFontSize()}"
font-weight="{$this->generator->getFontWeight()->value}"
dy=".1em" dominant-baseline="middle"
fill="{$this->generator->getForeground()}"
>
{$this->generator->getInitials()}
</text>
</svg>
SVG;
} catch (\Exception $e) {
logger($e->getMessage());
$svg = '<svg></svg>';
}

return $svg;
}

/**
* Find SVG element of the given shape.
*/
protected function getElement(Shape $shape): string
{
return match ($shape) {
Shape::RECTANGLE => $this->rectangleElement(),
default => $this->circleElement(),
};
}

/**
* The circle element of SVG markup.
*/
protected function circleElement(): string
{
return "<circle cx='50' cy='50' r='50' fill='url(#{$this->gradientId})'></circle>";
}

/**
* The rectangle element of SVG markup.
*/
protected function rectangleElement(): string
{
return "<rect width='100%' height='100%' fill='url(#{$this->gradientId})'/>";
return View::make('svg::elements.svg', [
'generator' => $this->generator,
'gradientId' => $this->gradientId,
])->render();
}
}
Loading

0 comments on commit 79a4e3e

Please sign in to comment.