diff --git a/src/Drivers/AbstractFontProcessor.php b/src/Drivers/AbstractTextModifier.php similarity index 92% rename from src/Drivers/AbstractFontProcessor.php rename to src/Drivers/AbstractTextModifier.php index 987b55ea..fd7cb53e 100644 --- a/src/Drivers/AbstractFontProcessor.php +++ b/src/Drivers/AbstractTextModifier.php @@ -6,15 +6,15 @@ use Intervention\Image\Geometry\Polygon; use Intervention\Image\Geometry\Rectangle; use Intervention\Image\Interfaces\FontInterface; -use Intervention\Image\Interfaces\FontProcessorInterface; -use Intervention\Image\Typography\Line; use Intervention\Image\Typography\TextBlock; +use Intervention\Image\Typography\Line; -abstract class AbstractFontProcessor implements FontProcessorInterface +/** + * @property FontInterface $font + */ +abstract class AbstractTextModifier extends DriverModifier { - public function __construct(protected FontInterface $font) - { - } + abstract protected function boxSize(string $text): Polygon; public function leadingInPixels(): int { diff --git a/src/Drivers/Gd/Driver.php b/src/Drivers/Gd/Driver.php index 9c815103..ae518b44 100644 --- a/src/Drivers/Gd/Driver.php +++ b/src/Drivers/Gd/Driver.php @@ -7,8 +7,6 @@ use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\ColorProcessorInterface; use Intervention\Image\Interfaces\ColorspaceInterface; -use Intervention\Image\Interfaces\FontInterface; -use Intervention\Image\Interfaces\FontProcessorInterface; use Intervention\Image\Interfaces\ImageInterface; class Driver extends AbstractDriver @@ -45,9 +43,4 @@ public function colorProcessor(ColorspaceInterface $colorspace): ColorProcessorI { return new ColorProcessor($colorspace); } - - public function fontProcessor(FontInterface $font): FontProcessorInterface - { - return new FontProcessor($font); - } } diff --git a/src/Drivers/Gd/FontProcessor.php b/src/Drivers/Gd/FontProcessor.php deleted file mode 100644 index cfc8d2f4..00000000 --- a/src/Drivers/Gd/FontProcessor.php +++ /dev/null @@ -1,87 +0,0 @@ -font->hasFilename()) { - // calculate box size from gd font - $box = new Rectangle(0, 0); - $chars = mb_strlen($text); - if ($chars > 0) { - $box->setWidth($chars * $this->getGdFontWidth()); - $box->setHeight($this->getGdFontHeight()); - } - return $box; - } - - // calculate box size from font file with angle 0 - $box = imageftbbox( - $this->adjustedSize(), - 0, - $this->font->filename(), - $text - ); - - // build polygon from points - $polygon = new Polygon(); - $polygon->addPoint(new Point($box[6], $box[7])); - $polygon->addPoint(new Point($box[4], $box[5])); - $polygon->addPoint(new Point($box[2], $box[3])); - $polygon->addPoint(new Point($box[0], $box[1])); - - return $polygon; - } - - public function adjustedSize(): float - { - return floatval(ceil($this->font->size() * .75)); - } - - public function getGdFont(): int - { - if (is_numeric($this->font->filename())) { - return intval($this->font->filename()); - } - - return 1; - } - - protected function getGdFontWidth(): int - { - return $this->getGdFont() + 4; - } - - protected function getGdFontHeight(): int - { - switch ($this->getGdFont()) { - case 2: - return 14; - - case 3: - return 14; - - case 4: - return 16; - - case 5: - return 16; - - default: - case 1: - return 8; - } - } -} diff --git a/src/Drivers/Gd/Modifiers/TextModifier.php b/src/Drivers/Gd/Modifiers/TextModifier.php index 139d8cbf..7129fd07 100644 --- a/src/Drivers/Gd/Modifiers/TextModifier.php +++ b/src/Drivers/Gd/Modifiers/TextModifier.php @@ -2,10 +2,11 @@ namespace Intervention\Image\Drivers\Gd\Modifiers; -use Intervention\Image\Drivers\DriverModifier; -use Intervention\Image\Drivers\Gd\FontProcessor; +use Intervention\Image\Drivers\AbstractTextModifier; use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Geometry\Point; +use Intervention\Image\Geometry\Polygon; +use Intervention\Image\Geometry\Rectangle; use Intervention\Image\Interfaces\FontInterface; /** @@ -13,12 +14,11 @@ * @property string $text * @property FontInterface $font */ -class TextModifier extends DriverModifier +class TextModifier extends AbstractTextModifier { public function apply(ImageInterface $image): ImageInterface { - $processor = $this->fontProcessor(); - $lines = $processor->alignedTextBlock($this->position, $this->text); + $lines = $this->alignedTextBlock($this->position, $this->text); $color = $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->driver()->handleInput($this->font->color()) @@ -29,7 +29,7 @@ public function apply(ImageInterface $image): ImageInterface foreach ($lines as $line) { imagettftext( $frame->native(), - $processor->adjustedSize(), + $this->adjustedSize(), $this->font->angle() * -1, $line->position()->x(), $line->position()->y(), @@ -42,7 +42,7 @@ public function apply(ImageInterface $image): ImageInterface foreach ($lines as $line) { imagestring( $frame->native(), - $processor->getGdFont(), + $this->getGdFont(), $line->position()->x(), $line->position()->y(), $line, @@ -55,8 +55,79 @@ public function apply(ImageInterface $image): ImageInterface return $image; } - private function fontProcessor(): FontProcessor + /** + * Calculate size of bounding box of given text + * + * @return Polygon + */ + protected function boxSize(string $text): Polygon { - return $this->driver()->fontProcessor($this->font); + if (!$this->font->hasFilename()) { + // calculate box size from gd font + $box = new Rectangle(0, 0); + $chars = mb_strlen($text); + if ($chars > 0) { + $box->setWidth($chars * $this->getGdFontWidth()); + $box->setHeight($this->getGdFontHeight()); + } + return $box; + } + + // calculate box size from font file with angle 0 + $box = imageftbbox( + $this->adjustedSize(), + 0, + $this->font->filename(), + $text + ); + + // build polygon from points + $polygon = new Polygon(); + $polygon->addPoint(new Point($box[6], $box[7])); + $polygon->addPoint(new Point($box[4], $box[5])); + $polygon->addPoint(new Point($box[2], $box[3])); + $polygon->addPoint(new Point($box[0], $box[1])); + + return $polygon; + } + + private function adjustedSize(): float + { + return floatval(ceil($this->font->size() * .75)); + } + + private function getGdFont(): int + { + if (is_numeric($this->font->filename())) { + return intval($this->font->filename()); + } + + return 1; + } + + private function getGdFontWidth(): int + { + return $this->getGdFont() + 4; + } + + private function getGdFontHeight(): int + { + switch ($this->getGdFont()) { + case 2: + return 14; + + case 3: + return 14; + + case 4: + return 16; + + case 5: + return 16; + + default: + case 1: + return 8; + } } } diff --git a/src/Drivers/Imagick/Driver.php b/src/Drivers/Imagick/Driver.php index 080a6ff9..9c1ea5d0 100644 --- a/src/Drivers/Imagick/Driver.php +++ b/src/Drivers/Imagick/Driver.php @@ -9,8 +9,6 @@ use Intervention\Image\Interfaces\ColorInterface; use Intervention\Image\Interfaces\ColorProcessorInterface; use Intervention\Image\Interfaces\ColorspaceInterface; -use Intervention\Image\Interfaces\FontInterface; -use Intervention\Image\Interfaces\FontProcessorInterface; use Intervention\Image\Interfaces\ImageInterface; class Driver extends AbstractDriver @@ -43,9 +41,4 @@ public function colorProcessor(ColorspaceInterface $colorspace): ColorProcessorI { return new ColorProcessor($colorspace); } - - public function fontProcessor(FontInterface $font): FontProcessorInterface - { - return new FontProcessor($font); - } } diff --git a/src/Drivers/Imagick/FontProcessor.php b/src/Drivers/Imagick/FontProcessor.php deleted file mode 100644 index 387ba4e3..00000000 --- a/src/Drivers/Imagick/FontProcessor.php +++ /dev/null @@ -1,57 +0,0 @@ -toImagickDraw(); - $draw->setStrokeAntialias(true); - $draw->setTextAntialias(true); - $dimensions = (new Imagick())->queryFontMetrics($draw, $text); - - return (new Rectangle( - intval(round($dimensions['textWidth'])), - intval(round($dimensions['ascender'] + $dimensions['descender'])), - )); - } - - public function toImagickDraw(?ImagickPixel $color = null): ImagickDraw - { - if (!$this->font->hasFilename()) { - throw new FontException('No font file specified.'); - } - - $draw = new ImagickDraw(); - $draw->setStrokeAntialias(true); - $draw->setTextAntialias(true); - $draw->setFont($this->font->filename()); - $draw->setFontSize($this->font->size()); - $draw->setTextAlignment(Imagick::ALIGN_LEFT); - - if ($color) { - $draw->setFillColor($color); - } - - return $draw; - } -} diff --git a/src/Drivers/Imagick/Modifiers/TextModifier.php b/src/Drivers/Imagick/Modifiers/TextModifier.php index f64478a8..068d1391 100644 --- a/src/Drivers/Imagick/Modifiers/TextModifier.php +++ b/src/Drivers/Imagick/Modifiers/TextModifier.php @@ -2,9 +2,14 @@ namespace Intervention\Image\Drivers\Imagick\Modifiers; -use Intervention\Image\Drivers\DriverModifier; -use Intervention\Image\Drivers\Imagick\FontProcessor; +use Imagick; +use ImagickDraw; +use ImagickPixel; +use Intervention\Image\Drivers\AbstractTextModifier; +use Intervention\Image\Exceptions\FontException; use Intervention\Image\Geometry\Point; +use Intervention\Image\Geometry\Polygon; +use Intervention\Image\Geometry\Rectangle; use Intervention\Image\Interfaces\FontInterface; use Intervention\Image\Interfaces\ImageInterface; @@ -13,18 +18,17 @@ * @property string $text * @property FontInterface $font */ -class TextModifier extends DriverModifier +class TextModifier extends AbstractTextModifier { public function apply(ImageInterface $image): ImageInterface { - $processor = $this->fontProcessor(); - $lines = $processor->alignedTextBlock($this->position, $this->text); + $lines = $this->alignedTextBlock($this->position, $this->text); $color = $this->driver()->colorProcessor($image->colorspace())->colorToNative( $this->driver()->handleInput($this->font->color()) ); - $draw = $processor->toImagickDraw($color); + $draw = $this->toImagickDraw($color); foreach ($image as $frame) { foreach ($lines as $line) { @@ -41,8 +45,46 @@ public function apply(ImageInterface $image): ImageInterface return $image; } - private function fontProcessor(): FontProcessor + /** + * Calculate box size of current font + * + * @return Polygon + */ + protected function boxSize(string $text): Polygon { - return $this->driver()->fontProcessor($this->font); + // no text - no box size + if (mb_strlen($text) === 0) { + return (new Rectangle(0, 0)); + } + + $draw = $this->toImagickDraw(); + $draw->setStrokeAntialias(true); + $draw->setTextAntialias(true); + $dimensions = (new Imagick())->queryFontMetrics($draw, $text); + + return (new Rectangle( + intval(round($dimensions['textWidth'])), + intval(round($dimensions['ascender'] + $dimensions['descender'])), + )); + } + + private function toImagickDraw(?ImagickPixel $color = null): ImagickDraw + { + if (!$this->font->hasFilename()) { + throw new FontException('No font file specified.'); + } + + $draw = new ImagickDraw(); + $draw->setStrokeAntialias(true); + $draw->setTextAntialias(true); + $draw->setFont($this->font->filename()); + $draw->setFontSize($this->font->size()); + $draw->setTextAlignment(Imagick::ALIGN_LEFT); + + if ($color) { + $draw->setFillColor($color); + } + + return $draw; } } diff --git a/src/Interfaces/DriverInterface.php b/src/Interfaces/DriverInterface.php index 049770dc..cb628b5c 100644 --- a/src/Interfaces/DriverInterface.php +++ b/src/Interfaces/DriverInterface.php @@ -9,5 +9,4 @@ public function resolve(object $input): object; public function createImage(int $width, int $height): ImageInterface; public function handleInput(mixed $input): ImageInterface|ColorInterface; public function colorProcessor(ColorspaceInterface $colorspace): ColorProcessorInterface; - public function fontProcessor(FontInterface $font): FontProcessorInterface; } diff --git a/src/Interfaces/FontProcessorInterface.php b/src/Interfaces/FontProcessorInterface.php deleted file mode 100644 index 12981091..00000000 --- a/src/Interfaces/FontProcessorInterface.php +++ /dev/null @@ -1,17 +0,0 @@ -shouldAllowMockingProtectedMethods() - ->makePartial(); - - $mock->shouldReceive('boxSize')->with('Hy')->andReturn(new Rectangle(123, 456)); - $mock->shouldReceive('boxSize')->with('T')->andReturn(new Rectangle(12, 34)); - $mock->shouldReceive('boxSize')->with('foobar')->andReturn(new Rectangle(4, 8)); - - return $mock; - } - - public function testLeadingInPixels(): void - { - $mock = $this->getMock((new Font())->setLineHeight(2)); - $this->assertEquals(912, $mock->leadingInPixels()); - } - - public function testCapHeight(): void - { - $mock = $this->getMock((new Font())->setLineHeight(2)); - $this->assertEquals(34, $mock->capHeight()); - } - - public function testFontSizeInPixels(): void - { - $mock = $this->getMock((new Font())->setLineHeight(2)); - $this->assertEquals(456, $mock->fontSizeInPixels()); - } - - public function testAlignedTextBlock(): void - { - $mock = $this->getMock((new Font())->setLineHeight(2)); - $block = $mock->alignedTextBlock(new Point(0, 0), 'foobar'); - $this->assertInstanceOf(TextBlock::class, $block); - } - - public function testBoundingBox(): void - { - $mock = $this->getMock((new Font())->setLineHeight(2)); - $box = $mock->boundingBox(new TextBlock('foobar')); - $this->assertInstanceOf(Polygon::class, $box); - $this->assertEquals(4, $box->width()); - $this->assertEquals(34, $box->height()); - } -} diff --git a/tests/Drivers/Gd/FontProcessorTest.php b/tests/Drivers/Gd/FontProcessorTest.php deleted file mode 100644 index 2f62d030..00000000 --- a/tests/Drivers/Gd/FontProcessorTest.php +++ /dev/null @@ -1,38 +0,0 @@ -boxSize('test'); - $this->assertInstanceOf(Polygon::class, $result); - $this->assertEquals(20, $result->width()); - $this->assertEquals(8, $result->height()); - } - - public function testAdjustedSize(): void - { - $processor = new FontProcessor((new Font())->setSize(100)); - $this->assertEquals(75, $processor->adjustedSize()); - } - - public function testGetGdFont(): void - { - $processor = new FontProcessor(new Font()); - $this->assertEquals(1, $processor->getGdFont()); - - $processor = new FontProcessor((new Font())->setFilename(100)); - $this->assertEquals(100, $processor->getGdFont()); - - $processor = new FontProcessor((new Font())->setFilename('foo')); - $this->assertEquals(1, $processor->getGdFont()); - } -}