From e161d7c9322a5a576e27ee8199a326ee6c9580cb Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Thu, 18 Jun 2026 21:45:59 +0200 Subject: [PATCH 1/2] Fix float-to-int cast warning in Font::uchr on PHP 8.1+ When a CMap contains an oversized hex value, hexdec() returns a float larger than PHP_INT_MAX. Casting it to int in Font::uchr() raised a "not representable as an int" warning on PHP 8.1+. Such a value can never be a valid Unicode code point, so it is now treated as a missing character. Fixes warnings during running the tests --- src/Smalot/PdfParser/Font.php | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/Smalot/PdfParser/Font.php b/src/Smalot/PdfParser/Font.php index 8e1fbce1..61dccdfb 100644 --- a/src/Smalot/PdfParser/Font.php +++ b/src/Smalot/PdfParser/Font.php @@ -142,6 +142,15 @@ public static function uchr($code): string // note: // $code was typed as int before, but changed in https://github.com/smalot/pdfparser/pull/623 // because in some cases uchr was called with a float instead of an integer. + // + // A float that is out of integer range (e.g. resulting from a hexdec() + // overflow) cannot be cast to int without raising a "not representable + // as int" warning on PHP 8.1+, and such a value can never be a valid + // Unicode code point, so we treat it as a missing character. + if (\is_float($code) && (!\is_finite($code) || $code < \PHP_INT_MIN || $code > \PHP_INT_MAX)) { + return self::MISSING; + } + $code = (int) $code; if (!isset(self::$uchrCache[$code])) { From 6053be650c69262acd35e7f1878c5160acbf259d Mon Sep 17 00:00:00 2001 From: Andreas Gohr Date: Fri, 19 Jun 2026 20:27:50 +0200 Subject: [PATCH 2/2] Add Font::uchr float-handling test Add testUchrWithOutOfRangeFloat covering both the warning fix from the previous commit (e161d7c) for out-of-range floats, INF and NAN, as well as a check that floats in general are still cast to int (added in #623). Note that the previous commit wrongly assumed that the cast warning was introduced in PHP 8.1, but it was in fact introduced in PHP 8.5 only. Previous PHP versions silently cast an unrepresentable float to system dependent "weird" results (negative integers, 0-bytes). See https://wiki.php.net/rfc/warnings-php-8-5 for details. This change enables PHPUnit's failOnWarning option which will turn warnings into actual test failures - the test in this commit as well as the existing ParserTest::testIssue621 and ParserTest::testParseFile will fail now without the patch in e161d7c. Unrelated, minor convenience fix: this also increases the memory limit for unit tests to 1GB (from default 128MB) to avoid out of memory issues during the test run via phpunit.xml --- phpunit.xml | 48 ++++++++++++++++++++------------- tests/PHPUnit/Unit/FontTest.php | 32 ++++++++++++++++++++++ 2 files changed, 62 insertions(+), 18 deletions(-) diff --git a/phpunit.xml b/phpunit.xml index 52673fe8..d606a143 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -1,21 +1,33 @@ - - - - - - - - - - - tests/PHPUnit - - - - - src - - + + + + + + + + + + + + tests/PHPUnit + + + + + src + + diff --git a/tests/PHPUnit/Unit/FontTest.php b/tests/PHPUnit/Unit/FontTest.php index f60818ff..ed3da5f9 100644 --- a/tests/PHPUnit/Unit/FontTest.php +++ b/tests/PHPUnit/Unit/FontTest.php @@ -68,4 +68,36 @@ public function testDecodeTextIssue597(): void // compare result with expected value self::assertEquals('3cc2ab083e', bin2hex($result)); } + + /** + * A CMap could contain oversized hex values. hexdec() then returns a float + * larger than PHP_INT_MAX which cannot be cast to int. On PHP 8.5 this + * cast raises a "not representable as int" warning. + * + * Since these values can not represent valid Unicode code points anyway, + * it's safe to return Font::MISSING for them. This test checks that this + * is the case. + * + * The test relies on PhpUnit's failOnWarning="true" in phpunit.xml: + * a warning would error. + * + * @see https://github.com/smalot/pdfparser/pull/623 + * @see https://github.com/smalot/pdfparser/pull/825 + */ + public function testUchrWithOutOfRangeFloat(): void + { + // a regular code point is still decoded + $this->assertEquals('A', Font::uchr(0x41)); + + // a float that fits into an integer is still cast and decoded; this is + // the reason uchr() accepts floats in the first place + $this->assertEquals('A', Font::uchr(65.0)); + + // floats that do not fit into an integer can never be a valid code + // point; the value below is produced by hexdec() of an oversized hex + // string taken from samples/bugs/Issue621.pdf + $this->assertEquals(Font::MISSING, Font::uchr(1.50646556872121E+28)); + $this->assertEquals(Font::MISSING, Font::uchr(\INF)); + $this->assertEquals(Font::MISSING, Font::uchr(\NAN)); + } }