PHPUnit Test Suite Source
You can view the full source code for Linebreaks4imagettftextTest.php below.
Linebreaks4imagettftextTest.php
<?php
/**
* Copyright (c) 2018-2026 Andrew G. Johnson <andrew@andrewgjohnson.com>
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
* documentation files (the "Software"), to deal in the Software without restriction, including without limitation the
* rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to
* permit persons to whom the Software is furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
* Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
* WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
namespace AndrewGJohnson\AgjGd\Tests;
use PHPUnit\Framework\TestCase;
class Linebreaks4imagettftextTest extends TestCase
{
private const FONT_PATH = __DIR__ . '/../../NotoSans-Regular.ttf';
private const FONT_SIZE = 10;
private const FONT_ANGLE = 0;
public function testFunctionExists(): void
{
$this->assertTrue(function_exists('AndrewGJohnson\\AgjGd\\linebreaks4imagettftext'));
}
public function testReverseCompatibility(): void
{
$this->assertTrue(function_exists('andrewgjohnson\\linebreaks4imagettftext'));
$current = new \ReflectionFunction('AndrewGJohnson\\AgjGd\\linebreaks4imagettftext');
$old = new \ReflectionFunction('andrewgjohnson\\linebreaks4imagettftext');
$this->assertSame(
$current->getReturnType(),
$old->getReturnType(),
'Return types do not match (reverse compatibility)'
);
$this->assertSame(
$this->getParameterSignature($current),
$this->getParameterSignature($old),
'Parameters do not match (reverse compatibility)'
);
}
public function testReturnsString(): void
{
$result = \AndrewGJohnson\AgjGd\linebreaks4imagettftext(
self::FONT_SIZE,
self::FONT_ANGLE,
self::FONT_PATH,
'Hello world!',
10000
);
$this->assertIsString($result);
}
public function testEmptyStringReturnsEmptyString(): void
{
$result = \AndrewGJohnson\AgjGd\linebreaks4imagettftext(
self::FONT_SIZE,
self::FONT_ANGLE,
self::FONT_PATH,
'',
10000
);
$this->assertSame('', $result);
}
// A single word should always be returned unchanged regardless of maximumWidth.
public function testSingleWordReturnedUnchanged(): void
{
$word = 'Hello';
$result = \AndrewGJohnson\AgjGd\linebreaks4imagettftext(
self::FONT_SIZE,
self::FONT_ANGLE,
self::FONT_PATH,
$word,
1
);
$this->assertSame($word, $result);
}
public function testTextFittingMaximumWidthHasNoLineBreaks(): void
{
$result = \AndrewGJohnson\AgjGd\linebreaks4imagettftext(
self::FONT_SIZE,
self::FONT_ANGLE,
self::FONT_PATH,
'Hello world!',
10000
);
$this->assertStringNotContainsString("\n", $result);
$this->assertStringNotContainsString("\r", $result);
}
public function testTextExceedingMaximumWidthGetsLineBreak(): void
{
$result = \AndrewGJohnson\AgjGd\linebreaks4imagettftext(
self::FONT_SIZE,
self::FONT_ANGLE,
self::FONT_PATH,
'Hello world!',
1,
"\n"
);
$this->assertStringContainsString("\n", $result);
}
public function testCustomLineBreakCharacter(): void
{
$customBreak = '<br>';
$result = \AndrewGJohnson\AgjGd\linebreaks4imagettftext(
self::FONT_SIZE,
self::FONT_ANGLE,
self::FONT_PATH,
'Hello world!',
1,
$customBreak
);
$this->assertStringContainsString($customBreak, $result);
$this->assertStringNotContainsString(PHP_EOL, $result);
}
public function testAllWordsArePresentInOutput(): void
{
$words = ['Hello', 'world', 'foo', 'bar'];
$text = implode(' ', $words);
$result = \AndrewGJohnson\AgjGd\linebreaks4imagettftext(
self::FONT_SIZE,
self::FONT_ANGLE,
self::FONT_PATH,
$text,
1,
"\n"
);
foreach ($words as $word) {
$this->assertStringContainsString($word, $result);
}
}
// With forceBreakOnSingleWords disabled a long word that does not fit should still appear intact on a line; with
// the flag enabled the word should be split across lines.
public function testForceBreakOnSingleWordsSplitsLongWord(): void
{
$longWord = 'Pneumonoultramicroscopicsilicovolcanoconiosis';
$halfWidth = (int)($this->getTextWidth($longWord) / 2);
$text = 'A ' . $longWord;
$withoutForce = \AndrewGJohnson\AgjGd\linebreaks4imagettftext(
self::FONT_SIZE,
self::FONT_ANGLE,
self::FONT_PATH,
$text,
$halfWidth,
"\n",
false,
false
);
$withForce = \AndrewGJohnson\AgjGd\linebreaks4imagettftext(
self::FONT_SIZE,
self::FONT_ANGLE,
self::FONT_PATH,
$text,
$halfWidth,
"\n",
false,
true
);
$foundIntact = false;
foreach (explode("\n", $withoutForce) as $line) {
if (strpos($line, $longWord) !== false) {
$foundIntact = true;
break;
}
}
$this->assertTrue(
$foundIntact,
'Without forceBreakOnSingleWords the long word should appear intact on a single line'
);
$this->assertGreaterThan(
count(explode("\n", $withoutForce)),
count(explode("\n", $withForce)),
'With forceBreakOnSingleWords there should be more lines because the word is split'
);
}
// With attemptToBreakOnHyphens enabled, a hyphenated word that would overflow should be split at a hyphen so that
// the line ends with a trailing hyphen character.
public function testAttemptToBreakOnHyphensBreaksAtHyphen(): void
{
// maximumWidth is exactly the pixel width of 'A B-', so 'A B-C' overflows and the hyphen-break logic commits
// 'A B-' and carries 'C' to the next line.
$text = 'A B-C';
$maximumWidth = $this->getTextWidth('A B-');
$withHyphens = \AndrewGJohnson\AgjGd\linebreaks4imagettftext(
self::FONT_SIZE,
self::FONT_ANGLE,
self::FONT_PATH,
$text,
$maximumWidth,
"\n",
true
);
$withoutHyphens = \AndrewGJohnson\AgjGd\linebreaks4imagettftext(
self::FONT_SIZE,
self::FONT_ANGLE,
self::FONT_PATH,
$text,
$maximumWidth,
"\n",
false
);
$this->assertNotSame(
$withoutHyphens,
$withHyphens,
'attemptToBreakOnHyphens should produce a different result when a break at a hyphen is possible'
);
$hasLineEndingWithHyphen = false;
foreach (explode("\n", $withHyphens) as $line) {
if (substr($line, -1) === '-') {
$hasLineEndingWithHyphen = true;
break;
}
}
$this->assertTrue(
$hasLineEndingWithHyphen,
'With attemptToBreakOnHyphens at least one line should end with a trailing hyphen'
);
}
// When the last word would appear alone on the final line (a widow), preventWidows should pull the previous word
// down so the two words share the last line.
public function testPreventWidowsMovesPreviousWordToLastLine(): void
{
$word1 = 'Hello';
$word2 = 'world';
$word3 = 'x';
$text = $word1 . ' ' . $word2 . ' ' . $word3;
// Width fits 'Hello world!' exactly, so 'Hello world x' overflows and 'x' becomes a widow.
$maximumWidth = $this->getTextWidth($word1 . ' ' . $word2);
$withoutPrevent = \AndrewGJohnson\AgjGd\linebreaks4imagettftext(
self::FONT_SIZE,
self::FONT_ANGLE,
self::FONT_PATH,
$text,
$maximumWidth,
"\n",
false,
false,
false
);
$withPrevent = \AndrewGJohnson\AgjGd\linebreaks4imagettftext(
self::FONT_SIZE,
self::FONT_ANGLE,
self::FONT_PATH,
$text,
$maximumWidth,
"\n",
false,
false,
true
);
$linesWithout = explode("\n", $withoutPrevent);
$this->assertSame(
$word3,
end($linesWithout),
'Without preventWidows the last word should appear alone on the final line'
);
$linesWith = explode("\n", $withPrevent);
$lastLine = end($linesWith);
$this->assertStringContainsString(
$word2,
$lastLine,
'With preventWidows the second-to-last word should be moved to the final line'
);
$this->assertStringContainsString(
$word3,
$lastLine,
'With preventWidows the last word should still appear on the final line'
);
}
private function getParameterSignature(\ReflectionFunction $function): array
{
$parameters = [];
foreach ($function->getParameters() as $parameter) {
$isDefaultValueAvailable = $parameter->isDefaultValueAvailable();
$parameters[] = [
'defaultValue' => $isDefaultValueAvailable ? $parameter->getDefaultValue() : null,
'isDefaultValueAvailable' => $parameter->isDefaultValueAvailable(),
'isOptional' => $parameter->isOptional(),
'isPassedByReference' => $parameter->isPassedByReference(),
'isVariadic' => $parameter->isVariadic(),
'name' => $parameter->getName(),
'position' => $parameter->getPosition()
];
}
return $parameters;
}
private function getTextWidth(string $text): int
{
$bbox = imagettfbbox(self::FONT_SIZE, self::FONT_ANGLE, self::FONT_PATH, $text);
$left = min($bbox[0], $bbox[2], $bbox[4], $bbox[6]);
$right = max($bbox[0], $bbox[2], $bbox[4], $bbox[6]);
return $right - $left;
}
}