mirror of
https://github.com/Intervention/image.git
synced 2025-08-28 08:09:54 +02:00
Extended Textwriter to handle multi line text
This commit is contained in:
@@ -2,9 +2,13 @@
|
||||
|
||||
namespace Intervention\Image\Drivers\Abstract;
|
||||
|
||||
use Intervention\Image\Geometry\Point;
|
||||
use Intervention\Image\Geometry\Polygon;
|
||||
use Intervention\Image\Geometry\Size;
|
||||
use Intervention\Image\Interfaces\ColorInterface;
|
||||
use Intervention\Image\Interfaces\FontInterface;
|
||||
use Intervention\Image\Traits\CanHandleInput;
|
||||
use Intervention\Image\Typography\TextBlock;
|
||||
|
||||
abstract class AbstractFont implements FontInterface
|
||||
{
|
||||
|
@@ -15,7 +15,7 @@ class Font extends AbstractFont
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate size of bounding box of current text
|
||||
* Calculate size of bounding box of given text
|
||||
*
|
||||
* @return Polygon
|
||||
*/
|
||||
|
@@ -5,84 +5,51 @@ 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\Polygon;
|
||||
use Intervention\Image\Geometry\Size;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
|
||||
class TextWriter extends AbstractTextWriter
|
||||
{
|
||||
public function apply(ImageInterface $image): ImageInterface
|
||||
{
|
||||
$box = $this->getBoundingBox();
|
||||
$position = clone $box->last();
|
||||
$leading = $this->getFont()->leadingInPixels();
|
||||
$lines = $this->getTextBlock();
|
||||
$boundingBox = $lines->getBoundingBox($this->getFont(), $this->position);
|
||||
$lines->alignByFont($this->getFont(), $boundingBox->last());
|
||||
|
||||
foreach ($image as $frame) {
|
||||
if ($this->font->hasFilename()) {
|
||||
$position->moveY($this->getFont()->capHeight());
|
||||
$posx = $position->getX();
|
||||
$posy = $position->getY();
|
||||
foreach ($this->getTextBlock() as $line) {
|
||||
foreach ($lines as $line) {
|
||||
imagettftext(
|
||||
$frame->getCore(),
|
||||
$this->getFont()->getSize(),
|
||||
$this->getFont()->getAngle() * (-1),
|
||||
$posx,
|
||||
$posy,
|
||||
$line->getPosition()->getX(),
|
||||
$line->getPosition()->getY(),
|
||||
$this->getFont()->getColor()->toInt(),
|
||||
$this->getFont()->getFilename(),
|
||||
$line
|
||||
);
|
||||
$posy += $leading;
|
||||
}
|
||||
|
||||
// debug
|
||||
imagepolygon($frame->getCore(), $box->toArray(), 0);
|
||||
imagepolygon($frame->getCore(), $boundingBox->toArray(), 0);
|
||||
} else {
|
||||
imagestring(
|
||||
$frame->getCore(),
|
||||
$this->getFont()->getGdFont(),
|
||||
$position->getX(),
|
||||
$position->getY(),
|
||||
$this->text,
|
||||
$this->font->getColor()->toInt()
|
||||
);
|
||||
foreach ($lines as $line) {
|
||||
imagestring(
|
||||
$frame->getCore(),
|
||||
$this->getFont()->getGdFont(),
|
||||
$line->getPosition()->getX(),
|
||||
$line->getPosition()->getY(),
|
||||
$line,
|
||||
$this->font->getColor()->toInt()
|
||||
);
|
||||
imagepolygon($frame->getCore(), $boundingBox->toArray(), 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $image;
|
||||
}
|
||||
|
||||
private function getBoundingBox(): Polygon
|
||||
{
|
||||
$size = new Size(
|
||||
$this->getTextBlock()->longestLine()->width($this->font),
|
||||
$this->getFont()->leadingInPixels() * $this->getTextBlock()->count()
|
||||
);
|
||||
|
||||
$poly = $size->toPolygon();
|
||||
$poly->setPivotPoint($this->position);
|
||||
$poly->align($this->getFont()->getAlign());
|
||||
$poly->valign($this->getFont()->getValign());
|
||||
|
||||
return $poly;
|
||||
}
|
||||
|
||||
// private function getAlignedPosition(): Point
|
||||
// {
|
||||
// $poly = $this->font->getBoxSize($this->text);
|
||||
// $poly->setPivotPoint($this->position);
|
||||
//
|
||||
// $poly->align($this->font->getAlign());
|
||||
// $poly->valign($this->font->getValign());
|
||||
//
|
||||
// if ($this->font->getAngle() != 0) {
|
||||
// $poly->rotate($this->font->getAngle());
|
||||
// }
|
||||
//
|
||||
// return $poly->last();
|
||||
// }
|
||||
|
||||
private function getFont(): Font
|
||||
{
|
||||
if (!is_a($this->font, Font::class)) {
|
||||
|
@@ -40,6 +40,34 @@ class Size implements SizeInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addWidth(int $value): SizeInterface
|
||||
{
|
||||
$this->width = $this->width + $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function subWidth(int $value): SizeInterface
|
||||
{
|
||||
$this->width = $this->width - $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function addHeight(int $value): SizeInterface
|
||||
{
|
||||
$this->height = $this->height + $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function subHeight(int $value): SizeInterface
|
||||
{
|
||||
$this->height = $this->height - $value;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get current pivot point
|
||||
*
|
||||
|
@@ -19,7 +19,14 @@ class Line
|
||||
return $this->position;
|
||||
}
|
||||
|
||||
public function width(FontInterface $font): int
|
||||
public function setPosition(Point $point): self
|
||||
{
|
||||
$this->position = $point;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function widthInFont(FontInterface $font): int
|
||||
{
|
||||
return $font->getBoxSize($this->text)->width();
|
||||
}
|
||||
|
@@ -3,6 +3,10 @@
|
||||
namespace Intervention\Image\Typography;
|
||||
|
||||
use Intervention\Image\Collection;
|
||||
use Intervention\Image\Geometry\Point;
|
||||
use Intervention\Image\Geometry\Polygon;
|
||||
use Intervention\Image\Geometry\Size;
|
||||
use Intervention\Image\Interfaces\FontInterface;
|
||||
|
||||
class TextBlock extends Collection
|
||||
{
|
||||
@@ -13,11 +17,83 @@ class TextBlock extends Collection
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set position of each line in text block
|
||||
* according to given font settings.
|
||||
*
|
||||
* @param FontInterface $font
|
||||
* @param Point $pivot
|
||||
* @return TextBlock
|
||||
*/
|
||||
public function alignByFont(FontInterface $font, Point $pivot = null): self
|
||||
{
|
||||
$pivot = $pivot ? $pivot : new Point();
|
||||
|
||||
$leading = $font->leadingInPixels();
|
||||
$x = $pivot->getX();
|
||||
$y = $font->hasFilename() ? $pivot->getY() + $font->capHeight() : $pivot->getY();
|
||||
|
||||
$x_adjustment = 0;
|
||||
$total_width = $this->longestLine()->widthInFont($font);
|
||||
foreach ($this as $line) {
|
||||
$x_adjustment = $font->getAlign() == 'left' ? 0 : $total_width - $line->widthInFont($font);
|
||||
$x_adjustment = $font->getAlign() == 'right' ? intval(round($x_adjustment)) : $x_adjustment;
|
||||
$x_adjustment = $font->getAlign() == 'center' ? intval(round($x_adjustment / 2)) : $x_adjustment;
|
||||
$position = new Point($x + $x_adjustment, $y);
|
||||
$position->rotate($font->getAngle(), $pivot);
|
||||
$line->setPosition($position);
|
||||
$y += $leading;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getBoundingBox(FontInterface $font, Point $pivot = null): Polygon
|
||||
{
|
||||
$pivot = $pivot ? $pivot : new Point();
|
||||
|
||||
// bounding box
|
||||
$box = (new Size(
|
||||
$this->longestLine()->widthInFont($font),
|
||||
$font->leadingInPixels() * ($this->count() - 1) + $font->capHeight()
|
||||
))->toPolygon();
|
||||
|
||||
// set pivot
|
||||
$box->setPivotPoint($pivot);
|
||||
|
||||
// align
|
||||
$box->align($font->getAlign());
|
||||
$box->valign($font->getValign());
|
||||
|
||||
$box->rotate($font->getAngle());
|
||||
|
||||
return $box;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return array of lines in text block
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function lines(): array
|
||||
{
|
||||
return $this->items;
|
||||
}
|
||||
|
||||
public function getLine($key): ?Line
|
||||
{
|
||||
if (!array_key_exists($key, $this->lines())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $this->lines()[$key];
|
||||
}
|
||||
|
||||
/**
|
||||
* Return line with most characters of text block
|
||||
*
|
||||
* @return Line
|
||||
*/
|
||||
public function longestLine(): Line
|
||||
{
|
||||
$lines = $this->lines();
|
||||
|
30
tests/Drivers/Gd/FontTest.php
Normal file
30
tests/Drivers/Gd/FontTest.php
Normal file
@@ -0,0 +1,30 @@
|
||||
<?php
|
||||
|
||||
namespace Intervention\Image\Tests\Drivers\Gd;
|
||||
|
||||
use Intervention\Image\Drivers\Gd\Font;
|
||||
use Intervention\Image\Tests\TestCase;
|
||||
|
||||
class FontTest extends TestCase
|
||||
{
|
||||
public function testGetSize(): void
|
||||
{
|
||||
$font = new Font();
|
||||
$font->size(12);
|
||||
$this->assertEquals(9, $font->getSize());
|
||||
}
|
||||
|
||||
public function testGetGdFont(): void
|
||||
{
|
||||
$font = new Font();
|
||||
$this->assertEquals(1, $font->getGdFont());
|
||||
$font->filename(12);
|
||||
$this->assertEquals(12, $font->getGdFont());
|
||||
}
|
||||
|
||||
public function testCapHeight(): void
|
||||
{
|
||||
$font = new Font();
|
||||
$this->assertEquals(8, $font->capHeight());
|
||||
}
|
||||
}
|
@@ -81,5 +81,10 @@ class PointTest extends TestCase
|
||||
$point->rotate(90, new Point(0, 0));
|
||||
$this->assertEquals(-200, $point->getX());
|
||||
$this->assertEquals(300, $point->getY());
|
||||
|
||||
$point = new Point(0, 74);
|
||||
$point->rotate(45, new Point(0, 0));
|
||||
$this->assertEquals(-52, $point->getX());
|
||||
$this->assertEquals(52, $point->getY());
|
||||
}
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace Intervention\Image\Tests\Typography;
|
||||
|
||||
use Intervention\Image\Geometry\Point;
|
||||
use Intervention\Image\Tests\TestCase;
|
||||
use Intervention\Image\Typography\Line;
|
||||
|
||||
@@ -18,4 +19,15 @@ class LineTest extends TestCase
|
||||
$line = new Line('foo');
|
||||
$this->assertEquals('foo', (string) $line);
|
||||
}
|
||||
|
||||
public function testSetGetPosition(): void
|
||||
{
|
||||
$line = new Line('foo');
|
||||
$this->assertEquals(0, $line->getPosition()->getX());
|
||||
$this->assertEquals(0, $line->getPosition()->getY());
|
||||
|
||||
$line->setPosition(new Point(10, 11));
|
||||
$this->assertEquals(10, $line->getPosition()->getX());
|
||||
$this->assertEquals(11, $line->getPosition()->getY());
|
||||
}
|
||||
}
|
||||
|
@@ -4,6 +4,10 @@ namespace Intervention\Image\Tests\Typography;
|
||||
|
||||
use Intervention\Image\Tests\TestCase;
|
||||
use Intervention\Image\Typography\TextBlock;
|
||||
use Intervention\Image\Drivers\Abstract\AbstractFont;
|
||||
use Intervention\Image\Geometry\Point;
|
||||
use Intervention\Image\Geometry\Polygon;
|
||||
use Mockery;
|
||||
|
||||
class TextBlockTest extends TestCase
|
||||
{
|
||||
@@ -11,10 +15,11 @@ class TextBlockTest extends TestCase
|
||||
{
|
||||
return new TextBlock(<<<EOF
|
||||
foo
|
||||
FooBar
|
||||
bar
|
||||
baz
|
||||
EOF);
|
||||
}
|
||||
|
||||
public function testConstructor(): void
|
||||
{
|
||||
$block = $this->getTestBlock();
|
||||
@@ -27,4 +32,50 @@ class TextBlockTest extends TestCase
|
||||
$block = $this->getTestBlock();
|
||||
$this->assertCount(3, $block->lines());
|
||||
}
|
||||
|
||||
public function testGetLine(): void
|
||||
{
|
||||
$block = $this->getTestBlock();
|
||||
$this->assertEquals('foo', $block->getLine(0));
|
||||
$this->assertEquals('FooBar', $block->getLine(1));
|
||||
$this->assertEquals('bar', $block->getLine(2));
|
||||
}
|
||||
|
||||
public function testAlignByFont(): void
|
||||
{
|
||||
$font = Mockery::mock(AbstractFont::class)
|
||||
->shouldAllowMockingProtectedMethods()
|
||||
->makePartial();
|
||||
|
||||
$font->shouldReceive('getBoxSize')->andReturn(
|
||||
new Polygon([
|
||||
new Point(-1, -29),
|
||||
new Point(141, -29),
|
||||
new Point(141, 98),
|
||||
new Point(-1, 98),
|
||||
])
|
||||
);
|
||||
|
||||
// $font->shouldReceive('capHeight')->andReturn(22);
|
||||
|
||||
$font->shouldReceive('leadingInPixels')->andReturn(74);
|
||||
$font->angle(45);
|
||||
|
||||
$block = $this->getTestBlock(); // before
|
||||
$this->assertEquals(0, $block->getLine(0)->getPosition()->getX());
|
||||
$this->assertEquals(0, $block->getLine(0)->getPosition()->getY());
|
||||
$this->assertEquals(0, $block->getLine(1)->getPosition()->getX());
|
||||
$this->assertEquals(0, $block->getLine(1)->getPosition()->getY());
|
||||
$this->assertEquals(0, $block->getLine(2)->getPosition()->getX());
|
||||
$this->assertEquals(0, $block->getLine(2)->getPosition()->getY());
|
||||
|
||||
$result = $block->alignByFont($font); // after
|
||||
$this->assertInstanceOf(TextBlock::class, $result);
|
||||
$this->assertEquals(0, $block->getLine(0)->getPosition()->getX());
|
||||
$this->assertEquals(0, $block->getLine(0)->getPosition()->getY());
|
||||
$this->assertEquals(-52, $block->getLine(1)->getPosition()->getX());
|
||||
$this->assertEquals(52, $block->getLine(1)->getPosition()->getY());
|
||||
$this->assertEquals(-104, $block->getLine(2)->getPosition()->getX());
|
||||
$this->assertEquals(104, $block->getLine(2)->getPosition()->getY());
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user