1
0
mirror of https://github.com/Intervention/image.git synced 2025-01-16 19:58:14 +01:00

Refactor font system

This commit is contained in:
Oliver Vogel 2023-11-25 09:38:20 +01:00
parent 9fa8ef5aed
commit 8054bc813f
25 changed files with 575 additions and 283 deletions

View File

@ -4,7 +4,7 @@ services:
tests:
build: ./
working_dir: /project
command: bash -c "composer install && ./vendor/bin/phpunit -vvv --filter=FontTest"
command: bash -c "composer install && ./vendor/bin/phpunit -vvv"
volumes:
- ./:/project
analysis:

View File

@ -0,0 +1,93 @@
<?php
namespace Intervention\Image\Drivers;
use Intervention\Image\Geometry\Point;
use Intervention\Image\Geometry\Polygon;
use Intervention\Image\Geometry\Rectangle;
use Intervention\Image\Interfaces\FontInterface;
use Intervention\Image\Interfaces\FontProcessorInterface;
use Intervention\Image\Typography\Line;
use Intervention\Image\Typography\TextBlock;
abstract class AbstractFontProcessor implements FontProcessorInterface
{
public function __construct(protected FontInterface $font)
{
}
public function leadingInPixels(): int
{
return intval(round($this->fontSizeInPixels() * $this->font->lineHeight()));
}
public function capHeight(): int
{
return $this->boxSize('T')->height();
}
public function fontSizeInPixels(): int
{
return $this->boxSize('Hy')->height();
}
/**
* Build TextBlock object from text string and align every line
* according to text writers font object and position.
*
* @return TextBlock
*/
public function alignedTextBlock(Point $position, string $text): TextBlock
{
$lines = new TextBlock($text);
$boundingBox = $this->boundingBox($lines, $position);
$pivot = $boundingBox->last();
$leading = $this->leadingInPixels();
$blockWidth = $this->lineWidth($lines->longestLine());
$x = $pivot->x();
$y = $this->font->hasFilename() ? $pivot->y() + $this->capHeight() : $pivot->y();
$x_adjustment = 0;
foreach ($lines as $line) {
$line_width = $this->lineWidth($line);
$x_adjustment = $this->font->alignment() == 'left' ? 0 : $blockWidth - $line_width;
$x_adjustment = $this->font->alignment() == 'right' ? intval(round($x_adjustment)) : $x_adjustment;
$x_adjustment = $this->font->alignment() == 'center' ? intval(round($x_adjustment / 2)) : $x_adjustment;
$position = new Point($x + $x_adjustment, $y);
$position->rotate($this->font->angle(), $pivot);
$line->setPosition($position);
$y += $leading;
}
return $lines;
}
public function boundingBox(TextBlock $block, Point $pivot = null): Polygon
{
$pivot = $pivot ? $pivot : new Point();
// bounding box
$box = (new Rectangle(
$this->lineWidth($block->longestLine()),
$this->leadingInPixels() * ($block->count() - 1) + $this->capHeight()
));
// set pivot
$box->setPivot($pivot);
// align
$box->align($this->font->alignment());
$box->valign($this->font->valignment());
$box->rotate($this->font->angle());
return $box;
}
private function lineWidth(Line $line): int
{
return $this->boxSize((string) $line)->width();
}
}

View File

