diff --git a/.github/workflows/php.yml b/.github/workflows/php.yml new file mode 100644 index 0000000..b9c99de --- /dev/null +++ b/.github/workflows/php.yml @@ -0,0 +1,116 @@ +name: PHP +on: + push: + branches: + - master + pull_request: + +jobs: + php-cs-fixer: + name: PHP CS Fixer + runs-on: ubuntu-latest + steps: + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '7.4' + extensions: xml + + - uses: actions/checkout@v4 + + - name: Validate composer config + run: composer validate --strict + + - name: Composer Install + run: composer global require friendsofphp/php-cs-fixer + + - name: Add environment path + run: export PATH="$PATH:$HOME/.composer/vendor/bin" + + - name: Run PHPCSFixer + run: php-cs-fixer fix --dry-run --diff + + phpstan: + name: PHP Static Analysis + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: + - '7.1' + - '7.2' + - '7.3' + - '7.4' + - '8.0' + - '8.1' + - '8.2' + - '8.3' + steps: + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: xml + + - uses: actions/checkout@v4 + + - name: Composer Install + run: composer install --ansi --prefer-dist --no-interaction --no-progress + + - name: Run phpstan + run: ./vendor/bin/phpstan analyse -c phpstan.neon.dist + + phpunit: + name: PHPUnit + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + php: + - '7.1' + - '7.2' + - '7.3' + - '7.4' + - '8.0' + - '8.1' + - '8.2' + - '8.3' + steps: + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: ${{ matrix.php }} + extensions: xml + coverage: ${{ (matrix.php == '8.1') && 'xdebug' || 'none' }} + + - uses: actions/checkout@v4 + + - name: Install dependencies + run: composer install --ansi --prefer-dist --no-interaction --no-progress + + - name: Run PHPUnit + if: matrix.php != '8.1' + run: ./vendor/bin/phpunit -c phpunit.xml.dist + + - name: Run PHPUnit (w CodeCoverage) + if: matrix.php == '8.1' + run: XDEBUG_MODE=coverage ./vendor/bin/phpunit -c phpunit.xml.dist --coverage-clover build/clover.xml + + - name: Upload coverage results to Coveralls + if: matrix.php == '8.1' + env: + COVERALLS_REPO_TOKEN: ${{ secrets.GITHUB_TOKEN }} + run: | + wget https://github.com/php-coveralls/php-coveralls/releases/download/v2.4.3/php-coveralls.phar + chmod +x php-coveralls.phar + php php-coveralls.phar --coverage_clover=build/clover.xml --json_path=build/coveralls-upload.json -vvv + + #roave-backwards-compatibility-check: + # name: Roave Backwards Compatibility Check + # runs-on: ubuntu-latest + # steps: + # - uses: actions/checkout@v4 + # with: + # fetch-depth: 0 + # - name: "Check for BC breaks" + # run: docker run -u $(id -u) -v $(pwd):/app nyholm/roave-bc-check-ga \ No newline at end of file diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 0000000..aaa5016 --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,45 @@ +setUsingCache(true) + ->setRiskyAllowed(true) + ->setRules([ + '@Symfony' => true, + 'array_indentation' => true, + 'cast_spaces' => [ + 'space' => 'single', + ], + 'combine_consecutive_issets' => true, + 'concat_space' => [ + 'spacing' => 'one', + ], + 'error_suppression' => [ + 'mute_deprecation_error' => false, + 'noise_remaining_usages' => false, + 'noise_remaining_usages_exclude' => [], + ], + 'function_to_constant' => false, + 'global_namespace_import' => true, + 'method_chaining_indentation' => true, + 'no_alias_functions' => false, + 'no_superfluous_phpdoc_tags' => false, + 'non_printable_character' => [ + 'use_escape_sequences_in_strings' => true, + ], + 'phpdoc_align' => [ + 'align' => 'left', + ], + 'phpdoc_summary' => false, + 'protected_to_private' => false, + 'self_accessor' => false, + 'yoda_style' => false, + 'single_line_throw' => false, + 'no_alias_language_construct_call' => false, + ]) + ->getFinder() + ->in(__DIR__) + ->exclude('vendor'); + +return $config; \ No newline at end of file diff --git a/composer.json b/composer.json index 4a3fac2..a1fa1eb 100644 --- a/composer.json +++ b/composer.json @@ -1,9 +1,9 @@ { "name": "phpoffice/wmf", + "description": "WMF - Manipulate WMF Images", + "keywords": ["PHP", "wmf", "emf", "image"], + "homepage": "https://phpoffice.github.io/WMF/", "type": "library", - "require-dev": { - "phpunit/phpunit": "10" - }, "license": "MIT", "autoload": { "psr-4": { @@ -20,5 +20,13 @@ "name": "Progi1984", "homepage": "https://lefevre.dev" } - ] + ], + "require": { + "php": "^7.1|^8.0", + "ext-gd": "*" + }, + "require-dev": { + "phpunit/phpunit": "^7.0 || ^9.0", + "phpstan/phpstan": "^0.12.88 || ^1.0.0" + } } diff --git a/phpstan.neon.dist b/phpstan.neon.dist new file mode 100644 index 0000000..c564cae --- /dev/null +++ b/phpstan.neon.dist @@ -0,0 +1,9 @@ +parameters: + level: 7 + bootstrapFiles: + - vendor/autoload.php + paths: + - src + - tests + reportUnmatchedIgnoredErrors: false + ignoreErrors: \ No newline at end of file diff --git a/phpunit.xml b/phpunit.xml.dist similarity index 100% rename from phpunit.xml rename to phpunit.xml.dist diff --git a/src/WMF/Reader/GD.php b/src/WMF/Reader/GD.php index 6e4463c..30abef1 100644 --- a/src/WMF/Reader/GD.php +++ b/src/WMF/Reader/GD.php @@ -4,13 +4,13 @@ namespace PhpOffice\WMF\Reader; +use GdImage; use PhpOffice\WMF\Exception\WMFException; -use function imagedestroy; class GD implements ReaderInterface { /** - * @var resource + * @var GdImage|false */ protected $gd; /** @@ -41,6 +41,10 @@ class GD implements ReaderInterface * @var int */ protected $unitPerInch; + /** + * @var array> + */ + protected $gdiObjects = []; const META_EOF = 0x0000; const META_SETPOLYFILLMODE = 0x0106; @@ -52,8 +56,6 @@ class GD implements ReaderInterface const META_CREATEBRUSHINDIRECT = 0x02FC; const META_POLYGON = 0x0324; - protected $gdiObjects = []; - public function __destruct() { if ($this->gd){ @@ -82,6 +84,8 @@ public function load(string $filename): bool $recordEnd = false; $dataFillColor = $dataDrawColor = null; + $nullPen = $nullBrush = false; + $dashArray = []; $modePolyFill = 0; while ($this->pos < $contentLen && !$recordEnd) { @@ -91,6 +95,7 @@ public function load(string $filename): bool list(,$recordType) = unpack('S', substr($this->content, $this->pos, 2)); $this->pos += 2; + $params = []; if ($size > 3) { $params = substr($this->content, $this->pos, 2 * ($size - 3)); $this->pos += 2 * ($size - 3); @@ -171,7 +176,6 @@ public function load(string $filename): bool break; case self::META_SETWINDOWEXT: // Do not allow window extent to be changed after drawing has begun - var_dump('META_SETWINDOWEXT'); if (!$this->windowWidth) { $windowExtent = array_reverse(unpack('s2', $params)); $this->windowWidth = (int) $windowExtent[0]; @@ -219,7 +223,11 @@ public function load(string $filename): bool $op = 'n'; } else { // Fill - \imagefilledpolygon($this->gd, $points, $dataFillColor); + if (\PHP_VERSION_ID < 80000) { + imagefilledpolygon($this->gd, $points, $numpoints, $dataFillColor); + } else { + imagefilledpolygon($this->gd, $points, $dataFillColor); + } } } else { if ($nullBrush) { @@ -228,7 +236,11 @@ public function load(string $filename): bool } else { // Stroke and Fill \imagepolygon($this->gd, $points, $numpoints, $dataDrawColor); - \imagefilledpolygon($this->gd, $points, $dataFillColor); + if (\PHP_VERSION_ID < 80000) { + imagefilledpolygon($this->gd, $points, $numpoints, $dataFillColor); + } else { + imagefilledpolygon($this->gd, $points, $dataFillColor); + } } } if ($modePolyFill == 1 && (($nullPen && !$nullBrush) || (!$nullPen && $nullBrush))) { @@ -284,8 +296,8 @@ public function getResource() $this->gd = imagescale( $this->gd, - ceil(($this->windowWidth/$this->unitPerInch) * $inchToPoint), - ceil(($this->windowHeight/$this->unitPerInch) * $inchToPoint) + (int) ceil(($this->windowWidth/$this->unitPerInch) * $inchToPoint), + (int) ceil(($this->windowHeight/$this->unitPerInch) * $inchToPoint) ); imagesavealpha($this->gd, true); return $this->gd; diff --git a/src/WMF/Reader/Imagick.php b/src/WMF/Reader/Imagick.php index 0fd93c0..8483ea1 100644 --- a/src/WMF/Reader/Imagick.php +++ b/src/WMF/Reader/Imagick.php @@ -5,6 +5,7 @@ namespace PhpOffice\WMF\Reader; use Imagick as ImagickBase; +use PhpOffice\WMF\Exception\WMFException; class Imagick implements ReaderInterface { diff --git a/src/WMF/Reader/Magic.php b/src/WMF/Reader/Magic.php index b64d103..d0e0d22 100644 --- a/src/WMF/Reader/Magic.php +++ b/src/WMF/Reader/Magic.php @@ -17,12 +17,14 @@ class Magic implements ReaderInterface public function __construct() { + $reader = null; if (extension_loaded('imagick') && in_array('WMF', ImagickBase::queryformats())) { - $this->reader = new ImagickReader(); + $reader = new ImagickReader(); } - if (!$this->reader && extension_loaded('gd')) { - $this->reader = new GD(); + if (!$reader && extension_loaded('gd')) { + $reader = new GD(); } + $this->reader = $reader; } public function load(string $filename): bool diff --git a/tests/WMF/Reader/AbstractTestReader.php b/tests/WMF/Reader/AbstractTestReader.php index cb3ec34..09626ac 100644 --- a/tests/WMF/Reader/AbstractTestReader.php +++ b/tests/WMF/Reader/AbstractTestReader.php @@ -23,6 +23,9 @@ public function assertImageCompare(string $expectedFile, string $outputFile, flo $this->assertLessThanOrEqual($threshold, $result[1]); } + /** + * @return array> + */ public static function dataProviderFiles(): array { return [ diff --git a/tests/WMF/Reader/GDTest.php b/tests/WMF/Reader/GDTest.php index 8c5c488..40a65a3 100644 --- a/tests/WMF/Reader/GDTest.php +++ b/tests/WMF/Reader/GDTest.php @@ -7,7 +7,7 @@ use GdImage; use PhpOffice\WMF\Reader\GD; -class GDTest extends AbstractTestReader +class GDTest extends AbstractTestReader { /** * @dataProvider dataProviderFiles @@ -27,13 +27,14 @@ public function testGetResource(string $file): void $reader->load($this->getResourceDir() . $file); $this->assertInstanceOf(GdImage::class, $reader->getResource()); } + /** * @dataProvider dataProviderFiles */ public function testOutput(string $file): void { - $outputFile = $this->getResourceDir() . 'output_'.pathinfo($file, PATHINFO_FILENAME).'.png'; - $similarFile = $this->getResourceDir() . pathinfo($file, PATHINFO_FILENAME).'.png'; + $outputFile = $this->getResourceDir() . 'output_' . pathinfo($file, PATHINFO_FILENAME) . '.png'; + $similarFile = $this->getResourceDir() . pathinfo($file, PATHINFO_FILENAME) . '.png'; $reader = new GD(); $reader->load($this->getResourceDir() . $file); @@ -50,6 +51,6 @@ public function testOutput(string $file): void public function testIsWMF(string $file): void { $reader = new GD(); - $this->assertTrue($reader->isWMF($this->getResourceDir() .$file)); + $this->assertTrue($reader->isWMF($this->getResourceDir() . $file)); } -} \ No newline at end of file +} diff --git a/tests/WMF/Reader/ImagickTest.php b/tests/WMF/Reader/ImagickTest.php index 9ba93fd..a8076f2 100644 --- a/tests/WMF/Reader/ImagickTest.php +++ b/tests/WMF/Reader/ImagickTest.php @@ -7,7 +7,7 @@ use Imagick as ImagickBase; use PhpOffice\WMF\Reader\Imagick as ImagickReader; -class ImagickTest extends AbstractTestReader +class ImagickTest extends AbstractTestReader { /** * @dataProvider dataProviderFiles @@ -33,8 +33,8 @@ public function testGetResource(string $file): void */ public function testOutput(string $file): void { - $outputFile = $this->getResourceDir() . 'output_'.pathinfo($file, PATHINFO_FILENAME).'.png'; - $similarFile = $this->getResourceDir() . pathinfo($file, PATHINFO_FILENAME).'.png'; + $outputFile = $this->getResourceDir() . 'output_' . pathinfo($file, PATHINFO_FILENAME) . '.png'; + $similarFile = $this->getResourceDir() . pathinfo($file, PATHINFO_FILENAME) . '.png'; $reader = new ImagickReader(); $reader->load($this->getResourceDir() . $file); @@ -53,4 +53,4 @@ public function testIsWMF(string $file): void $reader = new ImagickReader(); $this->assertTrue($reader->isWMF($this->getResourceDir() . $file)); } -} \ No newline at end of file +} diff --git a/tests/WMF/Reader/MagicTest.php b/tests/WMF/Reader/MagicTest.php index ff87d95..fab16e2 100644 --- a/tests/WMF/Reader/MagicTest.php +++ b/tests/WMF/Reader/MagicTest.php @@ -26,13 +26,14 @@ public function testGetResource(string $file): void $reader->load($this->getResourceDir() . $file); $this->assertIsObject($reader->getResource()); } + /** * @dataProvider dataProviderFiles */ public function testOutput(string $file): void { - $outputFile = $this->getResourceDir() . 'output_'.pathinfo($file, PATHINFO_FILENAME).'.png'; - $similarFile = $this->getResourceDir() . pathinfo($file, PATHINFO_FILENAME).'.png'; + $outputFile = $this->getResourceDir() . 'output_'.pathinfo($file, PATHINFO_FILENAME) . '.png'; + $similarFile = $this->getResourceDir() . pathinfo($file, PATHINFO_FILENAME) . '.png'; $reader = new Magic(); $reader->load($this->getResourceDir() . $file); @@ -49,6 +50,6 @@ public function testOutput(string $file): void public function testIsWMF(string $file): void { $reader = new Magic(); - $this->assertTrue($reader->isWMF($this->getResourceDir() .$file)); + $this->assertTrue($reader->isWMF($this->getResourceDir() . $file)); } -} \ No newline at end of file +}