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:
@@ -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
|
||||
*
|
||||
|
@@ -40,6 +40,11 @@ class FontProcessor extends AbstractFontProcessor
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see FontProcessorInterface::nativeFontSize()
|
||||
*/
|
||||
public function nativeFontSize(FontInterface $font): float
|
||||
{
|
||||
return $font->size();
|
||||
|
@@ -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;
|
||||
}
|
||||
|
@@ -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
|
||||
|
@@ -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;
|
||||
}
|
||||
}
|
||||
|
@@ -77,4 +77,11 @@ class FontFactory
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function wrap(int $width): self
|
||||
{
|
||||
$this->font->setWrapWidth($width);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@@ -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
|
||||
*
|
||||
|
@@ -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());
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user