1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
namespace EaselDrawing\Drawers; |
4
|
|
|
|
5
|
|
|
use EaselDrawing\Align; |
6
|
|
|
use EaselDrawing\Canvas; |
7
|
|
|
use EaselDrawing\Color; |
8
|
|
|
use EaselDrawing\DrawerInterface; |
9
|
|
|
use EaselDrawing\Elements\Image; |
10
|
|
|
use EaselDrawing\Elements\Label; |
11
|
|
|
use EaselDrawing\Elements\Line; |
12
|
|
|
use EaselDrawing\Elements\Rectangle; |
13
|
|
|
use EaselDrawing\Orientation; |
14
|
|
|
use EaselDrawing\TextBackground; |
15
|
|
|
|
16
|
|
|
class GDDrawer implements DrawerInterface |
17
|
|
|
{ |
18
|
|
|
/** @var resource Image created with GD */ |
19
|
|
|
private $im; |
20
|
|
|
|
21
|
|
|
public function create(Canvas $canvas): string |
22
|
|
|
{ |
23
|
|
|
$this->im = imagecreatetruecolor($canvas->getWidth(), $canvas->getHeight()); |
24
|
|
|
$backgroundColor = $this->colorAlloc($canvas->getBackground()); |
25
|
|
|
imagefilledrectangle($this->im, 0, 0, $canvas->getWidth(), $canvas->getHeight(), $backgroundColor); |
26
|
|
|
|
27
|
|
|
foreach ($canvas->getElements() as $element) { |
28
|
|
|
if ($element instanceof Rectangle) { |
29
|
|
|
$this->drawRectangle($element); |
30
|
|
|
continue; |
31
|
|
|
} |
32
|
|
|
if ($element instanceof Line) { |
33
|
|
|
$this->drawLine($element); |
34
|
|
|
continue; |
35
|
|
|
} |
36
|
|
|
if ($element instanceof Label) { |
37
|
|
|
$this->drawLabel($element); |
38
|
|
|
continue; |
39
|
|
|
} |
40
|
|
|
if ($element instanceof Image) { |
41
|
|
|
$this->drawImage($element); |
42
|
|
|
continue; |
43
|
|
|
} |
44
|
|
|
throw new \LogicException("Don't know what to do with element type " . get_class($element)); |
45
|
|
|
} |
46
|
|
|
if ($canvas->getOrientation()->getValue() === Orientation::PORTRAIT) { |
47
|
|
|
$this->drawPortrait($backgroundColor); |
48
|
|
|
} |
49
|
|
|
if ($canvas->isGrayScale()) { |
50
|
|
|
imagefilter($this->im, IMG_FILTER_GRAYSCALE); |
51
|
|
|
} |
52
|
|
|
$filename = tempnam(sys_get_temp_dir(), ''); |
53
|
|
|
imagepng($this->im, $filename); |
54
|
|
|
return $filename; |
55
|
|
|
} |
56
|
|
|
|
57
|
|
|
public function drawRectangle(Rectangle $rectangle) |
58
|
|
|
{ |
59
|
|
|
$color = $this->colorAlloc($rectangle->getColor()); |
60
|
|
|
imagesetthickness($this->im, $rectangle->getThickness()); |
61
|
|
|
$x1 = $rectangle->getX(); |
62
|
|
|
$x2 = $rectangle->getWidth() + $rectangle->getX(); |
63
|
|
|
$y1 = $rectangle->getY(); |
64
|
|
|
$y2 = $rectangle->getHeight() + $rectangle->getY(); |
65
|
|
|
imageline($this->im, $x1, $y1, $x2, $y1, $color); // top |
66
|
|
|
imageline($this->im, $x2, $y1, $x2, $y2, $color); // left |
67
|
|
|
imageline($this->im, $x1, $y2, $x2, $y2, $color); // top |
68
|
|
|
imageline($this->im, $x1, $y1, $x1, $y2, $color); // right |
69
|
|
|
} |
70
|
|
|
|
71
|
|
|
public function drawLine(Line $line) |
72
|
|
|
{ |
73
|
|
|
$color = $this->colorAlloc($line->getColor()); |
74
|
|
|
imagesetthickness($this->im, $line->getThickness()); |
75
|
|
|
imageline($this->im, $line->getX(), $line->getY(), $line->getX2(), $line->getY2(), $color); |
76
|
|
|
} |
77
|
|
|
|
78
|
|
|
public function drawLabel(Label $label) |
79
|
|
|
{ |
80
|
|
|
$font = $label->getFont(); |
81
|
|
|
$fontFile = $font->getFile(); |
82
|
|
|
$fontSize = $label->getSize(); |
83
|
|
|
$width = $label->getWidth(); |
84
|
|
|
$height = $label->getHeight(); |
85
|
|
|
if (intval(round($fontSize, 0)) < 4) { |
86
|
|
|
throw new \RuntimeException('The font size is too small to fit'); |
87
|
|
|
} |
88
|
|
|
|
89
|
|
|
// check if the content fits in the width and height |
90
|
|
|
$box = imagettfbbox($fontSize, 0, $fontFile, $label->getContent()); |
91
|
|
|
if ($box[2] > $width or abs($box[5]) > $height) { |
92
|
|
|
$reducedLabel = clone $label; |
93
|
|
|
$reducedLabel->setSize($reducedLabel->getSize() - 1); |
94
|
|
|
$this->drawLabel($reducedLabel); |
95
|
|
|
return; |
96
|
|
|
} |
97
|
|
|
|
98
|
|
|
// the text fit into the area |
99
|
|
|
$x = $label->getX(); |
100
|
|
|
$y = $label->getY() + ceil(($height - $box[5]) / 2); |
101
|
|
|
$align = $label->getAlign()->getValue(); |
102
|
|
View Code Duplication |
if ($align === Align::RIGHT) { |
|
|
|
|
103
|
|
|
$x = $label->getX() + $label->getWidth() - $box[2]; |
104
|
|
|
} |
105
|
|
View Code Duplication |
if ($align === Align::CENTER) { // center |
|
|
|
|
106
|
|
|
$x = $label->getX() + ceil(($label->getWidth() - $box[2]) / 2); |
107
|
|
|
} |
108
|
|
|
|
109
|
|
|
// draw text background |
110
|
|
|
$textBackground = $label->getTextBackground(); |
111
|
|
|
if ($textBackground->getValue() === TextBackground::BOX) { |
112
|
|
|
imagefilledrectangle( |
113
|
|
|
$this->im, |
114
|
|
|
$label->getX(), |
115
|
|
|
$label->getY(), |
116
|
|
|
$label->getX() + $label->getWidth(), |
117
|
|
|
$label->getY() + $label->getHeight(), |
118
|
|
|
$this->colorAlloc($textBackground->getColor()) |
119
|
|
|
); |
120
|
|
|
} |
121
|
|
|
if ($textBackground->getValue() === TextBackground::FIT) { |
122
|
|
|
imagefilledrectangle( |
123
|
|
|
$this->im, |
124
|
|
|
$x, |
|
|
|
|
125
|
|
|
$y, |
|
|
|
|
126
|
|
|
$x + $box[2], |
|
|
|
|
127
|
|
|
$y + $box[5], |
|
|
|
|
128
|
|
|
$this->colorAlloc($textBackground->getColor()) |
129
|
|
|
); |
130
|
|
|
} |
131
|
|
|
|
132
|
|
|
// draw text |
133
|
|
|
$color = $this->colorAlloc($label->getColor()); |
134
|
|
|
imagettftext($this->im, $fontSize, 0, $x, $y, $color, $fontFile, $label->getContent()); |
|
|
|
|
135
|
|
|
} |
136
|
|
|
|
137
|
|
|
public function drawPortrait(int $allocatedBackground) |
138
|
|
|
{ |
139
|
|
|
$this->im = imagerotate($this->im, 90, $allocatedBackground); |
140
|
|
|
} |
141
|
|
|
|
142
|
|
|
public function drawImage(Image $image) |
143
|
|
|
{ |
144
|
|
|
$filename = $image->getFilename(); |
145
|
|
|
$destWidth = $image->getWidth(); |
146
|
|
|
$destHeight = $image->getHeight(); |
147
|
|
|
$destX = $image->getX(); |
148
|
|
|
$destY = $image->getY(); |
149
|
|
|
|
150
|
|
|
$ix = $this->imageCreateFromFile($filename); |
151
|
|
|
|
152
|
|
|
$srcWidth = imagesx($ix); |
153
|
|
|
$srcHeight = imagesy($ix); |
154
|
|
|
$destRatio = $destWidth / $destHeight; |
155
|
|
|
$sourceRadio = $srcWidth / $srcHeight; |
156
|
|
|
if ($sourceRadio > $destRatio) { |
157
|
|
|
$finalWidth = $destWidth; |
158
|
|
|
$padX = 0; |
159
|
|
|
$finalHeight = round($destWidth * $srcHeight / $srcWidth, 0); |
160
|
|
|
$padY = floor(($destHeight - $finalHeight) / 2); |
161
|
|
|
} else { |
162
|
|
|
$finalHeight = $destHeight; |
163
|
|
|
$padY = 0; |
164
|
|
|
$finalWidth = round($destHeight * $srcWidth / $srcHeight, 0); |
165
|
|
|
$padX = floor(($destWidth - $finalWidth) / 2); |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
// draw background if exists |
169
|
|
|
if ($image->hasBackground()) { |
170
|
|
|
$color = $this->colorAlloc($image->getBackground()); |
171
|
|
|
imagefilledrectangle($this->im, $destX, $destY, $destX + $destWidth, $destY + $destHeight, $color); |
172
|
|
|
} |
173
|
|
|
|
174
|
|
|
// draw image |
175
|
|
|
$copy = imagecopyresized( |
176
|
|
|
$this->im, |
177
|
|
|
$ix, |
178
|
|
|
$destX + $padX, |
|
|
|
|
179
|
|
|
$destY + $padY, |
|
|
|
|
180
|
|
|
0, |
181
|
|
|
0, |
182
|
|
|
$finalWidth, |
|
|
|
|
183
|
|
|
$finalHeight, |
|
|
|
|
184
|
|
|
$srcWidth, |
185
|
|
|
$srcHeight |
186
|
|
|
); |
187
|
|
|
if (! $copy) { |
188
|
|
|
throw new \RuntimeException("Cannot import the image $filename"); |
189
|
|
|
} |
190
|
|
|
} |
191
|
|
|
|
192
|
|
|
/** |
193
|
|
|
* Open an image as a GD resource |
194
|
|
|
* |
195
|
|
|
* @param string $filename |
196
|
|
|
* @return resource |
197
|
|
|
*/ |
198
|
|
|
protected function imageCreateFromFile(string $filename) |
199
|
|
|
{ |
200
|
|
|
if (! file_exists($filename) || ! is_readable($filename)) { |
201
|
|
|
throw new \InvalidArgumentException("The file $filename does not exists or is not readable"); |
202
|
|
|
} |
203
|
|
|
$filetype = (new \finfo())->file($filename, FILEINFO_MIME_TYPE); |
204
|
|
|
if ('image/jpeg' == $filetype) { |
205
|
|
|
return imagecreatefromjpeg($filename); |
206
|
|
|
} |
207
|
|
|
if ('image/png' == $filetype) { |
208
|
|
|
return imagecreatefrompng($filename); |
209
|
|
|
} |
210
|
|
|
if ('image/gif' == $filetype) { |
211
|
|
|
return imagecreatefromgif($filename); |
212
|
|
|
} |
213
|
|
|
if ('image/bmp' == $filetype) { |
214
|
|
|
return imagecreatefromwbmp($filename); |
215
|
|
|
} |
216
|
|
|
throw new \InvalidArgumentException("The file $filename is not a valid file type"); |
217
|
|
|
} |
218
|
|
|
|
219
|
|
|
protected function colorAlloc(Color $color): int |
220
|
|
|
{ |
221
|
|
|
return imagecolorallocate($this->im, ...$color->getRGB()); |
|
|
|
|
222
|
|
|
} |
223
|
|
|
} |
224
|
|
|
|
Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.
You can also find more detailed suggestions in the “Code” section of your repository.