mirror of
https://github.com/Intervention/image.git
synced 2025-08-01 11:30:16 +02:00
Prepare TextWriter for multi line functionality
This commit is contained in:
@@ -17,18 +17,13 @@ abstract class AbstractFont implements FontInterface
|
||||
protected $align = 'left';
|
||||
protected $valign = 'bottom';
|
||||
|
||||
public function __construct(protected string $text, ?callable $init = null)
|
||||
public function __construct(callable $init = null)
|
||||
{
|
||||
if (is_callable($init)) {
|
||||
$init($this);
|
||||
}
|
||||
}
|
||||
|
||||
public function getText(): string
|
||||
{
|
||||
return $this->text;
|
||||
}
|
||||
|
||||
public function size(float $size): FontInterface
|
||||
{
|
||||
$this->size = $size;
|
||||
|
@@ -194,8 +194,8 @@ abstract class AbstractImage implements ImageInterface
|
||||
|
||||
public function text(string $text, int $x, int $y, ?callable $init = null): ImageInterface
|
||||
{
|
||||
$font = $this->resolveDriverClass('Font', $text, $init);
|
||||
$modifier = $this->resolveDriverClass('Modifiers\TextWriter', new Point($x, $y), $font);
|
||||
$font = $this->resolveDriverClass('Font', $init);
|
||||
$modifier = $this->resolveDriverClass('Modifiers\TextWriter', new Point($x, $y), $font, $text);
|
||||
|
||||
return $this->modify($modifier);
|
||||
}
|
||||
|
18
src/Drivers/Abstract/AbstractTextWriter.php
Normal file
18
src/Drivers/Abstract/AbstractTextWriter.php
Normal 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
|
||||
) {
|
||||
//
|
||||
}
|
||||
}
|
@@ -19,12 +19,12 @@ class Font extends AbstractFont
|
||||
*
|
||||
* @return Polygon
|
||||
*/
|
||||
public function getBoxSize(): Polygon
|
||||
public function getBoxSize(string $text): Polygon
|
||||
{
|
||||
if (!$this->hasFilename()) {
|
||||
// calculate box size from gd font
|
||||
$box = new Size(0, 0);
|
||||
$chars = mb_strlen($this->getText());
|
||||
$chars = mb_strlen($text);
|
||||
if ($chars > 0) {
|
||||
$box->setWidth($chars * $this->getGdFontWidth());
|
||||
$box->setHeight($this->getGdFontHeight());
|
||||
@@ -37,7 +37,7 @@ class Font extends AbstractFont
|
||||
$this->getSize(),
|
||||
0,
|
||||
$this->getFilename(),
|
||||
$this->getText()
|
||||
$text
|
||||
);
|
||||
|
||||
// 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[0], $box[1]));
|
||||
|
||||
|
||||
return $polygon;
|
||||
}
|
||||
|
||||
|
@@ -2,20 +2,14 @@
|
||||
|
||||
namespace Intervention\Image\Drivers\Gd\Modifiers;
|
||||
|
||||
use Intervention\Image\Drivers\Abstract\AbstractTextWriter;
|
||||
use Intervention\Image\Drivers\Gd\Font;
|
||||
use Intervention\Image\Exceptions\FontException;
|
||||
use Intervention\Image\Geometry\Point;
|
||||
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
|
||||
{
|
||||
$position = $this->getAlignedPosition();
|
||||
@@ -23,21 +17,21 @@ class TextWriter implements ModifierInterface
|
||||
if ($this->font->hasFilename()) {
|
||||
imagettftext(
|
||||
$frame->getCore(),
|
||||
$this->font->getSize(),
|
||||
$this->font->getAngle() * (-1),
|
||||
$this->getFont()->getSize(),
|
||||
$this->getFont()->getAngle() * (-1),
|
||||
$position->getX(),
|
||||
$position->getY(),
|
||||
$this->font->getColor()->toInt(),
|
||||
$this->font->getFilename(),
|
||||
$this->font->getText()
|
||||
$this->getFont()->getColor()->toInt(),
|
||||
$this->getFont()->getFilename(),
|
||||
$this->text
|
||||
);
|
||||
} else {
|
||||
imagestring(
|
||||
$frame->getCore(),
|
||||
$this->font->getGdFont(),
|
||||
$this->getFont()->getGdFont(),
|
||||
$position->getX(),
|
||||
$position->getY(),
|
||||
$this->font->getText(),
|
||||
$this->text,
|
||||
$this->font->getColor()->toInt()
|
||||
);
|
||||
}
|
||||
@@ -46,9 +40,9 @@ class TextWriter implements ModifierInterface
|
||||
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->align($this->font->getAlign());
|
||||
@@ -60,4 +54,12 @@ class TextWriter implements ModifierInterface
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@@ -5,10 +5,10 @@ namespace Intervention\Image\Drivers\Imagick;
|
||||
use Imagick;
|
||||
use ImagickDraw;
|
||||
use Intervention\Image\Drivers\Abstract\AbstractFont;
|
||||
use Intervention\Image\Exceptions\DecoderException;
|
||||
use Intervention\Image\Exceptions\FontException;
|
||||
use Intervention\Image\Geometry\Polygon;
|
||||
use Intervention\Image\Geometry\Size;
|
||||
use Intervention\Image\Interfaces\ColorInterface;
|
||||
|
||||
class Font extends AbstractFont
|
||||
{
|
||||
@@ -18,22 +18,28 @@ class Font extends AbstractFont
|
||||
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->setStrokeAntialias(true);
|
||||
$draw->setTextAntialias(true);
|
||||
$draw->setFont($this->getFilename());
|
||||
$draw->setFontSize($this->getSize());
|
||||
$draw->setFillColor($color->getPixel());
|
||||
$draw->setFillColor($this->getColor()->getPixel());
|
||||
$draw->setTextAlignment($this->getImagickAlign());
|
||||
|
||||
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
|
||||
{
|
||||
return parent::getAngle() * (-1);
|
||||
@@ -57,16 +63,16 @@ class Font extends AbstractFont
|
||||
*
|
||||
* @return Polygon
|
||||
*/
|
||||
public function getBoxSize(): Polygon
|
||||
public function getBoxSize(string $text): Polygon
|
||||
{
|
||||
// no text - no box size
|
||||
if (mb_strlen($this->getText()) === 0) {
|
||||
if (mb_strlen($text) === 0) {
|
||||
return (new Size(0, 0))->toPolygon();
|
||||
}
|
||||
|
||||
$dimensions = (new Imagick())->queryFontMetrics(
|
||||
$this->toImagickDraw(),
|
||||
$this->getText()
|
||||
$text
|
||||
);
|
||||
|
||||
return (new Size(
|
||||
|
@@ -2,30 +2,24 @@
|
||||
|
||||
namespace Intervention\Image\Drivers\Imagick\Modifiers;
|
||||
|
||||
use Intervention\Image\Drivers\Abstract\AbstractTextWriter;
|
||||
use Intervention\Image\Drivers\Imagick\Font;
|
||||
use Intervention\Image\Exceptions\FontException;
|
||||
use Intervention\Image\Geometry\Point;
|
||||
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
|
||||
{
|
||||
$position = $this->getAlignedPosition();
|
||||
foreach ($image as $frame) {
|
||||
$frame->getCore()->annotateImage(
|
||||
$this->font->toImagickDraw(),
|
||||
$this->getFont()->toImagickDraw(),
|
||||
$position->getX(),
|
||||
$position->getY(),
|
||||
$this->font->getAngle() * (-1),
|
||||
$this->font->getText()
|
||||
$this->getFont()->getAngle() * (-1),
|
||||
$this->text
|
||||
);
|
||||
}
|
||||
|
||||
@@ -35,10 +29,10 @@ class TextWriter implements ModifierInterface
|
||||
protected function getAlignedPosition(): Point
|
||||
{
|
||||
$position = $this->position;
|
||||
$box = $this->font->getBoxSize();
|
||||
$box = $this->getFont()->getBoxSize($this->text);
|
||||
|
||||
// adjust y pos
|
||||
switch ($this->font->getValign()) {
|
||||
switch ($this->getFont()->getValign()) {
|
||||
case 'top':
|
||||
$position->setY($position->getY() + $box->height());
|
||||
break;
|
||||
@@ -51,4 +45,12 @@ class TextWriter implements ModifierInterface
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@@ -7,7 +7,6 @@ use Intervention\Image\Interfaces\ColorInterface;
|
||||
|
||||
interface FontInterface
|
||||
{
|
||||
public function getText(): string;
|
||||
public function color($color): self;
|
||||
public function getColor(): ?ColorInterface;
|
||||
public function size(float $size): self;
|
||||
@@ -19,5 +18,7 @@ interface FontInterface
|
||||
public function hasFilename(): bool;
|
||||
public function align(string $align): self;
|
||||
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
16
src/Typography/Line.php
Normal 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;
|
||||
}
|
||||
}
|
20
src/Typography/TextBlock.php
Normal file
20
src/Typography/TextBlock.php
Normal 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;
|
||||
}
|
||||
}
|
@@ -12,7 +12,7 @@ class AbstractFontTest extends TestCase
|
||||
private function getAbstractFontMock()
|
||||
{
|
||||
// create mock
|
||||
$mock = Mockery::mock(AbstractFont::class, ['test123'])
|
||||
$mock = Mockery::mock(AbstractFont::class)
|
||||
->shouldAllowMockingProtectedMethods()
|
||||
->makePartial();
|
||||
|
||||
@@ -34,7 +34,6 @@ class AbstractFontTest extends TestCase
|
||||
public function testConstructor(): void
|
||||
{
|
||||
$mock = $this->getAbstractFontMock();
|
||||
$this->assertEquals('test123', $mock->getText());
|
||||
$this->assertEquals(24.0, $mock->getSize());
|
||||
$this->assertEquals(30, $mock->getAngle());
|
||||
$this->assertEquals(__DIR__ . '/AbstractFontTest.php', $mock->getFilename());
|
||||
|
21
tests/Typography/LineTest.php
Normal file
21
tests/Typography/LineTest.php
Normal 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);
|
||||
}
|
||||
}
|
30
tests/Typography/TextBlockTest.php
Normal file
30
tests/Typography/TextBlockTest.php
Normal 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());
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user