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:
@@ -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;
|
||||||
|
@@ -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);
|
||||||
}
|
}
|
||||||
|
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
|
* @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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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(
|
||||||
|
@@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -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
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()
|
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());
|
||||||
|
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