Skip to content

Commit

Permalink
WIP Make GD Extension Optional
Browse files Browse the repository at this point in the history
Fix #3719. Also #367, #469, #1384. It clearly comes up a lot. It has been rejected every time, and maybe it should be rejected now as well. For now, it can't hurt to submit a draft PR and gather feedback. Latest issue makes a decent case for this change, but will it open us up to a series of issues where user fails because GD is not enabled?

Based on approximately 30-35 unit tests that fail/error when run without GD, I believe the following is an exhaustive list of what is not supported (because they use functions available only when GD is enabled). Note that function `getimagesize` is available even if GD is not enabled.
- reading an image in an Xls file. This is the most likely source of problems if GD is not enabled.
- cloning a MemoryDrawing.
- creating a MemoryDrawing from string.
- setting imageResource for MemoryDrawing.
- rendering a chart.
- Writer/Xls support for GIF/BMP drawings (Xls requires that these be converted to PNG).
- Writer/Xlsx support for GIF/BMP drawings as background images for comments. We convert these to PNG. I am not convinced that these conversions are necessary, and may investigate further if this PR is implemented.
- Dompdf and Tcpdf may require GD under certain circumstances.
- exact font measurements.

I have changed the code so that any attempt to use one of the unsupported functions identified above (except exact font and Dompdf/Tcpdf) will throw an exception ('Required PHP extension GD is not enabled') if they are attempted when GD is not enabled; this will, I hope, make it clear to the end user what the problem is. Dompdf/Tcpdf already generate a similar message when GD is needed but unavailable. For exact fonts, we will just fall back to approximate, which we sometimes do even when GD is available.

I will leave this PR in draft status for a while to see if there is feedback pro or con before attempting to merge it.
  • Loading branch information
