1
0
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:
Oliver Vogel
2022-06-26 20:09:21 +02:00
parent e1e1291fc5
commit 5dc4e66969
10 changed files with 234 additions and 54 deletions

View File

@@ -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
{

View File

@@ -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
*/

View File

@@ -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)) {

View File

@@ -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
*

View File

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

View File

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

View 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());
}
}

View File

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

View File

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

View File

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