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

Implement text wrapping

This commit is contained in:
Oliver Vogel
2024-02-03 14:51:17 +01:00
parent ac7389fa96
commit 684f5e6eb6
9 changed files with 161 additions and 9 deletions

View File

@@ -9,6 +9,7 @@ use Intervention\Image\Geometry\Rectangle;
use Intervention\Image\Interfaces\FontInterface;
use Intervention\Image\Interfaces\FontProcessorInterface;
use Intervention\Image\Interfaces\PointInterface;
use Intervention\Image\Typography\Line;
use Intervention\Image\Typography\TextBlock;
abstract class AbstractFontProcessor implements FontProcessorInterface
@@ -20,7 +21,7 @@ abstract class AbstractFontProcessor implements FontProcessorInterface
*/
public function textBlock(string $text, FontInterface $font, PointInterface $position): TextBlock
{
$lines = new TextBlock($text);
$lines = $this->wrapTextBlock(new TextBlock($text), $font);
$pivot = $this->buildPivot($lines, $font, $position);
$leading = $this->leading($font);
@@ -74,6 +75,58 @@ abstract class AbstractFontProcessor implements FontProcessorInterface
return intval(round($this->typographicalSize($font) * $font->lineHeight()));
}
/**
* Reformat a text block by wrapping each line before the given maximum width
*
* @param TextBlock $block
* @param FontInterface $font
* @return TextBlock
*/
protected function wrapTextBlock(TextBlock $block, FontInterface $font): TextBlock
{
$newLines = [];
foreach ($block as $line) {
foreach ($this->wrapLine($line, $font) as $newLine) {
$newLines[] = $newLine;
}
}
return $block->setLines($newLines);
}
/**
* Check if a line exceeds the given maximum width and wrap it if necessary.
* The output will be an array of formatted lines that are all within the
* maximum width.
*
* @param Line $line
* @param FontInterface $font
* @return array
*/
protected function wrapLine(Line $line, FontInterface $font): array
{
// no wrap width - no wrapping
if (is_null($font->wrapWidth())) {
return [$line];
}
$wrapped = [];
$formatedLine = new Line();
foreach ($line as $word) {
if ($font->wrapWidth() >= $this->boxSize((string) $formatedLine . ' ' . $word, $font)->width()) {
$formatedLine->add($word);
} else {
$wrapped[] = $formatedLine;
$formatedLine = new Line($word);
}
}
$wrapped[] = $formatedLine;
return $wrapped;
}
/**
* Build pivot point of textblock according to the font settings and based on given position
*

View File

@@ -40,6 +40,11 @@ class FontProcessor extends AbstractFontProcessor
);
}
/**
* {@inheritdoc}
*
* @see FontProcessorInterface::nativeFontSize()
*/
public function nativeFontSize(FontInterface $font): float
{
return $font->size();

View File

@@ -117,4 +117,19 @@ interface FontInterface
* @return float
*/
public function lineHeight(): float;
/**
* Set the wrap width with which the text is rendered
*
* @param int $width
* @return FontInterface
*/
public function setWrapWidth(?int $width): self;
/**
* Get wrap width with which the text is rendered
*
* @return null|int
*/
public function wrapWidth(): ?int;
}

View File

@@ -16,8 +16,8 @@ interface FontProcessorInterface
public function boxSize(string $text, FontInterface $font): SizeInterface;
/**
* Build TextBlock object from text string and align every line
* according to text modifier's font object and position.
* Build TextBlock object from text string and align every line according
* to text modifier's font object and position.
*
* @param string $text
* @param FontInterface $font

View File

@@ -15,6 +15,7 @@ class Font implements FontInterface
protected string $alignment = 'left';
protected string $valignment = 'bottom';
protected float $lineHeight = 1.25;
protected ?int $wrapWidth = null;
public function __construct(?string $filename = null)
{
@@ -184,4 +185,16 @@ class Font implements FontInterface
{
return $this->lineHeight;
}
public function setWrapWidth(?int $width): FontInterface
{
$this->wrapWidth = $width;
return $this;
}
public function wrapWidth(): ?int
{
return $this->wrapWidth;
}
}

View File

@@ -77,4 +77,11 @@ class FontFactory
return $this;
}
public function wrap(int $width): self
{
$this->font->setWrapWidth($width);
return $this;
}
}

View File

@@ -4,13 +4,17 @@ declare(strict_types=1);
namespace Intervention\Image\Typography;
use ArrayIterator;
use Countable;
use Intervention\Image\Geometry\Point;
use Intervention\Image\Interfaces\PointInterface;
use IteratorAggregate;
use Traversable;
class Line
class Line implements IteratorAggregate, Countable
{
/**
* Segments (usually individual words) of the line
* Segments (usually individual words including punctuation marks) of the line
*/
protected array $segments = [];
@@ -22,10 +26,35 @@ class Line
* @return void
*/
public function __construct(
string $text,
?string $text = null,
protected PointInterface $position = new Point()
) {
$this->segments = explode(" ", $text);
if (is_string($text)) {
$this->segments = explode(" ", $text);
}
}
/**
* Add word to current line
*
* @param string $word
* @return Line
*/
public function add(string $word): self
{
$this->segments[] = $word;
return $this;
}
/**
* Returns Iterator
*
* @return Traversable
*/
public function getIterator(): Traversable
{
return new ArrayIterator($this->segments);
}
/**

View File

@@ -25,6 +25,19 @@ class TextBlock extends Collection
return $this->items;
}
/**
* Set lines of the text block
*
* @param array $lines
* @return self
*/
public function setLines(array $lines): self
{
$this->items = $lines;
return $this;
}
/**
* Get line by given key
*

View File

@@ -18,8 +18,8 @@ class LineTest extends TestCase
public function testToString(): void
{
$line = new Line('foo');
$this->assertEquals('foo', (string) $line);
$line = new Line('foo bar');
$this->assertEquals('foo bar', (string) $line);
}
public function testSetGetPosition(): void
@@ -35,10 +35,27 @@ class LineTest extends TestCase
public function testCount(): void
{
$line = new Line();
$this->assertEquals(0, $line->count());
$line = new Line("foo");
$this->assertEquals(1, $line->count());
$line = new Line("foo bar");
$this->assertEquals(2, $line->count());
}
public function testAdd(): void
{
$line = new Line();
$this->assertEquals(0, $line->count());
$result = $line->add('foo');
$this->assertEquals(1, $line->count());
$this->assertEquals(1, $result->count());
$result = $line->add('bar');
$this->assertEquals(2, $line->count());
$this->assertEquals(2, $result->count());
}
}