1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace floor12\imagenator; |
4
|
|
|
|
5
|
|
|
use floor12\imagenator\exception\FontNotFoundException; |
6
|
|
|
use floor12\imagenator\exception\ImageNotFoundException; |
7
|
|
|
use floor12\imagenator\exception\ImageNotLoadedException; |
8
|
|
|
use floor12\imagenator\exception\InvalidFontSizeException; |
9
|
|
|
use floor12\imagenator\exception\InvalidHexColorException; |
10
|
|
|
use floor12\imagenator\exception\InvalidPositionValueException; |
11
|
|
|
use floor12\imagenator\exception\InvalidRowHeightException; |
12
|
|
|
|
13
|
|
|
class Imagenator |
14
|
|
|
{ |
15
|
|
|
const DEFAULT_IMAGE = __DIR__ . '/../assets/default.png'; |
16
|
|
|
/** |
17
|
|
|
* @var string Path to TTF font file |
18
|
|
|
*/ |
19
|
|
|
protected $font = __DIR__ . '/../assets/Rubik.ttf'; |
20
|
|
|
/** |
21
|
|
|
* @var string |
22
|
|
|
*/ |
23
|
|
|
protected $text = "Dont forget to put some text here :-)"; |
24
|
|
|
/** |
25
|
|
|
* @var resource |
26
|
|
|
*/ |
27
|
|
|
protected $image; |
28
|
|
|
/** |
29
|
|
|
* @var int |
30
|
|
|
*/ |
31
|
|
|
protected $imageWidth; |
32
|
|
|
/** |
33
|
|
|
* @var int |
34
|
|
|
*/ |
35
|
|
|
protected $imageHeight; |
36
|
|
|
/** |
37
|
|
|
* @var int |
38
|
|
|
*/ |
39
|
|
|
protected $rowHeight = 8; |
40
|
|
|
/** |
41
|
|
|
* @var int |
42
|
|
|
*/ |
43
|
|
|
protected $marginTopInPercents = 50; |
44
|
|
|
/** |
45
|
|
|
* @var int |
46
|
|
|
*/ |
47
|
|
|
protected $paddingLeftRightInPercents = 5; |
48
|
|
|
/** |
49
|
|
|
* @var int Default color is white |
50
|
|
|
*/ |
51
|
|
|
private $textColor = '16777215'; |
52
|
|
|
/** |
53
|
|
|
* @var int |
54
|
|
|
*/ |
55
|
|
|
private $fontSizeInPercents = 5; |
56
|
|
|
/** |
57
|
|
|
* @var array |
58
|
|
|
*/ |
59
|
|
|
private $rows = []; |
60
|
|
|
/** |
61
|
|
|
* @var float|int |
62
|
|
|
*/ |
63
|
|
|
private $positionX; |
64
|
|
|
/** |
65
|
|
|
* @var float|int |
66
|
|
|
*/ |
67
|
|
|
private $positionStartY; |
68
|
|
|
/** |
69
|
|
|
* @var float|int |
70
|
|
|
*/ |
71
|
|
|
private $rowHeightInPx; |
72
|
|
|
/** |
73
|
|
|
* @var float|int |
74
|
|
|
*/ |
75
|
|
|
private $fontSizeInPx; |
76
|
|
|
|
77
|
|
|
/** |
78
|
|
|
* Imagenator constructor. |
79
|
|
|
* @param string $backgroundImagePath |
80
|
|
|
* @throws ImageNotFoundException |
81
|
|
|
*/ |
82
|
|
|
public function __construct(string $backgroundImagePath = self::DEFAULT_IMAGE) |
83
|
|
|
{ |
84
|
|
|
$this->loadImage($backgroundImagePath); |
85
|
|
|
} |
86
|
|
|
|
87
|
|
|
/** |
88
|
|
|
* @param string $backgroundImagePath |
89
|
|
|
* @throws ImageNotFoundException |
90
|
|
|
* @throws ImageNotLoadedException |
91
|
|
|
*/ |
92
|
|
|
protected function loadImage(string $backgroundImagePath) |
93
|
|
|
{ |
94
|
|
|
if (!file_exists($backgroundImagePath)) |
95
|
|
|
throw new ImageNotFoundException(); |
96
|
|
|
$this->image = imagecreatefrompng($backgroundImagePath); |
|
|
|
|
97
|
|
|
if ($this->image == null) |
98
|
|
|
throw new ImageNotLoadedException(); |
99
|
|
|
list($this->imageWidth, $this->imageHeight) = getimagesize($backgroundImagePath); |
100
|
|
|
} |
101
|
|
|
|
102
|
|
|
/** |
103
|
|
|
* @param string $resultImagePath |
104
|
|
|
* @return bool |
105
|
|
|
* @throws FontNotFoundException |
106
|
|
|
*/ |
107
|
|
|
public function generate(string $resultImagePath): bool |
108
|
|
|
{ |
109
|
|
|
$this->calculateParams(); |
110
|
|
|
$this->prepareRows(); |
111
|
|
|
$this->putRowsToImage(); |
112
|
|
|
return $this->saveImage($resultImagePath); |
113
|
|
|
} |
114
|
|
|
|
115
|
|
|
/** |
116
|
|
|
* @param string $pathToSave |
117
|
|
|
* @return bool |
118
|
|
|
*/ |
119
|
|
|
protected function saveImage(string $pathToSave): bool |
120
|
|
|
{ |
121
|
|
|
return imagepng($this->image, $pathToSave) && imagedestroy($this->image); |
122
|
|
|
} |
123
|
|
|
|
124
|
|
|
protected function calculateParams() |
125
|
|
|
{ |
126
|
|
|
$this->positionStartY = intval($this->imageHeight / 100 * $this->marginTopInPercents); |
127
|
|
|
$this->rowHeightInPx = intval($this->imageHeight / 100 * $this->rowHeight); |
128
|
|
|
$this->fontSizeInPx = intval($this->imageHeight / 100 * $this->fontSizeInPercents); |
129
|
|
|
$this->positionX = intval($this->imageWidth / 100 * $this->paddingLeftRightInPercents); |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
/** |
133
|
|
|
* @throws FontNotFoundException |
134
|
|
|
*/ |
135
|
|
|
protected function prepareRows(): void |
136
|
|
|
{ |
137
|
|
|
if (!file_exists($this->font)) |
138
|
|
|
throw new FontNotFoundException(); |
139
|
|
|
|
140
|
|
|
$wordsArray = explode(' ', $this->text); |
141
|
|
|
$validRowWidth = $this->imageWidth - $this->positionX * 2; |
142
|
|
|
$currentRow = 0; |
143
|
|
|
|
144
|
|
|
foreach ($wordsArray as $word) { |
145
|
|
|
$testRow = isset($this->rows[$currentRow]) ? array_merge($this->rows[$currentRow], [$word]) : [$word]; |
146
|
|
|
$imageBox = imagettfbbox($this->fontSizeInPx, 0, $this->font, implode('', $testRow)); |
147
|
|
|
if ($imageBox[2] >= $validRowWidth) { |
148
|
|
|
$lastWord = end($this->rows[$currentRow]); |
149
|
|
|
if (mb_strlen($lastWord) <= 2) { |
150
|
|
|
$this->rows[$currentRow + 1][] = array_pop($this->rows[$currentRow]); |
151
|
|
|
} |
152
|
|
|
$this->rows[++$currentRow][] = $word; |
153
|
|
|
} else { |
154
|
|
|
$this->rows[$currentRow][] = $word; |
155
|
|
|
} |
156
|
|
|
} |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
/** |
160
|
|
|
* |
161
|
|
|
*/ |
162
|
|
|
protected function putRowsToImage() |
163
|
|
|
{ |
164
|
|
|
foreach ($this->rows as $rowNumber => $row) { |
165
|
|
|
$string = implode(' ', $row); |
166
|
|
|
$positionY = intval($this->positionStartY + ($rowNumber * $this->rowHeightInPx)); |
167
|
|
|
imagettftext($this->image, $this->fontSizeInPx, 0, $this->positionX, $positionY, $this->textColor, $this->font, $string); |
|
|
|
|
168
|
|
|
} |
169
|
|
|
} |
170
|
|
|
|
171
|
|
|
/** |
172
|
|
|
* @param string $text |
173
|
|
|
* @return self |
174
|
|
|
*/ |
175
|
|
|
public |
176
|
|
|
function setText(string $text): self |
177
|
|
|
{ |
178
|
|
|
$this->text = $text; |
179
|
|
|
return $this; |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
/** |
183
|
|
|
* @param string $font |
184
|
|
|
* @return self |
185
|
|
|
*/ |
186
|
|
|
public |
187
|
|
|
function setFont(string $font): self |
188
|
|
|
{ |
189
|
|
|
$this->font = $font; |
190
|
|
|
return $this; |
191
|
|
|
} |
192
|
|
|
|
193
|
|
|
/** |
194
|
|
|
* @param string $colorInHex |
195
|
|
|
* @return self |
196
|
|
|
* @throws InvalidHexColorException |
197
|
|
|
*/ |
198
|
|
|
public |
199
|
|
|
function setColor(string $colorInHex): self |
200
|
|
|
{ |
201
|
|
|
$colorInHex = str_replace('#', '', $colorInHex); |
202
|
|
|
|
203
|
|
|
if (!ctype_xdigit($colorInHex) || strlen($colorInHex) !== 6) |
204
|
|
|
throw new InvalidHexColorException(); |
205
|
|
|
|
206
|
|
|
$red = intval(hexdec(substr($colorInHex, 0, 2))); |
207
|
|
|
$green = intval(hexdec(substr($colorInHex, 2, 2))); |
208
|
|
|
$blue = intval(hexdec(substr($colorInHex, 4, 2))); |
209
|
|
|
$this->textColor = imagecolorallocate($this->image, $red, $green, $blue); |
210
|
|
|
return $this; |
211
|
|
|
} |
212
|
|
|
|
213
|
|
|
/** |
214
|
|
|
* @param int $size |
215
|
|
|
* @return Imagenator |
216
|
|
|
* @throws InvalidFontSizeException |
217
|
|
|
*/ |
218
|
|
|
public |
219
|
|
|
function setFontSize(int $size) |
220
|
|
|
{ |
221
|
|
|
if ($size < 1 || $size > 20) |
222
|
|
|
throw new InvalidFontSizeException(); |
223
|
|
|
$this->fontSizeInPercents = $size; |
224
|
|
|
return $this; |
225
|
|
|
} |
226
|
|
|
|
227
|
|
|
/** |
228
|
|
|
* @param int $percent |
229
|
|
|
* @return Imagenator |
230
|
|
|
* @throws InvalidPositionValueException |
231
|
|
|
*/ |
232
|
|
|
public |
233
|
|
|
function setPadding(int $percent) |
234
|
|
|
{ |
235
|
|
|
if ($percent < 1 || $percent > 100) |
236
|
|
|
throw new InvalidPositionValueException(); |
237
|
|
|
$this->paddingLeftRightInPercents = $percent; |
238
|
|
|
return $this; |
239
|
|
|
} |
240
|
|
|
|
241
|
|
|
/** |
242
|
|
|
* @param int $percent |
243
|
|
|
* @return Imagenator |
244
|
|
|
* @throws InvalidPositionValueException |
245
|
|
|
*/ |
246
|
|
|
public function setMarginTopInPercents(int $percent) |
247
|
|
|
{ |
248
|
|
|
if ($percent < 1 || $percent > 100) |
249
|
|
|
throw new InvalidPositionValueException(); |
250
|
|
|
$this->marginTopInPercents = $percent; |
251
|
|
|
return $this; |
252
|
|
|
} |
253
|
|
|
|
254
|
|
|
/** |
255
|
|
|
* @param int $percent |
256
|
|
|
* @return Imagenator |
257
|
|
|
* @throws InvalidRowHeightException |
258
|
|
|
*/ |
259
|
|
|
public |
260
|
|
|
function setRowHeight(int $percent) |
261
|
|
|
{ |
262
|
|
|
if ($percent < 1 || $percent > 30) |
263
|
|
|
throw new InvalidRowHeightException(); |
264
|
|
|
$this->rowHeight = $percent; |
265
|
|
|
return $this; |
266
|
|
|
} |
267
|
|
|
} |
268
|
|
|
|
Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.
For example, imagine you have a variable
$accountId
that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to theid
property of an instance of theAccount
class. This class holds a proper account, so the id value must no longer be false.Either this assignment is in error or a type check should be added for that assignment.