1
0
mirror of https://github.com/Intervention/image.git synced 2025-08-06 05:47:25 +02:00

Prepare TextWriter for multi line functionality

This commit is contained in:
Oliver Vogel
2022-06-25 12:18:46 +02:00
parent 452b91929c
commit ae3762d455
13 changed files with 167 additions and 58 deletions

View File

@@ -17,18 +17,13 @@ abstract class AbstractFont implements FontInterface
protected $align = 'left'; protected $align = 'left';
protected $valign = 'bottom'; protected $valign = 'bottom';
public function __construct(protected string $text, ?callable $init = null) public function __construct(callable $init = null)
{ {
if (is_callable($init)) { if (is_callable($init)) {
$init($this); $init($this);
} }
} }
public function getText(): string
{
return $this->text;
}
public function size(float $size): FontInterface public function size(float $size): FontInterface
{ {
$this->size = $size; $this->size = $size;

View File

@@ -194,8 +194,8 @@ abstract class AbstractImage implements ImageInterface
public function text(string $text, int $x, int $y, ?callable $init = null): ImageInterface public function text(string $text, int $x, int $y, ?callable $init = null): ImageInterface
{ {
$font = $this->resolveDriverClass('Font', $text, $init); $font = $this->resolveDriverClass('Font', $init);
$modifier = $this->resolveDriverClass('Modifiers\TextWriter', new Point($x, $y), $font); $modifier = $this->resolveDriverClass('Modifiers\TextWriter', new Point($x, $y), $font, $text);
return $this->modify($modifier); return $this->modify($modifier);
} }

View File

@@ -0,0 +1,18 @@
<?php
namespace Intervention\Image\Drivers\Abstract;
use Intervention\Image\Geometry\Point;
use Intervention\Image\Interfaces\FontInterface;
use Intervention\Image\Interfaces\ModifierInterface;
abstract class AbstractTextWriter implements ModifierInterface
{
public function __construct(
protected Point $position,
protected FontInterface $font,
protected string $text
) {
//
}
}

View File

@@ -19,12 +19,12 @@ class Font extends AbstractFont
* *
* @return Polygon * @return Polygon
*/ */
public function getBoxSize(): Polygon public function getBoxSize(string $text): Polygon
{ {
if (!$this->hasFilename()) { if (!$this->hasFilename()) {
// calculate box size from gd font // calculate box size from gd font
$box = new Size(0, 0); $box = new Size(0, 0);
$chars = mb_strlen($this->getText()); $chars = mb_strlen($text);
if ($chars > 0) { if ($chars > 0) {
$box->setWidth($chars * $this->getGdFontWidth()); $box->setWidth($chars * $this->getGdFontWidth());
$box->setHeight($this->getGdFontHeight()); $box->setHeight($this->getGdFontHeight());
@@ -37,7 +37,7 @@ class Font extends AbstractFont
$this->getSize(), $this->getSize(),
0, 0,
$this->getFilename(), $this->getFilename(),
$this->getText() $text
); );
// build polygon from points // build polygon from points
@@ -47,7 +47,6 @@ class Font extends AbstractFont
$polygon->addPoint(new Point($box[2], $box[3])); $polygon->addPoint(new Point($box[2], $box[3]));
$polygon->addPoint(new Point($box[0], $box[1])); $polygon->addPoint(new Point($box[0], $box[1]));
return $polygon; return $polygon;
} }

View File

@@ -2,20 +2,14 @@
namespace Intervention\Image\Drivers\Gd\Modifiers; namespace Intervention\Image\Drivers\Gd\Modifiers;
use Intervention\Image\Drivers\Abstract\AbstractTextWriter;
use Intervention\Image\Drivers\Gd\Font; use Intervention\Image\Drivers\Gd\Font;
use Intervention\Image\Exceptions\FontException;
use Intervention\Image\Geometry\Point; use Intervention\Image\Geometry\Point;
use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Interfaces\ModifierInterface;
class TextWriter implements ModifierInterface class TextWriter extends AbstractTextWriter
{ {
public function __construct(
protected Point $position,
protected Font $font
) {
//
}
public function apply(ImageInterface $image): ImageInterface public function apply(ImageInterface $image): ImageInterface
{ {
$position = $this->getAlignedPosition(); $position = $this->getAlignedPosition();
@@ -23,21 +17,21 @@ class TextWriter implements ModifierInterface
if ($this->font->hasFilename()) { if ($this->font->hasFilename()) {
imagettftext( imagettftext(
$frame->getCore(), $frame->getCore(),
$this->font->getSize(), $this->getFont()->getSize(),
$this->font->getAngle() * (-1), $this->getFont()->getAngle() * (-1),
$position->getX(), $position->getX(),
$position->getY(), $position->getY(),
$this->font->getColor()->toInt(), $this->getFont()->getColor()->toInt(),
$this->font->getFilename(), $this->getFont()->getFilename(),
$this->font->getText() $this->text
); );
} else { } else {
imagestring( imagestring(
$frame->getCore(), $frame->getCore(),
$this->font->getGdFont(), $this->getFont()->getGdFont(),
$position->getX(), $position->getX(),
$position->getY(), $position->getY(),
$this->font->getText(), $this->text,
$this->font->getColor()->toInt() $this->font->getColor()->toInt()
); );
} }
@@ -46,9 +40,9 @@ class TextWriter implements ModifierInterface
return $image; return $image;
} }
public function getAlignedPosition(): Point private function getAlignedPosition(): Point
{ {
$poly = $this->font->getBoxSize(); $poly = $this->font->getBoxSize($this->text);
$poly->setPivotPoint($this->position); $poly->setPivotPoint($this->position);
$poly->align($this->font->getAlign()); $poly->align($this->font->getAlign());
@@ -60,4 +54,12 @@ class TextWriter implements ModifierInterface
return $poly->last(); return $poly->last();
} }
private function getFont(): Font
{
if (!is_a($this->font, Font::class)) {
throw new FontException('Font is not compatible to current driver.');
}
return $this->font;
}
} }

View File

@@ -5,10 +5,10 @@ namespace Intervention\Image\Drivers\Imagick;
use Imagick; use Imagick;
use ImagickDraw; use ImagickDraw;
use Intervention\Image\Drivers\Abstract\AbstractFont; use Intervention\Image\Drivers\Abstract\AbstractFont;
use Intervention\Image\Exceptions\DecoderException;
use Intervention\Image\Exceptions\FontException; use Intervention\Image\Exceptions\FontException;
use Intervention\Image\Geometry\Polygon; use Intervention\Image\Geometry\Polygon;
use Intervention\Image\Geometry\Size; use Intervention\Image\Geometry\Size;
use Intervention\Image\Interfaces\ColorInterface;
class Font extends AbstractFont class Font extends AbstractFont
{ {
@@ -18,22 +18,28 @@ class Font extends AbstractFont
throw new FontException('No font file specified.'); throw new FontException('No font file specified.');
} }
$color = $this->getColor();
if (!is_a($color, Color::class)) {
throw new DecoderException('Unable to decode font color.');
}
$draw = new ImagickDraw(); $draw = new ImagickDraw();
$draw->setStrokeAntialias(true); $draw->setStrokeAntialias(true);
$draw->setTextAntialias(true); $draw->setTextAntialias(true);
$draw->setFont($this->getFilename()); $draw->setFont($this->getFilename());
$draw->setFontSize($this->getSize()); $draw->setFontSize($this->getSize());
$draw->setFillColor($color->getPixel()); $draw->setFillColor($this->getColor()->getPixel());
$draw->setTextAlignment($this->getImagickAlign()); $draw->setTextAlignment($this->getImagickAlign());
return $draw; return $draw;
} }
public function getColor(): ?ColorInterface
{
$color = parent::getColor();
if (!is_a($color, Color::class)) {
throw new FontException('Font is not compatible to current driver.');
}
return $color;
}
public function getAngle(): float public function getAngle(): float
{ {
return parent::getAngle() * (-1); return parent::getAngle() * (-1);
@@ -57,16 +63,16 @@ class Font extends AbstractFont
* *
* @return Polygon * @return Polygon
*/ */
public function getBoxSize(): Polygon public function getBoxSize(string $text): Polygon
{ {
// no text - no box size // no text - no box size
if (mb_strlen($this->getText()) === 0) { if (mb_strlen($text) === 0) {
return (new Size(0, 0))->toPolygon(); return (new Size(0, 0))->toPolygon();
} }
$dimensions = (new Imagick())->queryFontMetrics( $dimensions = (new Imagick())->queryFontMetrics(
$this->toImagickDraw(), $this->toImagickDraw(),
$this->getText() $text
); );
return (new Size( return (new Size(

View File

@@ -2,30 +2,24 @@
namespace Intervention\Image\Drivers\Imagick\Modifiers; namespace Intervention\Image\Drivers\Imagick\Modifiers;
use Intervention\Image\Drivers\Abstract\AbstractTextWriter;
use Intervention\Image\Drivers\Imagick\Font; use Intervention\Image\Drivers\Imagick\Font;
use Intervention\Image\Exceptions\FontException;
use Intervention\Image\Geometry\Point; use Intervention\Image\Geometry\Point;
use Intervention\Image\Interfaces\ImageInterface; use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Interfaces\ModifierInterface;
class TextWriter implements ModifierInterface class TextWriter extends AbstractTextWriter
{ {
public function __construct(
protected Point $position,
protected Font $font
) {
//
}
public function apply(ImageInterface $image): ImageInterface public function apply(ImageInterface $image): ImageInterface
{ {
$position = $this->getAlignedPosition(); $position = $this->getAlignedPosition();
foreach ($image as $frame) { foreach ($image as $frame) {
$frame->getCore()->annotateImage( $frame->getCore()->annotateImage(
$this->font->toImagickDraw(), $this->getFont()->toImagickDraw(),
$position->getX(), $position->getX(),
$position->getY(), $position->getY(),
$this->font->getAngle() * (-1), $this->getFont()->getAngle() * (-1),
$this->font->getText() $this->text
); );
} }
@@ -35,10 +29,10 @@ class TextWriter implements ModifierInterface
protected function getAlignedPosition(): Point protected function getAlignedPosition(): Point
{ {
$position = $this->position; $position = $this->position;
$box = $this->font->getBoxSize(); $box = $this->getFont()->getBoxSize($this->text);
// adjust y pos // adjust y pos
switch ($this->font->getValign()) { switch ($this->getFont()->getValign()) {
case 'top': case 'top':
$position->setY($position->getY() + $box->height()); $position->setY($position->getY() + $box->height());
break; break;
@@ -51,4 +45,12 @@ class TextWriter implements ModifierInterface
return $position; return $position;
} }
private function getFont(): Font
{
if (!is_a($this->font, Font::class)) {
throw new FontException('Font is not compatible to current driver.');
}
return $this->font;
}
} }

View File

@@ -7,7 +7,6 @@ use Intervention\Image\Interfaces\ColorInterface;
interface FontInterface interface FontInterface
{ {
public function getText(): string;
public function color($color): self; public function color($color): self;
public function getColor(): ?ColorInterface; public function getColor(): ?ColorInterface;
public function size(float $size): self; public function size(float $size): self;
@@ -19,5 +18,7 @@ interface FontInterface
public function hasFilename(): bool; public function hasFilename(): bool;
public function align(string $align): self; public function align(string $align): self;
public function getAlign(): string; public function getAlign(): string;
public function getBoxSize(): Polygon; public function valign(string $align): self;
public function getValign(): string;
public function getBoxSize(string $text): Polygon;
} }

16
src/Typography/Line.php Normal file
View File

@@ -0,0 +1,16 @@
<?php
namespace Intervention\Image\Typography;
class Line
{
public function __construct(protected string $text)
{
//
}
public function __toString(): string
{
return $this->text;
}
}

View File

@@ -0,0 +1,20 @@
<?php
namespace Intervention\Image\Typography;
use Intervention\Image\Collection;
class TextBlock extends Collection
{
public function __construct(string $text)
{
foreach (explode("\n", $text) as $line) {
$this->push(new Line($line));
}
}
public function lines(): array
{
return $this->items;
}
}

View File

@@ -12,7 +12,7 @@ class AbstractFontTest extends TestCase
private function getAbstractFontMock() private function getAbstractFontMock()
{ {
// create mock // create mock
$mock = Mockery::mock(AbstractFont::class, ['test123']) $mock = Mockery::mock(AbstractFont::class)
->shouldAllowMockingProtectedMethods() ->shouldAllowMockingProtectedMethods()
->makePartial(); ->makePartial();
@@ -34,7 +34,6 @@ class AbstractFontTest extends TestCase
public function testConstructor(): void public function testConstructor(): void
{ {
$mock = $this->getAbstractFontMock(); $mock = $this->getAbstractFontMock();
$this->assertEquals('test123', $mock->getText());
$this->assertEquals(24.0, $mock->getSize()); $this->assertEquals(24.0, $mock->getSize());
$this->assertEquals(30, $mock->getAngle()); $this->assertEquals(30, $mock->getAngle());
$this->assertEquals(__DIR__ . '/AbstractFontTest.php', $mock->getFilename()); $this->assertEquals(__DIR__ . '/AbstractFontTest.php', $mock->getFilename());

View File

@@ -0,0 +1,21 @@
<?php
namespace Intervention\Image\Tests\Typography;
use Intervention\Image\Tests\TestCase;
use Intervention\Image\Typography\Line;
class LineTest extends TestCase
{
public function testConstructor(): void
{
$line = new Line('foo');
$this->assertInstanceOf(Line::class, $line);
}
public function testToString(): void
{
$line = new Line('foo');
$this->assertEquals('foo', (string) $line);
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Intervention\Image\Tests\Typography;
use Intervention\Image\Tests\TestCase;
use Intervention\Image\Typography\TextBlock;
class TextBlockTest extends TestCase
{
protected function getTestBlock(): TextBlock
{
return new TextBlock(<<<EOF
foo
bar
baz
EOF);
}
public function testConstructor(): void
{
$block = $this->getTestBlock();
$this->assertInstanceOf(TextBlock::class, $block);
$this->assertEquals(3, $block->count());
}
public function testLines(): void
{
$block = $this->getTestBlock();
$this->assertCount(3, $block->lines());
}
}