@ -7,6 +7,8 @@ use Intervention\Image\Image;
use Intervention\Image\Interfaces\ColorInterface;
use Intervention\Image\Interfaces\ColorProcessorInterface;
use Intervention\Image\Interfaces\ColorspaceInterface;
use Intervention\Image\Interfaces\FontInterface;
use Intervention\Image\Interfaces\FontProcessorInterface;
use Intervention\Image\Interfaces\ImageInterface;
class Driver extends AbstractDriver
@ -44,6 +46,11 @@ class Driver extends AbstractDriver
return new ColorProcessor($colorspace);
}
public function fontProcessor(FontInterface $font): FontProcessorInterface
{
return new FontProcessor($font);
}
public function colorToNative(ColorInterface $color, ColorspaceInterface $colorspace): mixed
{
return (new ColorProcessor($colorspace))->colorToNative($color);

View File

@ -2,26 +2,21 @@
namespace Intervention\Image\Drivers\Gd;
use Intervention\Image\Drivers\AbstractFont;
use Intervention\Image\Drivers\AbstractFontProcessor;
use Intervention\Image\Geometry\Point;
use Intervention\Image\Geometry\Polygon;
use Intervention\Image\Geometry\Rectangle;
class Font extends AbstractFont
class FontProcessor extends AbstractFontProcessor
{
public function size(): float
{
return floatval(ceil(parent::size() * 0.75));
}
/**
* Calculate size of bounding box of given text
*
* @return Polygon
*/
public function getBoxSize(string $text): Polygon
public function boxSize(string $text): Polygon
{
if (!$this->hasFilename()) {
if (!$this->font->hasFilename()) {
// calculate box size from gd font
$box = new Rectangle(0, 0);
$chars = mb_strlen($text);
@ -34,9 +29,9 @@ class Font extends AbstractFont
// calculate box size from font file with angle 0
$box = imageftbbox(
$this->size(),
$this->adjustedSize(),
0,
$this->filename(),
$this->font->filename(),
$text
);
@ -50,10 +45,15 @@ class Font extends AbstractFont
return $polygon;
}
public function adjustedSize(): int
{
return floatval(ceil($this->font->size() * .75));
}
public function getGdFont(): int
{
if (is_numeric($this->filename)) {
return $this->filename;
if (is_numeric($this->font->filename())) {
return $this->font->filename();
}
return 1;

View File

@ -2,31 +2,34 @@
namespace Intervention\Image\Drivers\Gd\Modifiers;
use Intervention\Image\Colors\Rgb\Colorspace;
use Intervention\Image\Drivers\DriverModifier;
use Intervention\Image\Drivers\Gd\Traits\CanHandleColors;
use Intervention\Image\Drivers\Gd\FontProcessor;
use Intervention\Image\Interfaces\ImageInterface;
class TextWriter extends DriverModifier
class TextModifier extends DriverModifier
{
use CanHandleColors;
public function apply(ImageInterface $image): ImageInterface
{
$lines = $this->getAlignedTextBlock();
$font = $this->font;
$color = $this->colorToInteger($font->getColor());
$processor = $this->fontProcessor();
$lines = $processor->getAlignedTextBlock($this->position, $this->text);
$color = $this->driver()->colorToNative(
$this->driver()->handleInput($this->font->color()),
new Colorspace()
);
foreach ($image as $frame) {
if ($this->font->hasFilename()) {
foreach ($lines as $line) {
imagettftext(
$frame->native(),
$font->getSize(),
$font->getAngle() * (-1),
$line->getPosition()->x(),
$line->getPosition()->y(),
$processor->adjustedSize(),
$this->font->angle() * -1,
$line->position()->x(),
$line->position()->y(),
$color,
$font->getFilename(),
$this->font->filename(),
$line
);
}
@ -34,7 +37,7 @@ class TextWriter extends DriverModifier
foreach ($lines as $line) {
imagestring(
$frame->native(),
$font->getGdFont(),
$processor->getGdFont(),
$line->getPosition()->x(),
$line->getPosition()->y(),
$line,
@ -46,4 +49,9 @@ class TextWriter extends DriverModifier
return $image;
}
private function fontProcessor(): FontProcessor
{
return $this->driver()->fontProcessor($this->font);
}
}

View File

@ -9,6 +9,8 @@ use Intervention\Image\Image;
use Intervention\Image\Interfaces\ColorInterface;
use Intervention\Image\Interfaces\ColorProcessorInterface;
use Intervention\Image\Interfaces\ColorspaceInterface;
use Intervention\Image\Interfaces\FontInterface;
use Intervention\Image\Interfaces\FontProcessorInterface;
use Intervention\Image\Interfaces\ImageInterface;
class Driver extends AbstractDriver
@ -42,6 +44,11 @@ class Driver extends AbstractDriver
return new ColorProcessor($colorspace);
}
public function fontProcessor(FontInterface $font): FontProcessorInterface
{
return new FontProcessor($font);
}
public function colorToNative(ColorInterface $color, ColorspaceInterface $colorspace): mixed
{
return (new ColorProcessor($colorspace))->colorToNative($color);

View File

@ -4,42 +4,20 @@ namespace Intervention\Image\Drivers\Imagick;
use Imagick;
use ImagickDraw;
use Intervention\Image\Drivers\AbstractFont;
use ImagickPixel;
use Intervention\Image\Drivers\AbstractFontProcessor;
use Intervention\Image\Exceptions\FontException;
use Intervention\Image\Geometry\Polygon;
use Intervention\Image\Geometry\Rectangle;
use Intervention\Image\Interfaces\ColorspaceInterface;
class Font extends AbstractFont
class FontProcessor extends AbstractFontProcessor
{
public function toImagickDraw(?ColorspaceInterface $colorspace = null): ImagickDraw
{
if (!$this->hasFilename()) {
throw new FontException('No font file specified.');
}
$draw = new ImagickDraw();
$draw->setStrokeAntialias(true);
$draw->setTextAntialias(true);
$draw->setFont($this->filename());
$draw->setFontSize($this->size());
$draw->setTextAlignment(Imagick::ALIGN_LEFT);
if ($colorspace) {
$draw->setFillColor(
$this->colorToPixel($this->color(), $colorspace)
);
}
return $draw;
}
/**
* Calculate box size of current font
*
* @return Polygon
*/
public function getBoxSize(string $text): Polygon
public function boxSize(string $text): Polygon
{
// no text - no box size
if (mb_strlen($text) === 0) {
@ -56,4 +34,24 @@ class Font extends AbstractFont
intval(round($dimensions['ascender'] + $dimensions['descender'])),
));
}
public function toImagickDraw(?ImagickPixel $color = null): ImagickDraw
{
if (!$this->font->hasFilename()) {
throw new FontException('No font file specified.');
}
$draw = new ImagickDraw();
$draw->setStrokeAntialias(true);
$draw->setTextAntialias(true);
$draw->setFont($this->font->filename());
$draw->setFontSize($this->font->size());
$draw->setTextAlignment(Imagick::ALIGN_LEFT);
if ($color) {
$draw->setFillColor($color);
}
return $draw;
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace Intervention\Image\Drivers\Imagick\Modifiers;
use Intervention\Image\Drivers\DriverModifier;
use Intervention\Image\Drivers\Imagick\FontProcessor;
use Intervention\Image\Interfaces\ImageInterface;
class TextModifier extends DriverModifier
{
public function apply(ImageInterface $image): ImageInterface
{
$processor = $this->fontProcessor();
$lines = $processor->getAlignedTextBlock($this->position, $this->text);
$color = $this->driver()->colorToNative(
$this->driver()->handleInput($this->font->color()),
$image->colorspace()
);
$draw = $processor->toImagickDraw($color);
foreach ($image as $frame) {
foreach ($lines as $line) {
$frame->native()->annotateImage(
$draw,
$line->position()->x(),
$line->position()->y(),
$this->font->angle(),
$line
);
}
}
return $image;
}
private function fontProcessor(): FontProcessor
{
return $this->driver()->fontProcessor($this->font);
}
}

View File

@ -1,29 +0,0 @@
<?php
namespace Intervention\Image\Drivers\Imagick\Modifiers;
use Intervention\Image\Drivers\DriverModifier;
use Intervention\Image\Interfaces\ImageInterface;
class TextWriter extends DriverModifier
{
public function apply(ImageInterface $image): ImageInterface
{
$lines = $this->getAlignedTextBlock();
$font = $this->font;
foreach ($image as $frame) {
foreach ($lines as $line) {
$frame->native()->annotateImage(
$font->toImagickDraw($image->colorspace()),
$line->getPosition()->x(),
$line->getPosition()->y(),
$font->getAngle(),
$line
);
}
}
return $image;
}
}

View File

@ -10,6 +10,7 @@ use Intervention\Image\Analyzers\PixelColorsAnalyzer;
use Intervention\Image\Analyzers\ProfileAnalyzer;
use Intervention\Image\Analyzers\ResolutionAnalyzer;
use Intervention\Image\Analyzers\WidthAnalyzer;
use Intervention\Image\Geometry\Point;
use Traversable;
use Intervention\Image\Geometry\Rectangle;
use Intervention\Image\Interfaces\AnalyzerInterface;
@ -19,12 +20,15 @@ use Intervention\Image\Interfaces\ColorspaceInterface;
use Intervention\Image\Interfaces\CoreInterface;
use Intervention\Image\Interfaces\DriverInterface;
use Intervention\Image\Interfaces\EncoderInterface;
use Intervention\Image\Interfaces\FontInterface;
use Intervention\Image\Interfaces\ImageInterface;
use Intervention\Image\Interfaces\ModifierInterface;
use Intervention\Image\Interfaces\ProfileInterface;
use Intervention\Image\Interfaces\ResolutionInterface;
use Intervention\Image\Interfaces\SizeInterface;
use Intervention\Image\Modifiers\SharpenModifier;
use Intervention\Image\Modifiers\TextModifier;
use Intervention\Image\Typography\FontFactory;
class Image implements ImageInterface, Countable
{
@ -129,4 +133,15 @@ class Image implements ImageInterface, Countable
{
return $this->modify(new SharpenModifier($amount));
}
public function text(string $text, int $x, int $y, callable|FontInterface $font): ImageInterface
{
return $this->modify(
new TextModifier(
$text,
new Point($x, $y),
call_user_func(new FontFactory($font)),
),
);
}
}

View File

@ -9,5 +9,6 @@ interface DriverInterface
public function createImage(int $width, int $height): ImageInterface;
public function handleInput(mixed $input): ImageInterface|ColorInterface;
public function colorProcessor(ColorspaceInterface $colorspace): ColorProcessorInterface;
public function fontProcessor(FontInterface $font): FontProcessorInterface;
public function colorToNative(ColorInterface $color, ColorspaceInterface $colorspace): mixed;
}

View File

@ -2,8 +2,6 @@
namespace Intervention\Image\Interfaces;
use Intervention\Image\Geometry\Polygon;
interface FontInterface
{
public function setColor(mixed $color): self;
@ -21,8 +19,4 @@ interface FontInterface
public function valignment(): string;
public function setLineHeight(float $value): self;
public function lineHeight(): float;
public function leadingInPixels(): int;
public function fontSizeInPixels(): int;
public function capHeight(): int;
public function getBoxSize(string $text): Polygon;
}

View File

@ -0,0 +1,17 @@
<?php
namespace Intervention\Image\Interfaces;
use Intervention\Image\Geometry\Point;
use Intervention\Image\Geometry\Polygon;
use Intervention\Image\Typography\TextBlock;
interface FontProcessorInterface
{
public function leadingInPixels(): int;
public function fontSizeInPixels(): int;
public function capHeight(): int;
public function boxSize(string $text): Polygon;
public function alignedTextBlock(Point $position, string $text): TextBlock;
public function boundingBox(TextBlock $block, Point $pivot = null): Polygon;
}

View File

@ -0,0 +1,16 @@
<?php
namespace Intervention\Image\Modifiers;
use Intervention\Image\Geometry\Point;
use Intervention\Image\Interfaces\FontInterface;
class TextModifier extends AbstractModifier
{
public function __construct(
public string $text,
public Point $position,
public FontInterface $font
) {
}
}

View File

@ -1,52 +0,0 @@
<?php
namespace Intervention\Image\Modifiers;
use Intervention\Image\Geometry\Point;
use Intervention\Image\Interfaces\FontInterface;
use Intervention\Image\Typography\TextBlock;
class TextWriter extends AbstractModifier
{
public function __construct(
public Point $position,
public FontInterface $font,
public string $text
) {
}
/**
* Build TextBlock object from text string and align every line
* according to text writers font object and position.
*
* @return TextBlock
*/
public function getAlignedTextBlock(): TextBlock
{
$lines = new TextBlock($this->text);
$position = $this->position;
$font = $this->font;
$boundingBox = $lines->boundingBox($font, $position);
$pivot = $boundingBox->last();
$leading = $font->leadingInPixels();
$blockWidth = $lines->longestLine()->widthInFont($font);
$x = $pivot->x();
$y = $font->hasFilename() ? $pivot->y() + $font->capHeight() : $pivot->y();
$x_adjustment = 0;
foreach ($lines as $line) {
$x_adjustment = $font->alignment() == 'left' ? 0 : $blockWidth - $line->widthInFont($font);
$x_adjustment = $font->alignment() == 'right' ? intval(round($x_adjustment)) : $x_adjustment;
$x_adjustment = $font->alignment() == 'center' ? intval(round($x_adjustment / 2)) : $x_adjustment;
$position = new Point($x + $x_adjustment, $y);
$position->rotate($font->angle(), $pivot);
$line->setPosition($position);
$y += $leading;
}
return $lines;
}
}

View File

@ -1,19 +1,24 @@
<?php
namespace Intervention\Image\Drivers;
namespace Intervention\Image\Typography;
use Intervention\Image\Interfaces\FontInterface;
abstract class AbstractFont implements FontInterface
class Font implements FontInterface
{
protected float $size = 12;
protected float $angle = 0;
protected mixed $color = '000000';
protected ?string $filename = null;
protected string $align = 'left';
protected string $valign = 'bottom';
protected string $alignment = 'left';
protected string $valignment = 'bottom';
protected float $lineHeight = 1.25;
public function __construct(?string $filename = null)
{
$this->filename = $filename;
}
public function setSize(float $size): FontInterface
{
$this->size = $size;
@ -67,30 +72,30 @@ abstract class AbstractFont implements FontInterface
return $this->color;
}
public function setAlignment(string $align): FontInterface
public function alignment(): string
{
$this->align = $align;
return $this->alignment;
}
public function setAlignment(string $value): FontInterface
{
$this->alignment = $value;
return $this;
}
public function valignment(): string
{
return $this->valign;
return $this->valignment;
}
public function setValignment(string $valign): FontInterface
public function setValignment(string $value): FontInterface
{
$this->valign = $valign;
$this->valignment = $value;
return $this;
}
public function alignment(): string
{
return $this->align;
}
public function setLineHeight(float $height): FontInterface
{
$this->lineHeight = $height;
@ -102,19 +107,4 @@ abstract class AbstractFont implements FontInterface
{
return $this->lineHeight;
}
public function leadingInPixels(): int
{
return intval(round($this->fontSizeInPixels() * $this->lineHeight()));
}
public function capHeight(): int
{
return $this->getBoxSize('T')->height();
}
public function fontSizeInPixels(): int
{
return $this->getBoxSize('Hy')->height();
}
}

View File

@ -0,0 +1,73 @@
<?php
namespace Intervention\Image\Typography;
use Intervention\Image\Interfaces\FontInterface;
class FontFactory
{
protected FontInterface $font;
public function __construct(callable|FontInterface $init)
{
$this->font = is_a($init, FontInterface::class) ? $init : new Font();
if (is_callable($init)) {
$init($this);
}
}
public function __invoke(): FontInterface
{
return $this->font;
}
public function filename(string $value): self
{
$this->font->setFilename($value);
return $this;
}
public function color(mixed $value): self
{
$this->font->setColor($value);
return $this;
}
public function size(float $value): self
{
$this->font->setSize($value);
return $this;
}
public function align(string $value): self
{
$this->font->setAlignment($value);
return $this;
}
public function valign(string $value): self
{
$this->font->setValignment($value);
return $this;
}
public function lineHeight(float $value): self
{
$this->font->setLineHeight($value);
return $this;
}
public function angle(float $value): self
{
$this->font->setAngle($value);
return $this;
}
}

View File

@ -2,7 +2,6 @@
namespace Intervention\Image\Typography;
use Intervention\Image\Interfaces\FontInterface;
use Intervention\Image\Geometry\Point;
use Intervention\Image\Interfaces\PointInterface;
@ -26,11 +25,6 @@ class Line
return $this;
}
public function widthInFont(FontInterface $font): int
{
return $font->getBoxSize($this->text)->width();
}
public function __toString(): string
{
return $this->text;

View File

@ -3,10 +3,6 @@
namespace Intervention\Image\Typography;
use Intervention\Image\Collection;
use Intervention\Image\Geometry\Point;
use Intervention\Image\Geometry\Polygon;
use Intervention\Image\Geometry\Rectangle;
use Intervention\Image\Interfaces\FontInterface;
class TextBlock extends Collection
{
@ -17,28 +13,6 @@ class TextBlock extends Collection
}
}
public function boundingBox(FontInterface $font, Point $pivot = null): Polygon
{
$pivot = $pivot ? $pivot : new Point();
// bounding box
$box = (new Rectangle(
$this->longestLine()->widthInFont($font),
$font->leadingInPixels() * ($this->count() - 1) + $font->capHeight()
));
// set pivot
$box->setPivot($pivot);
// align
$box->align($font->alignment());
$box->valign($font->valignment());
$box->rotate($font->angle());
return $box;
}
/**
* Return array of lines in text block
*

View File

@ -1,49 +0,0 @@
<?php
namespace Intervention\Image\Tests\Drivers\Abstract;
use Intervention\Image\Drivers\AbstractFont;
use Intervention\Image\Geometry\Rectangle;
use Intervention\Image\Tests\TestCase;
use Mockery;
class AbstractFontTest extends TestCase
{
private function getAbstractFontMock()
{
// create mock
$mock = Mockery::mock(AbstractFont::class)
->shouldAllowMockingProtectedMethods()
->makePartial();
$mock->shouldReceive('getBoxSize')->with('Hy')->andReturn(new Rectangle(123, 456));
$mock->shouldReceive('getBoxSize')->with('T')->andReturn(new Rectangle(12, 34));
// settings
$mock->setSize(24);
$mock->setAngle(30);
$mock->setFilename(__DIR__ . '/AbstractFontTest.php');
$mock->setColor('ccc');
$mock->setAlignment('center');
$mock->setValignment('top');
$mock->setLineHeight(1.5);
return $mock;
}
public function testConstructor(): void
{
$mock = $this->getAbstractFontMock();
$this->assertEquals(24.0, $mock->size());
$this->assertEquals(30, $mock->angle());
$this->assertEquals(__DIR__ . '/AbstractFontTest.php', $mock->filename());
$this->assertEquals('ccc', $mock->color());
$this->assertEquals('center', $mock->alignment());
$this->assertEquals('top', $mock->valignment());
$this->assertEquals(1.5, $mock->lineHeight());
$this->assertEquals(456, $mock->fontSizeInPixels());
$this->assertEquals(34, $mock->capHeight());
$this->assertEquals(684, $mock->leadingInPixels());
$this->assertTrue($mock->hasFilename());
}
}

View File

@ -0,0 +1,64 @@
<?php
namespace Intervention\Image\Tests\Drivers;
use Intervention\Image\Drivers\AbstractFontProcessor;
use Intervention\Image\Geometry\Point;
use Intervention\Image\Geometry\Polygon;
use Intervention\Image\Geometry\Rectangle;
use Intervention\Image\Interfaces\FontInterface;
use Intervention\Image\Tests\TestCase;
use Intervention\Image\Typography\Font;
use Intervention\Image\Typography\TextBlock;
use Mockery;
class AbstractFontProcessorTest extends TestCase
{
private function getMock(FontInterface $font)
{
// create mock
$mock = Mockery::mock(AbstractFontProcessor::class, [$font])
->shouldAllowMockingProtectedMethods()
->makePartial();
$mock->shouldReceive('boxSize')->with('Hy')->andReturn(new Rectangle(123, 456));
$mock->shouldReceive('boxSize')->with('T')->andReturn(new Rectangle(12, 34));
$mock->shouldReceive('boxSize')->with('foobar')->andReturn(new Rectangle(4, 8));
return $mock;
}
public function testLeadingInPixels(): void
{
$mock = $this->getMock((new Font())->setLineHeight(2));
$this->assertEquals(912, $mock->leadingInPixels());
}
public function testCapHeight(): void
{
$mock = $this->getMock((new Font())->setLineHeight(2));
$this->assertEquals(34, $mock->capHeight());
}
public function testFontSizeInPixels(): void
{
$mock = $this->getMock((new Font())->setLineHeight(2));
$this->assertEquals(456, $mock->fontSizeInPixels());
}
public function testAlignedTextBlock(): void
{
$mock = $this->getMock((new Font())->setLineHeight(2));
$block = $mock->alignedTextBlock(new Point(0, 0), 'foobar');
$this->assertInstanceOf(TextBlock::class, $block);
}
public function testBoundingBox(): void
{
$mock = $this->getMock((new Font())->setLineHeight(2));
$box = $mock->boundingBox(new TextBlock('foobar'));
$this->assertInstanceOf(Polygon::class, $box);
$this->assertEquals(4, $box->width());
$this->assertEquals(34, $box->height());
}
}

View File

@ -0,0 +1,38 @@
<?php
namespace Intervention\Image\Tests\Drivers\Gd;
use Intervention\Image\Drivers\Gd\FontProcessor;
use Intervention\Image\Geometry\Polygon;
use Intervention\Image\Tests\TestCase;
use Intervention\Image\Typography\Font;
class FontProcessorTest extends TestCase
{
public function testBoxSize(): void
{
$processor = new FontProcessor(new Font());
$result = $processor->boxSize('test');
$this->assertInstanceOf(Polygon::class, $result);
$this->assertEquals(20, $result->width());
$this->assertEquals(8, $result->height());
}
public function testAdjustedSize(): void
{
$processor = new FontProcessor((new Font())->setSize(100));
$this->assertEquals(75, $processor->adjustedSize());
}
public function testGetGdFont(): void
{
$processor = new FontProcessor(new Font());
$this->assertEquals(1, $processor->getGdFont());
$processor = new FontProcessor((new Font())->setFilename(100));
$this->assertEquals(100, $processor->getGdFont());
$processor = new FontProcessor((new Font())->setFilename('foo'));
$this->assertEquals(1, $processor->getGdFont());
}
}

View File

@ -0,0 +1,42 @@
<?php
namespace Intervention\Image\Tests\Typography;
use Intervention\Image\Interfaces\FontInterface;
use Intervention\Image\Tests\TestCase;
use Intervention\Image\Typography\Font;
use Intervention\Image\Typography\FontFactory;
class FontFactoryTest extends TestCase
{
public function testBuildWithFont(): void
{
$factory = new FontFactory(new Font('foo.ttf'));
$result = $factory();
$this->assertInstanceOf(FontInterface::class, $result);
$this->assertEquals('foo.ttf', $result->filename());
}
public function testBuildWithCallback(): void
{
$factory = new FontFactory(function ($font) {
$font->filename('foo.ttf');
$font->color('#b01735');
$font->size(70);
$font->align('center');
$font->valign('middle');
$font->lineHeight(1.6);
$font->angle(10);
});
$result = $factory();
$this->assertInstanceOf(FontInterface::class, $result);
$this->assertEquals('foo.ttf', $result->filename());
$this->assertEquals('#b01735', $result->color());
$this->assertEquals(70, $result->size());
$this->assertEquals('center', $result->alignment());
$this->assertEquals('middle', $result->valignment());
$this->assertEquals(1.6, $result->lineHeight());
$this->assertEquals(10, $result->angle());
}
}

View File

@ -0,0 +1,82 @@
<?php
namespace Intervention\Image\Tests\Typography;
use Intervention\Image\Tests\TestCase;
use Intervention\Image\Typography\Font;
class FontTest extends TestCase
{
public function testConstructor(): void
{
$font = new Font('foo.ttf');
$this->assertInstanceOf(Font::class, $font);
$this->assertEquals('foo.ttf', $font->filename());
}
public function testSetGetSize(): void
{
$font = new Font();
$this->assertEquals(12, $font->size());
$result = $font->setSize(123);
$this->assertInstanceOf(Font::class, $result);
$this->assertEquals(123, $font->size());
}
public function testSetGetAngle(): void
{
$font = new Font();
$this->assertEquals(0, $font->angle());
$result = $font->setAngle(123);
$this->assertInstanceOf(Font::class, $result);
$this->assertEquals(123, $font->angle());
}
public function testSetGetFilename(): void
{
$font = new Font();
$this->assertEquals(null, $font->filename());
$this->assertFalse($font->hasFilename());
$filename = $this->getTestImagePath();
$result = $font->setFilename($filename);
$this->assertTrue($font->hasFilename());
$this->assertInstanceOf(Font::class, $result);
$this->assertEquals($filename, $font->filename());
}
public function testSetGetColor(): void
{
$font = new Font();
$this->assertEquals('000000', $font->color());
$result = $font->setColor('fff');
$this->assertInstanceOf(Font::class, $result);
$this->assertEquals('fff', $font->color());
}
public function testSetGetAlignment(): void
{
$font = new Font();
$this->assertEquals('left', $font->alignment());
$result = $font->setAlignment('center');
$this->assertInstanceOf(Font::class, $result);
$this->assertEquals('center', $font->alignment());
}
public function testSetGetValignment(): void
{
$font = new Font();
$this->assertEquals('bottom', $font->valignment());
$result = $font->setValignment('center');
$this->assertInstanceOf(Font::class, $result);
$this->assertEquals('center', $font->valignment());
}
public function testSetGetLineHeight(): void
{
$font = new Font();
$this->assertEquals(1.25, $font->lineHeight());
$result = $font->setLineHeight(3.2);
$this->assertInstanceOf(Font::class, $result);
$this->assertEquals(3.2, $font->lineHeight());
}
}

View File

@ -2,12 +2,8 @@
namespace Intervention\Image\Tests\Typography;
use Intervention\Image\Geometry\Point;
use Intervention\Image\Geometry\Polygon;
use Intervention\Image\Interfaces\FontInterface;
use Intervention\Image\Tests\TestCase;
use Intervention\Image\Typography\TextBlock;
use Mockery;
class TextBlockTest extends TestCase
{
@ -47,33 +43,4 @@ class TextBlockTest extends TestCase
$result = $block->longestLine();
$this->assertEquals('FooBar', (string) $result);
}
public function testGetBoundingBox(): void
{
$block = $this->getTestBlock();
$font = Mockery::mock(FontInterface::class)
->shouldAllowMockingProtectedMethods()
->makePartial();
$font->shouldReceive('getBoxSize')->andReturn(
new Polygon([
new Point(0, 0),
new Point(300, 0),
new Point(300, 150),
new Point(0, 150),
])
);
$font->shouldReceive('leadingInPixels')->andReturn(30);
$font->shouldReceive('alignment')->andReturn('left');
$font->shouldReceive('valignment')->andReturn('bottom');
$font->shouldReceive('angle')->andReturn(0);
$font->shouldReceive('capHeight')->andReturn(22);
$box = $block->boundingBox($font, new Point(10, 15));
$this->assertEquals(300, $box->width());
$this->assertEquals(82, $box->height());
$this->assertEquals(10, $box->pivot()->x());
$this->assertEquals(15, $box->pivot()->y());
}
}