mirror of
https://github.com/Intervention/image.git
synced 2025-08-26 07:14:31 +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\FontInterface;
|
||||||
use Intervention\Image\Interfaces\FontProcessorInterface;
|
use Intervention\Image\Interfaces\FontProcessorInterface;
|
||||||
use Intervention\Image\Interfaces\PointInterface;
|
use Intervention\Image\Interfaces\PointInterface;
|
||||||
|
use Intervention\Image\Typography\Line;
|
||||||
use Intervention\Image\Typography\TextBlock;
|
use Intervention\Image\Typography\TextBlock;
|
||||||
|
|
||||||
abstract class AbstractFontProcessor implements FontProcessorInterface
|
abstract class AbstractFontProcessor implements FontProcessorInterface
|
||||||
@@ -20,7 +21,7 @@ abstract class AbstractFontProcessor implements FontProcessorInterface
|
|||||||
*/
|
*/
|
||||||
public function textBlock(string $text, FontInterface $font, PointInterface $position): TextBlock
|
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);
|
$pivot = $this->buildPivot($lines, $font, $position);
|
||||||
|
|
||||||
$leading = $this->leading($font);
|
$leading = $this->leading($font);
|
||||||
@@ -74,6 +75,58 @@ abstract class AbstractFontProcessor implements FontProcessorInterface
|
|||||||
return intval(round($this->typographicalSize($font) * $font->lineHeight()));
|
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
|
* 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
|
public function nativeFontSize(FontInterface $font): float
|
||||||
{
|
{
|
||||||
return $font->size();
|
return $font->size();
|
||||||
|
@@ -117,4 +117,19 @@ interface FontInterface
|
|||||||
* @return float
|
* @return float
|
||||||
*/
|
*/
|
||||||
public function lineHeight(): 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;
|
public function boxSize(string $text, FontInterface $font): SizeInterface;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Build TextBlock object from text string and align every line
|
* Build TextBlock object from text string and align every line according
|
||||||
* according to text modifier's font object and position.
|
* to text modifier's font object and position.
|
||||||
*
|
*
|
||||||
* @param string $text
|
* @param string $text
|
||||||
* @param FontInterface $font
|
* @param FontInterface $font
|
||||||
|
@@ -15,6 +15,7 @@ class Font implements FontInterface
|
|||||||
protected string $alignment = 'left';
|
protected string $alignment = 'left';
|
||||||
protected string $valignment = 'bottom';
|
protected string $valignment = 'bottom';
|
||||||
protected float $lineHeight = 1.25;
|
protected float $lineHeight = 1.25;
|
||||||
|
protected ?int $wrapWidth = null;
|
||||||
|
|
||||||
public function __construct(?string $filename = null)
|
public function __construct(?string $filename = null)
|
||||||
{
|
{
|
||||||
@@ -184,4 +185,16 @@ class Font implements FontInterface
|
|||||||
{
|
{
|
||||||
return $this->lineHeight;
|
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;
|
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;
|
namespace Intervention\Image\Typography;
|
||||||
|
|
||||||
|
use ArrayIterator;
|
||||||
|
use Countable;
|
||||||
use Intervention\Image\Geometry\Point;
|
use Intervention\Image\Geometry\Point;
|
||||||
use Intervention\Image\Interfaces\PointInterface;
|
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 = [];
|
protected array $segments = [];
|
||||||
|
|
||||||
@@ -22,11 +26,36 @@ class Line
|
|||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
public function __construct(
|
public function __construct(
|
||||||
string $text,
|
?string $text = null,
|
||||||
protected PointInterface $position = new Point()
|
protected PointInterface $position = new Point()
|
||||||
) {
|
) {
|
||||||
|
if (is_string($text)) {
|
||||||
$this->segments = explode(" ", $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);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get Position of line
|
* Get Position of line
|
||||||
|
@@ -25,6 +25,19 @@ class TextBlock extends Collection
|
|||||||
return $this->items;
|
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
|
* Get line by given key
|
||||||
*
|
*
|
||||||
|
@@ -18,8 +18,8 @@ class LineTest extends TestCase
|
|||||||
|
|
||||||
public function testToString(): void
|
public function testToString(): void
|
||||||
{
|
{
|
||||||
$line = new Line('foo');
|
$line = new Line('foo bar');
|
||||||
$this->assertEquals('foo', (string) $line);
|
$this->assertEquals('foo bar', (string) $line);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function testSetGetPosition(): void
|
public function testSetGetPosition(): void
|
||||||
@@ -35,10 +35,27 @@ class LineTest extends TestCase
|
|||||||
|
|
||||||
public function testCount(): void
|
public function testCount(): void
|
||||||
{
|
{
|
||||||
|
$line = new Line();
|
||||||
|
$this->assertEquals(0, $line->count());
|
||||||
|
|
||||||
$line = new Line("foo");
|
$line = new Line("foo");
|
||||||
$this->assertEquals(1, $line->count());
|
$this->assertEquals(1, $line->count());
|
||||||
|
|
||||||
$line = new Line("foo bar");
|
$line = new Line("foo bar");
|
||||||
$this->assertEquals(2, $line->count());
|
$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