|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
namespace Spatie\PdfToImage; |
|
4
|
|
|
|
|
5
|
|
|
use Imagick; |
|
6
|
|
|
use Spatie\PdfToImage\Exceptions\InvalidFormat; |
|
7
|
|
|
use Spatie\PdfToImage\Exceptions\PdfDoesNotExist; |
|
8
|
|
|
use Spatie\PdfToImage\Exceptions\PageDoesNotExist; |
|
9
|
|
|
use Spatie\PdfToImage\Exceptions\InvalidLayerMethod; |
|
10
|
|
|
use Spatie\PdfToImage\Exceptions\TempFileDoesNotExist; |
|
11
|
|
|
use Spatie\PdfToImage\Exceptions\TempPathNotWritable; |
|
12
|
|
|
use Spatie\PdfToImage\Exceptions\RemoteFileFetchFailed; |
|
13
|
|
|
|
|
14
|
|
|
class Pdf |
|
15
|
|
|
{ |
|
16
|
|
|
protected $pdfFile; |
|
17
|
|
|
|
|
18
|
|
|
protected $resolution = 144; |
|
19
|
|
|
|
|
20
|
|
|
protected $outputFormat = 'jpg'; |
|
21
|
|
|
|
|
22
|
|
|
protected $page = 1; |
|
23
|
|
|
|
|
24
|
|
|
public $imagick; |
|
25
|
|
|
|
|
26
|
|
|
protected $numberOfPages; |
|
27
|
|
|
|
|
28
|
|
|
protected $validOutputFormats = ['jpg', 'jpeg', 'png']; |
|
29
|
|
|
|
|
30
|
|
|
protected $layerMethod = Imagick::LAYERMETHOD_FLATTEN; |
|
31
|
|
|
|
|
32
|
|
|
protected $colorspace; |
|
33
|
|
|
|
|
34
|
|
|
protected $compressionQuality; |
|
35
|
|
|
|
|
36
|
|
|
protected $isRemoteFile = false; |
|
37
|
|
|
|
|
38
|
|
|
/** |
|
39
|
|
|
* @param string $pdfFile The path or url to the pdffile. |
|
40
|
|
|
* |
|
41
|
|
|
* @throws \Spatie\PdfToImage\Exceptions\PdfDoesNotExist |
|
42
|
|
|
*/ |
|
43
|
|
|
public function __construct($pdfFile) |
|
44
|
|
|
{ |
|
45
|
|
|
if (! filter_var($pdfFile, FILTER_VALIDATE_URL) && ! file_exists($pdfFile)) { |
|
46
|
|
|
throw new PdfDoesNotExist(); |
|
47
|
|
|
} |
|
48
|
|
|
|
|
49
|
|
|
if (filter_var($pdfFile, FILTER_VALIDATE_URL)) { |
|
50
|
|
|
$this->pdfFile = $this->fetchRemoteFile($pdfFile); |
|
51
|
|
|
|
|
52
|
|
|
$this->isRemoteFile = true; |
|
53
|
|
|
} else { |
|
54
|
|
|
$this->pdfFile = $pdfFile; |
|
55
|
|
|
} |
|
56
|
|
|
|
|
57
|
|
|
$this->imagick = new Imagick(); |
|
58
|
|
|
|
|
59
|
|
|
$this->imagick->pingImage($this->pdfFile); |
|
60
|
|
|
|
|
61
|
|
|
$this->numberOfPages = $this->imagick->getNumberImages(); |
|
62
|
|
|
} |
|
63
|
|
|
|
|
64
|
|
|
/** |
|
65
|
|
|
* Set the raster resolution. |
|
66
|
|
|
* |
|
67
|
|
|
* @param int $resolution |
|
68
|
|
|
* |
|
69
|
|
|
* @return $this |
|
70
|
|
|
*/ |
|
71
|
|
|
public function setResolution($resolution) |
|
72
|
|
|
{ |
|
73
|
|
|
$this->resolution = $resolution; |
|
74
|
|
|
|
|
75
|
|
|
return $this; |
|
76
|
|
|
} |
|
77
|
|
|
|
|
78
|
|
|
/** |
|
79
|
|
|
* Set the output format. |
|
80
|
|
|
* |
|
81
|
|
|
* @param string $outputFormat |
|
82
|
|
|
* |
|
83
|
|
|
* @return $this |
|
84
|
|
|
* |
|
85
|
|
|
* @throws \Spatie\PdfToImage\Exceptions\InvalidFormat |
|
86
|
|
|
*/ |
|
87
|
|
|
public function setOutputFormat($outputFormat) |
|
88
|
|
|
{ |
|
89
|
|
|
if (! $this->isValidOutputFormat($outputFormat)) { |
|
90
|
|
|
throw new InvalidFormat("Format {$outputFormat} is not supported"); |
|
91
|
|
|
} |
|
92
|
|
|
|
|
93
|
|
|
$this->outputFormat = $outputFormat; |
|
94
|
|
|
|
|
95
|
|
|
return $this; |
|
96
|
|
|
} |
|
97
|
|
|
|
|
98
|
|
|
/** |
|
99
|
|
|
* Get the output format. |
|
100
|
|
|
* |
|
101
|
|
|
* @return string |
|
102
|
|
|
*/ |
|
103
|
|
|
public function getOutputFormat() |
|
104
|
|
|
{ |
|
105
|
|
|
return $this->outputFormat; |
|
106
|
|
|
} |
|
107
|
|
|
|
|
108
|
|
|
/** |
|
109
|
|
|
* Sets the layer method for Imagick::mergeImageLayers() |
|
110
|
|
|
* If int, should correspond to a predefined LAYERMETHOD constant. |
|
111
|
|
|
* If null, Imagick::mergeImageLayers() will not be called. |
|
112
|
|
|
* |
|
113
|
|
|
* @param int|null |
|
114
|
|
|
* |
|
115
|
|
|
* @return $this |
|
116
|
|
|
* |
|
117
|
|
|
* @throws \Spatie\PdfToImage\Exceptions\InvalidLayerMethod |
|
118
|
|
|
* |
|
119
|
|
|
* @see https://secure.php.net/manual/en/imagick.constants.php |
|
120
|
|
|
* @see Pdf::getImageData() |
|
121
|
|
|
*/ |
|
122
|
|
|
public function setLayerMethod($layerMethod) |
|
123
|
|
|
{ |
|
124
|
|
|
if ( |
|
125
|
|
|
is_int($layerMethod) === false && |
|
126
|
|
|
is_null($layerMethod) === false |
|
127
|
|
|
) { |
|
128
|
|
|
throw new InvalidLayerMethod('LayerMethod must be an integer or null'); |
|
129
|
|
|
} |
|
130
|
|
|
|
|
131
|
|
|
$this->layerMethod = $layerMethod; |
|
132
|
|
|
|
|
133
|
|
|
return $this; |
|
134
|
|
|
} |
|
135
|
|
|
|
|
136
|
|
|
/** |
|
137
|
|
|
* Determine if the given format is a valid output format. |
|
138
|
|
|
* |
|
139
|
|
|
* @param $outputFormat |
|
140
|
|
|
* |
|
141
|
|
|
* @return bool |
|
142
|
|
|
*/ |
|
143
|
|
|
public function isValidOutputFormat($outputFormat) |
|
144
|
|
|
{ |
|
145
|
|
|
return in_array($outputFormat, $this->validOutputFormats); |
|
146
|
|
|
} |
|
147
|
|
|
|
|
148
|
|
|
/** |
|
149
|
|
|
* Set the page number that should be rendered. |
|
150
|
|
|
* |
|
151
|
|
|
* @param int $page |
|
152
|
|
|
* |
|
153
|
|
|
* @return $this |
|
154
|
|
|
* |
|
155
|
|
|
* @throws \Spatie\PdfToImage\Exceptions\PageDoesNotExist |
|
156
|
|
|
*/ |
|
157
|
|
|
public function setPage($page) |
|
158
|
|
|
{ |
|
159
|
|
|
if ($page > $this->getNumberOfPages()) { |
|
160
|
|
|
throw new PageDoesNotExist("Page {$page} does not exist"); |
|
161
|
|
|
} |
|
162
|
|
|
|
|
163
|
|
|
$this->page = $page; |
|
164
|
|
|
|
|
165
|
|
|
return $this; |
|
166
|
|
|
} |
|
167
|
|
|
|
|
168
|
|
|
/** |
|
169
|
|
|
* Get the number of pages in the pdf file. |
|
170
|
|
|
* |
|
171
|
|
|
* @return int |
|
172
|
|
|
*/ |
|
173
|
|
|
public function getNumberOfPages() |
|
174
|
|
|
{ |
|
175
|
|
|
return $this->numberOfPages; |
|
176
|
|
|
} |
|
177
|
|
|
|
|
178
|
|
|
/** |
|
179
|
|
|
* Save the image to the given path. |
|
180
|
|
|
* |
|
181
|
|
|
* @param string $pathToImage |
|
182
|
|
|
* @param bool $clear |
|
183
|
|
|
* |
|
184
|
|
|
* @return bool |
|
185
|
|
|
*/ |
|
186
|
|
|
public function saveImage($pathToImage, $clear = true) |
|
187
|
|
|
{ |
|
188
|
|
|
if (is_dir($pathToImage)) { |
|
189
|
|
|
$pathToImage = rtrim($pathToImage, '\/').DIRECTORY_SEPARATOR.$this->page.'.'.$this->outputFormat; |
|
190
|
|
|
} |
|
191
|
|
|
|
|
192
|
|
|
$imageData = $this->getImageData($pathToImage); |
|
193
|
|
|
|
|
194
|
|
|
$status = file_put_contents($pathToImage, $imageData) !== false; |
|
195
|
|
|
|
|
196
|
|
|
if ($clear) { |
|
197
|
|
|
$this->clear(); |
|
198
|
|
|
} |
|
199
|
|
|
|
|
200
|
|
|
return $status; |
|
201
|
|
|
} |
|
202
|
|
|
|
|
203
|
|
|
/** |
|
204
|
|
|
* Save the file as images to the given directory. |
|
205
|
|
|
* |
|
206
|
|
|
* @param string $directory |
|
207
|
|
|
* @param string $prefix |
|
208
|
|
|
* |
|
209
|
|
|
* @return array $files the paths to the created images |
|
210
|
|
|
*/ |
|
211
|
|
|
public function saveAllPagesAsImages($directory, $prefix = '') |
|
212
|
|
|
{ |
|
213
|
|
|
|
|
214
|
|
|
$numberOfPages = $this->getNumberOfPages(); |
|
215
|
|
|
|
|
216
|
|
|
if ($numberOfPages === 0) { |
|
217
|
|
|
return []; |
|
218
|
|
|
} |
|
219
|
|
|
|
|
220
|
|
|
return array_map(function ($pageNumber) use ($directory, $prefix) { |
|
221
|
|
|
$this->setPage($pageNumber); |
|
222
|
|
|
|
|
223
|
|
|
$destination = "{$directory}/{$prefix}{$pageNumber}.{$this->outputFormat}"; |
|
224
|
|
|
|
|
225
|
|
|
$this->saveImage($destination, false); |
|
226
|
|
|
|
|
227
|
|
|
return $destination; |
|
228
|
|
|
}, range(1, $numberOfPages)); |
|
229
|
|
|
|
|
230
|
|
|
$this->clear(); |
|
|
|
|
|
|
231
|
|
|
} |
|
232
|
|
|
|
|
233
|
|
|
/** |
|
234
|
|
|
* Return raw image data. |
|
235
|
|
|
* |
|
236
|
|
|
* @param string $pathToImage |
|
237
|
|
|
* |
|
238
|
|
|
* @return \Imagick |
|
239
|
|
|
*/ |
|
240
|
|
|
public function getImageData($pathToImage) |
|
241
|
|
|
{ |
|
242
|
|
|
/* |
|
243
|
|
|
* Reinitialize imagick because the target resolution must be set |
|
244
|
|
|
* before reading the actual image. |
|
245
|
|
|
*/ |
|
246
|
|
|
$this->imagick = new Imagick(); |
|
247
|
|
|
|
|
248
|
|
|
$this->imagick->setResolution($this->resolution, $this->resolution); |
|
249
|
|
|
|
|
250
|
|
|
if ($this->colorspace !== null) { |
|
251
|
|
|
$this->imagick->setColorspace($this->colorspace); |
|
252
|
|
|
} |
|
253
|
|
|
|
|
254
|
|
|
if ($this->compressionQuality !== null) { |
|
255
|
|
|
$this->imagick->setCompressionQuality($this->compressionQuality); |
|
256
|
|
|
} |
|
257
|
|
|
|
|
258
|
|
|
$this->imagick->readImage(sprintf('%s[%s]', $this->pdfFile, $this->page - 1)); |
|
259
|
|
|
|
|
260
|
|
|
if (is_int($this->layerMethod)) { |
|
261
|
|
|
$this->imagick = $this->imagick->mergeImageLayers($this->layerMethod); |
|
262
|
|
|
} |
|
263
|
|
|
|
|
264
|
|
|
$this->imagick->setFormat($this->determineOutputFormat($pathToImage)); |
|
265
|
|
|
|
|
266
|
|
|
return $this->imagick; |
|
267
|
|
|
} |
|
268
|
|
|
|
|
269
|
|
|
/** |
|
270
|
|
|
* @param int $colorspace |
|
271
|
|
|
* |
|
272
|
|
|
* @return $this |
|
273
|
|
|
*/ |
|
274
|
|
|
public function setColorspace(int $colorspace) |
|
275
|
|
|
{ |
|
276
|
|
|
$this->colorspace = $colorspace; |
|
277
|
|
|
|
|
278
|
|
|
return $this; |
|
279
|
|
|
} |
|
280
|
|
|
|
|
281
|
|
|
/** |
|
282
|
|
|
* @param int $compressionQuality |
|
283
|
|
|
* |
|
284
|
|
|
* @return $this |
|
285
|
|
|
*/ |
|
286
|
|
|
public function setCompressionQuality(int $compressionQuality) |
|
287
|
|
|
{ |
|
288
|
|
|
$this->compressionQuality = $compressionQuality; |
|
289
|
|
|
|
|
290
|
|
|
return $this; |
|
291
|
|
|
} |
|
292
|
|
|
|
|
293
|
|
|
/** |
|
294
|
|
|
* Determine in which format the image must be rendered. |
|
295
|
|
|
* |
|
296
|
|
|
* @param $pathToImage |
|
297
|
|
|
* |
|
298
|
|
|
* @return string |
|
299
|
|
|
*/ |
|
300
|
|
|
protected function determineOutputFormat($pathToImage) |
|
301
|
|
|
{ |
|
302
|
|
|
$outputFormat = pathinfo($pathToImage, PATHINFO_EXTENSION); |
|
303
|
|
|
|
|
304
|
|
|
if ($this->outputFormat != '') { |
|
305
|
|
|
$outputFormat = $this->outputFormat; |
|
306
|
|
|
} |
|
307
|
|
|
|
|
308
|
|
|
$outputFormat = strtolower($outputFormat); |
|
309
|
|
|
|
|
310
|
|
|
if (! $this->isValidOutputFormat($outputFormat)) { |
|
311
|
|
|
$outputFormat = 'jpg'; |
|
312
|
|
|
} |
|
313
|
|
|
|
|
314
|
|
|
return $outputFormat; |
|
315
|
|
|
} |
|
316
|
|
|
|
|
317
|
|
|
/** |
|
318
|
|
|
* Fetch remote file and save temporary on image dir. |
|
319
|
|
|
* |
|
320
|
|
|
* @throws \Spatie\PdfToImage\Exceptions\TempPathNotWritable |
|
321
|
|
|
* @throws \Spatie\PdfToImage\Exceptions\RemoteFileFetchFailed |
|
322
|
|
|
* |
|
323
|
|
|
* @return string |
|
324
|
|
|
*/ |
|
325
|
|
|
protected function fetchRemoteFile($source) |
|
326
|
|
|
{ |
|
327
|
|
|
$pathToTemp = tempnam(sys_get_temp_dir(), 'pdf'); |
|
328
|
|
|
|
|
329
|
|
|
if (!is_writable($pathToTemp)) { |
|
330
|
|
|
throw new TempPathNotWritable(); |
|
331
|
|
|
} |
|
332
|
|
|
|
|
333
|
|
|
$remote = curl_init($source); |
|
334
|
|
|
|
|
335
|
|
|
$local = fopen($pathToTemp, 'w'); |
|
336
|
|
|
|
|
337
|
|
|
curl_setopt($remote, CURLOPT_FILE, $local); |
|
338
|
|
|
|
|
339
|
|
|
curl_setopt($remote, CURLOPT_TIMEOUT, 60); |
|
340
|
|
|
|
|
341
|
|
|
curl_setopt($remote, CURLOPT_FOLLOWLOCATION, true); |
|
342
|
|
|
|
|
343
|
|
|
curl_exec($remote); |
|
344
|
|
|
|
|
345
|
|
|
if (curl_error($remote)) { |
|
346
|
|
|
throw new RemoteFileFetchFailed("Remote file fetch failed. Error ".curl_error($remote)); |
|
347
|
|
|
} |
|
348
|
|
|
|
|
349
|
|
|
curl_close($remote); |
|
350
|
|
|
|
|
351
|
|
|
fclose($local); |
|
352
|
|
|
|
|
353
|
|
|
return $pathToTemp; |
|
354
|
|
|
} |
|
355
|
|
|
|
|
356
|
|
|
/** |
|
357
|
|
|
* Delete Temporary pdf file. |
|
358
|
|
|
* |
|
359
|
|
|
* @throws \Spatie\PdfToImage\Exceptions\TempFileDoesNotExist |
|
360
|
|
|
* |
|
361
|
|
|
* @return bool |
|
362
|
|
|
*/ |
|
363
|
|
|
protected function deleteTempFile() |
|
364
|
|
|
{ |
|
365
|
|
|
$tempPath = $this->pdfFile; |
|
366
|
|
|
|
|
367
|
|
|
if (!file_exists($tempPath)) { |
|
368
|
|
|
throw new TempFileDoesNotExist("Temporary file {$tempPath} does not exist"); |
|
369
|
|
|
} |
|
370
|
|
|
|
|
371
|
|
|
return unlink($tempPath); |
|
372
|
|
|
} |
|
373
|
|
|
|
|
374
|
|
|
/** |
|
375
|
|
|
* Remove temp file and clear Imagick object |
|
376
|
|
|
* |
|
377
|
|
|
* @return bool |
|
378
|
|
|
*/ |
|
379
|
|
|
protected function clear() |
|
380
|
|
|
{ |
|
381
|
|
|
if ($this->isRemoteFile) { |
|
382
|
|
|
$this->deleteTempFile(); |
|
383
|
|
|
} |
|
384
|
|
|
|
|
385
|
|
|
return $this->imagick->clear(); |
|
386
|
|
|
} |
|
387
|
|
|
|
|
388
|
|
|
} |
|
389
|
|
|
|
This check looks for unreachable code. It uses sophisticated control flow analysis techniques to find statements which will never be executed.
Unreachable code is most often the result of
return,dieorexitstatements that have been added for debug purposes.In the above example, the last
return falsewill never be executed, because a return statement has already been met in every possible execution path.