oleibman committed Oct 16, 2023
1 parent db35a41 commit 49722a0
Show file tree
Hide file tree
Showing 17 changed files with 48 additions and 7 deletions.
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,6 @@
"ext-ctype": "*",
"ext-dom": "*",
"ext-fileinfo": "*",
"ext-gd": "*",
"ext-iconv": "*",
"ext-libxml": "*",
"ext-mbstring": "*",
Expand All @@ -88,6 +87,7 @@
"voku/anti-xss": "^4.1"
},
"require-dev": {
"ext-gd": "*",
"dealerdirect/phpcodesniffer-composer-installer": "dev-main",
"dompdf/dompdf": "^2.0",
"friendsofphp/php-cs-fixer": "^3.2",
Expand All @@ -101,6 +101,7 @@
"tecnickcom/tcpdf": "^6.5"
},
"suggest": {
"ext-gd": "Graphics for images including graphs, and exact font measurement",
"ext-intl": "PHP Internationalization Functions",
"mpdf/mpdf": "Option for rendering PDF with PDF Writer",
"dompdf/dompdf": "Option for rendering PDF with PDF Writer",
Expand Down
1 change: 1 addition & 0 deletions samples/Basic/25_In_memory_image.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@

// Generate an image
$helper->log('Generate an image');
MemoryDrawing::checkGd();
$gdImage = @imagecreatetruecolor(120, 20);
if (!$gdImage) {
exit('Cannot Initialize new GD image stream');
Expand Down
6 changes: 5 additions & 1 deletion samples/Pdf/21_Pdf_TCPDF.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,8 @@
IOFactory::registerWriter('Pdf', $className);

// Save
$helper->write($spreadsheet, __FILE__, ['Pdf']);
if (extension_loaded('gd')) {
$helper->write($spreadsheet, __FILE__, ['Pdf']);
} else {
$helper->log('Required PHP Extension Gd is not enabled');
}
2 changes: 2 additions & 0 deletions samples/Reader/20_Reader_worksheet_hyperlink_image.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@

use PhpOffice\PhpSpreadsheet\Shared\File;
use PhpOffice\PhpSpreadsheet\Spreadsheet;
use PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing;

require __DIR__ . '/../Header.php';
$inputFileType = 'Xlsx';

$helper->log('Start');
BaseDrawing::checkGd();

$spreadsheet = new Spreadsheet();

Expand Down
2 changes: 2 additions & 0 deletions src/PhpSpreadsheet/Chart/Chart.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace PhpOffice\PhpSpreadsheet\Chart;

use PhpOffice\PhpSpreadsheet\Settings;
use PhpOffice\PhpSpreadsheet\Worksheet\BaseDrawing;
use PhpOffice\PhpSpreadsheet\Worksheet\Worksheet;

class Chart
Expand Down Expand Up @@ -673,6 +674,7 @@ public function render($outputDestination = null)

/** @var Renderer\IRenderer */
$renderer = new $libraryName($this);
BaseDrawing::checkGd();

return $renderer->render($outputDestination);
}
Expand Down
1 change: 1 addition & 0 deletions src/PhpSpreadsheet/Reader/Xls.php
Original file line number Diff line number Diff line change
Expand Up @@ -1091,6 +1091,7 @@ protected function loadSpreadsheetFromFile(string $filename): Spreadsheet

// need check because some blip types are not supported by Escher reader such as EMF
if ($blip = $BSE->getBlip()) {
MemoryDrawing::checkGd();
$ih = imagecreatefromstring($blip->getData());
if ($ih !== false) {
$drawing = new MemoryDrawing();
Expand Down
2 changes: 1 addition & 1 deletion src/PhpSpreadsheet/Shared/Font.php
Original file line number Diff line number Diff line change
Expand Up @@ -404,7 +404,7 @@ public static function calculateColumnWidth(
}

// Try to get the exact text width in pixels
$approximate = self::$autoSizeMethod === self::AUTOSIZE_METHOD_APPROX;
$approximate = (self::$autoSizeMethod === self::AUTOSIZE_METHOD_APPROX) || !extension_loaded('gd');
$columnWidth = 0;
if (!$approximate) {
try {
Expand Down
14 changes: 14 additions & 0 deletions src/PhpSpreadsheet/Worksheet/BaseDrawing.php
Original file line number Diff line number Diff line change
Expand Up @@ -542,4 +542,18 @@ public function setSrcRect($srcRect): self

return $this;
}

private static ?bool $gdEnabled = null;

public static function checkGd(): void
{
if (self::$gdEnabled === null) {
self::$gdEnabled = extension_loaded('gd');
}
if (self::$gdEnabled !== true) {
// @codeCoverageIgnoreStart
throw new PhpSpreadsheetException('Required PHP extension Gd is not enabled');
// @codeCoverageIgnoreEnd
}
}
}
3 changes: 3 additions & 0 deletions src/PhpSpreadsheet/Worksheet/MemoryDrawing.php
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ private function cloneResource(): void
if (!$this->imageResource) {
return;
}
self::checkGd();

$width = (int) imagesx($this->imageResource);
$height = (int) imagesy($this->imageResource);
Expand Down Expand Up @@ -144,6 +145,7 @@ public static function fromStream($imageStream): self
*/
public static function fromString(string $imageString): self
{
self::checkGd();
$gdImage = @imagecreatefromstring($imageString);
if ($gdImage === false) {
throw new Exception('Value cannot be converted to an image');
Expand Down Expand Up @@ -254,6 +256,7 @@ public function setImageResource(?GdImage $value): static

if ($this->imageResource !== null) {
// Get width/height
self::checkGd();
$this->width = (int) imagesx($this->imageResource);
$this->height = (int) imagesy($this->imageResource);
}
Expand Down
2 changes: 2 additions & 0 deletions src/PhpSpreadsheet/Writer/Xls.php
Original file line number Diff line number Diff line change
Expand Up @@ -437,6 +437,7 @@ private function processDrawing(BstoreContainer &$bstoreContainer, Drawing $draw
switch ($imageFormat) {
case 1: // GIF, not supported by BIFF8, we convert to PNG
$blipType = BSE::BLIPTYPE_PNG;
BaseDrawing::checkGd();
$newImage = @imagecreatefromgif($filename);
if ($newImage === false) {
throw new Exception("Unable to create image from $filename");
Expand All @@ -459,6 +460,7 @@ private function processDrawing(BstoreContainer &$bstoreContainer, Drawing $draw
break;
case 6: // Windows DIB (BMP), we convert to PNG
$blipType = BSE::BLIPTYPE_PNG;
BaseDrawing::checkGd();
$newImage = @imagecreatefrombmp($filename);
if ($newImage === false) {
throw new Exception("Unable to create image from $filename");
Expand Down
2 changes: 2 additions & 0 deletions src/PhpSpreadsheet/Writer/Xlsx.php
Original file line number Diff line number Diff line change
Expand Up @@ -672,6 +672,7 @@ private function processDrawing(WorksheetDrawing $drawing): string|null|false
if (!empty($imageData)) {
switch ($imageData[2]) {
case 1: // GIF, not supported by BIFF8, we convert to PNG
BaseDrawing::checkGd();
$image = imagecreatefromgif($filename);
if ($image !== false) {
ob_start();
Expand All @@ -693,6 +694,7 @@ private function processDrawing(WorksheetDrawing $drawing): string|null|false
break;

case 6: // Windows DIB (BMP), we convert to PNG
BaseDrawing::checkGd();
$image = imagecreatefrombmp($filename);
if ($image !== false) {
ob_start();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ class DrawingImageHyperlinkTest extends AbstractFunctional
{
public function testDrawingImageHyperlinkTest(): void
{
MemoryDrawing::checkGd();
$gdImage = @imagecreatetruecolor(120, 20);
$textColor = ($gdImage === false) ? false : imagecolorallocate($gdImage, 255, 255, 255);
if ($gdImage === false || $textColor === false) {
Expand Down
11 changes: 7 additions & 4 deletions tests/PhpSpreadsheetTests/Shared/ExactFontTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,9 @@ public function testExact(string $fontName, float $excelWidth, float $xmlWidth,
if ($this->incompleteMessage !== '') {
self::markTestIncomplete($this->incompleteMessage);
}
if (!extension_loaded('gd')) {
self::markTestSkipped('Need GD extension for exact font sizes');
}
$font = new StyleFont();
$font->setName($fontName);
$font->setSize(11);
Expand Down Expand Up @@ -165,7 +168,7 @@ public function testIssue3626NoPad(): void
$font = new StyleFont();
$font->setName($fontName);
$exactWidth = Font::calculateColumnWidth($font, 'Col3');
$expectedWidth = 4.5703;
$expectedWidth = extension_loaded('gd') ? 4.5703 : 5.8557;
if ($exactWidth > 0.95 * $expectedWidth && $exactWidth < 1.05 * $expectedWidth) {
self::assertTrue(true);
} else {
Expand All @@ -175,7 +178,7 @@ public function testIssue3626NoPad(): void
$font = new StyleFont();
$font->setName($fontName);
$exactWidth = Font::calculateColumnWidth($font, 'Big Column in 4 position');
$expectedWidth = 26.2793;
$expectedWidth = extension_loaded('gd') ? 26.2793 : 29.4214;
if ($exactWidth > 0.95 * $expectedWidth && $exactWidth < 1.05 * $expectedWidth) {
self::assertTrue(true);
} else {
Expand All @@ -198,7 +201,7 @@ public function testIssue3626Pad(): void
$font->setName($fontName);
$font->setSize(20);
$exactWidth = Font::calculateColumnWidth($font, 'Column2');
$expectedWidth = 18.8525;
$expectedWidth = extension_loaded('gd') ? 18.8525 : 16.7102;
if ($exactWidth > 0.95 * $expectedWidth && $exactWidth < 1.05 * $expectedWidth) {
self::assertTrue(true);
} else {
Expand All @@ -218,7 +221,7 @@ public function testIssue3626Pad(): void
$font = new StyleFont();
$font->setName($fontName);
$exactWidth = Font::calculateColumnWidth($font, 'Big Column in 4 position');
$expectedWidth = 27.5647;
$expectedWidth = extension_loaded('gd') ? 27.5647 : 29.4214;
if ($exactWidth > 0.95 * $expectedWidth && $exactWidth < 1.05 * $expectedWidth) {
self::assertTrue(true);
} else {
Expand Down
1 change: 1 addition & 0 deletions tests/PhpSpreadsheetTests/Worksheet/DrawingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class DrawingTest extends TestCase
{
public function testCloningWorksheetWithImages(): void
{
MemoryDrawing::checkGd();
$gdImage = @imagecreatetruecolor(120, 20);
$textColor = ($gdImage === false) ? false : imagecolorallocate($gdImage, 255, 255, 255);
if ($gdImage === false || $textColor === false) {
Expand Down
2 changes: 2 additions & 0 deletions tests/PhpSpreadsheetTests/Worksheet/MemoryDrawingTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class MemoryDrawingTest extends TestCase
{
public function testMemoryDrawing(): void
{
MemoryDrawing::checkGd();
$name = 'In-Memory image';
$gdImage = @imagecreatetruecolor(120, 20);
if ($gdImage === false) {
Expand Down Expand Up @@ -56,6 +57,7 @@ public function testMemoryDrawingFromString(): void

public function testMemoryDrawingFromInvalidString(): void
{
MemoryDrawing::checkGd();
$this->expectException(Exception::class);
$this->expectExceptionMessage('Value cannot be converted to an image');

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public function testMemoryDrawingOffset(int $w, int $h, int $x, int $y): void

$image = file_get_contents(__DIR__ . '/../../../data/Reader/HTML/memoryDrawingTest.jpg');
self::assertNotFalse($image, 'unable to read file');
MemoryDrawing::checkGd();
$image = imagecreatefromstring($image);
self::assertNotFalse($image, 'unable to create image from string');
$drawing = new MemoryDrawing();
Expand Down
1 change: 1 addition & 0 deletions tests/PhpSpreadsheetTests/Writer/Xlsx/DrawingsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -565,6 +565,7 @@ public static function providerEditAs(): array

public function testMemoryDrawingDuplicateResource(): void
{
BaseDrawing::checkGd();
$gdImage = imagecreatetruecolor(120, 20);
$textColor = ($gdImage === false) ? false : imagecolorallocate($gdImage, 255, 255, 255);
if ($gdImage === false || $textColor === false) {
Expand Down

0 comments on commit 49722a0

Please sign in to comment.