mirror of
https://github.com/Intervention/image.git
synced 2025-08-13 17:34:04 +02:00
Refactor font system
This commit is contained in:
@@ -4,7 +4,7 @@ services:
|
|||||||
tests:
|
tests:
|
||||||
build: ./
|
build: ./
|
||||||
working_dir: /project
|
working_dir: /project
|
||||||
command: bash -c "composer install && ./vendor/bin/phpunit -vvv --filter=FontTest"
|
command: bash -c "composer install && ./vendor/bin/phpunit -vvv"
|
||||||
volumes:
|
volumes:
|
||||||
- ./:/project
|
- ./:/project
|
||||||
analysis:
|
analysis:
|
||||||
|
93
src/Drivers/AbstractFontProcessor.php
Normal file
93
src/Drivers/AbstractFontProcessor.php
Normal 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();
|
||||||
|
}
|
||||||
|
}
|
@@ -7,6 +7,8 @@ use Intervention\Image\Image;
|
|||||||
use Intervention\Image\Interfaces\ColorInterface;
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
use Intervention\Image\Interfaces\ColorProcessorInterface;
|
use Intervention\Image\Interfaces\ColorProcessorInterface;
|
||||||
use Intervention\Image\Interfaces\ColorspaceInterface;
|
use Intervention\Image\Interfaces\ColorspaceInterface;
|
||||||
|
use Intervention\Image\Interfaces\FontInterface;
|
||||||
|
use Intervention\Image\Interfaces\FontProcessorInterface;
|
||||||
use Intervention\Image\Interfaces\ImageInterface;
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
class Driver extends AbstractDriver
|
class Driver extends AbstractDriver
|
||||||
@@ -44,6 +46,11 @@ class Driver extends AbstractDriver
|
|||||||
return new ColorProcessor($colorspace);
|
return new ColorProcessor($colorspace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function fontProcessor(FontInterface $font): FontProcessorInterface
|
||||||
|
{
|
||||||
|
return new FontProcessor($font);
|
||||||
|
}
|
||||||
|
|
||||||
public function colorToNative(ColorInterface $color, ColorspaceInterface $colorspace): mixed
|
public function colorToNative(ColorInterface $color, ColorspaceInterface $colorspace): mixed
|
||||||
{
|
{
|
||||||
return (new ColorProcessor($colorspace))->colorToNative($color);
|
return (new ColorProcessor($colorspace))->colorToNative($color);
|
||||||
|
@@ -2,26 +2,21 @@
|
|||||||
|
|
||||||
namespace Intervention\Image\Drivers\Gd;
|
namespace Intervention\Image\Drivers\Gd;
|
||||||
|
|
||||||
use Intervention\Image\Drivers\AbstractFont;
|
use Intervention\Image\Drivers\AbstractFontProcessor;
|
||||||
use Intervention\Image\Geometry\Point;
|
use Intervention\Image\Geometry\Point;
|
||||||
use Intervention\Image\Geometry\Polygon;
|
use Intervention\Image\Geometry\Polygon;
|
||||||
use Intervention\Image\Geometry\Rectangle;
|
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
|
* Calculate size of bounding box of given text
|
||||||
*
|
*
|
||||||
* @return Polygon
|
* @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
|
// calculate box size from gd font
|
||||||
$box = new Rectangle(0, 0);
|
$box = new Rectangle(0, 0);
|
||||||
$chars = mb_strlen($text);
|
$chars = mb_strlen($text);
|
||||||
@@ -34,9 +29,9 @@ class Font extends AbstractFont
|
|||||||
|
|
||||||
// calculate box size from font file with angle 0
|
// calculate box size from font file with angle 0
|
||||||
$box = imageftbbox(
|
$box = imageftbbox(
|
||||||
$this->size(),
|
$this->adjustedSize(),
|
||||||
0,
|
0,
|
||||||
$this->filename(),
|
$this->font->filename(),
|
||||||
$text
|
$text
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -50,10 +45,15 @@ class Font extends AbstractFont
|
|||||||
return $polygon;
|
return $polygon;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function adjustedSize(): int
|
||||||
|
{
|
||||||
|
return floatval(ceil($this->font->size() * .75));
|
||||||
|
}
|
||||||
|
|
||||||
public function getGdFont(): int
|
public function getGdFont(): int
|
||||||
{
|
{
|
||||||
if (is_numeric($this->filename)) {
|
if (is_numeric($this->font->filename())) {
|
||||||
return $this->filename;
|
return $this->font->filename();
|
||||||
}
|
}
|
||||||
|
|
||||||
return 1;
|
return 1;
|
@@ -2,31 +2,34 @@
|
|||||||
|
|
||||||
namespace Intervention\Image\Drivers\Gd\Modifiers;
|
namespace Intervention\Image\Drivers\Gd\Modifiers;
|
||||||
|
|
||||||
|
use Intervention\Image\Colors\Rgb\Colorspace;
|
||||||
use Intervention\Image\Drivers\DriverModifier;
|
use Intervention\Image\Drivers\DriverModifier;
|
||||||
use Intervention\Image\Drivers\Gd\Traits\CanHandleColors;
|
use Intervention\Image\Drivers\Gd\FontProcessor;
|
||||||
use Intervention\Image\Interfaces\ImageInterface;
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
class TextWriter extends DriverModifier
|
class TextModifier extends DriverModifier
|
||||||
{
|
{
|
||||||
use CanHandleColors;
|
|
||||||
|
|
||||||
public function apply(ImageInterface $image): ImageInterface
|
public function apply(ImageInterface $image): ImageInterface
|
||||||
{
|
{
|
||||||
$lines = $this->getAlignedTextBlock();
|
$processor = $this->fontProcessor();
|
||||||
$font = $this->font;
|
$lines = $processor->getAlignedTextBlock($this->position, $this->text);
|
||||||
$color = $this->colorToInteger($font->getColor());
|
|
||||||
|
$color = $this->driver()->colorToNative(
|
||||||
|
$this->driver()->handleInput($this->font->color()),
|
||||||
|
new Colorspace()
|
||||||
|
);
|
||||||
|
|
||||||
foreach ($image as $frame) {
|
foreach ($image as $frame) {
|
||||||
if ($this->font->hasFilename()) {
|
if ($this->font->hasFilename()) {
|
||||||
foreach ($lines as $line) {
|
foreach ($lines as $line) {
|
||||||
imagettftext(
|
imagettftext(
|
||||||
$frame->native(),
|
$frame->native(),
|
||||||
$font->getSize(),
|
$processor->adjustedSize(),
|
||||||
$font->getAngle() * (-1),
|
$this->font->angle() * -1,
|
||||||
$line->getPosition()->x(),
|
$line->position()->x(),
|
||||||
$line->getPosition()->y(),
|
$line->position()->y(),
|
||||||
$color,
|
$color,
|
||||||
$font->getFilename(),
|
$this->font->filename(),
|
||||||
$line
|
$line
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -34,7 +37,7 @@ class TextWriter extends DriverModifier
|
|||||||
foreach ($lines as $line) {
|
foreach ($lines as $line) {
|
||||||
imagestring(
|
imagestring(
|
||||||
$frame->native(),
|
$frame->native(),
|
||||||
$font->getGdFont(),
|
$processor->getGdFont(),
|
||||||
$line->getPosition()->x(),
|
$line->getPosition()->x(),
|
||||||
$line->getPosition()->y(),
|
$line->getPosition()->y(),
|
||||||
$line,
|
$line,
|
||||||
@@ -46,4 +49,9 @@ class TextWriter extends DriverModifier
|
|||||||
|
|
||||||
return $image;
|
return $image;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function fontProcessor(): FontProcessor
|
||||||
|
{
|
||||||
|
return $this->driver()->fontProcessor($this->font);
|
||||||
|
}
|
||||||
}
|
}
|
@@ -9,6 +9,8 @@ use Intervention\Image\Image;
|
|||||||
use Intervention\Image\Interfaces\ColorInterface;
|
use Intervention\Image\Interfaces\ColorInterface;
|
||||||
use Intervention\Image\Interfaces\ColorProcessorInterface;
|
use Intervention\Image\Interfaces\ColorProcessorInterface;
|
||||||
use Intervention\Image\Interfaces\ColorspaceInterface;
|
use Intervention\Image\Interfaces\ColorspaceInterface;
|
||||||
|
use Intervention\Image\Interfaces\FontInterface;
|
||||||
|
use Intervention\Image\Interfaces\FontProcessorInterface;
|
||||||
use Intervention\Image\Interfaces\ImageInterface;
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
|
|
||||||
class Driver extends AbstractDriver
|
class Driver extends AbstractDriver
|
||||||
@@ -42,6 +44,11 @@ class Driver extends AbstractDriver
|
|||||||
return new ColorProcessor($colorspace);
|
return new ColorProcessor($colorspace);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function fontProcessor(FontInterface $font): FontProcessorInterface
|
||||||
|
{
|
||||||
|
return new FontProcessor($font);
|
||||||
|
}
|
||||||
|
|
||||||
public function colorToNative(ColorInterface $color, ColorspaceInterface $colorspace): mixed
|
public function colorToNative(ColorInterface $color, ColorspaceInterface $colorspace): mixed
|
||||||
{
|
{
|
||||||
return (new ColorProcessor($colorspace))->colorToNative($color);
|
return (new ColorProcessor($colorspace))->colorToNative($color);
|
||||||
|
@@ -4,42 +4,20 @@ namespace Intervention\Image\Drivers\Imagick;
|
|||||||
|
|
||||||
use Imagick;
|
use Imagick;
|
||||||
use ImagickDraw;
|
use ImagickDraw;
|
||||||
use Intervention\Image\Drivers\AbstractFont;
|
use ImagickPixel;
|
||||||
|
use Intervention\Image\Drivers\AbstractFontProcessor;
|
||||||
use Intervention\Image\Exceptions\FontException;
|
use Intervention\Image\Exceptions\FontException;
|
||||||
use Intervention\Image\Geometry\Polygon;
|
use Intervention\Image\Geometry\Polygon;
|
||||||
use Intervention\Image\Geometry\Rectangle;
|
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
|
* Calculate box size of current font
|
||||||
*
|
*
|
||||||
* @return Polygon
|
* @return Polygon
|
||||||
*/
|
*/
|
||||||
public function getBoxSize(string $text): Polygon
|
public function boxSize(string $text): Polygon
|
||||||
{
|
{
|
||||||
// no text - no box size
|
// no text - no box size
|
||||||
if (mb_strlen($text) === 0) {
|
if (mb_strlen($text) === 0) {
|
||||||
@@ -56,4 +34,24 @@ class Font extends AbstractFont
|
|||||||
intval(round($dimensions['ascender'] + $dimensions['descender'])),
|
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;
|
||||||
|
}
|
||||||
}
|
}
|
42
src/Drivers/Imagick/Modifiers/TextModifier.php
Normal file
42
src/Drivers/Imagick/Modifiers/TextModifier.php
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -10,6 +10,7 @@ use Intervention\Image\Analyzers\PixelColorsAnalyzer;
|
|||||||
use Intervention\Image\Analyzers\ProfileAnalyzer;
|
use Intervention\Image\Analyzers\ProfileAnalyzer;
|
||||||
use Intervention\Image\Analyzers\ResolutionAnalyzer;
|
use Intervention\Image\Analyzers\ResolutionAnalyzer;
|
||||||
use Intervention\Image\Analyzers\WidthAnalyzer;
|
use Intervention\Image\Analyzers\WidthAnalyzer;
|
||||||
|
use Intervention\Image\Geometry\Point;
|
||||||
use Traversable;
|
use Traversable;
|
||||||
use Intervention\Image\Geometry\Rectangle;
|
use Intervention\Image\Geometry\Rectangle;
|
||||||
use Intervention\Image\Interfaces\AnalyzerInterface;
|
use Intervention\Image\Interfaces\AnalyzerInterface;
|
||||||
@@ -19,12 +20,15 @@ use Intervention\Image\Interfaces\ColorspaceInterface;
|
|||||||
use Intervention\Image\Interfaces\CoreInterface;
|
use Intervention\Image\Interfaces\CoreInterface;
|
||||||
use Intervention\Image\Interfaces\DriverInterface;
|
use Intervention\Image\Interfaces\DriverInterface;
|
||||||
use Intervention\Image\Interfaces\EncoderInterface;
|
use Intervention\Image\Interfaces\EncoderInterface;
|
||||||
|
use Intervention\Image\Interfaces\FontInterface;
|
||||||
use Intervention\Image\Interfaces\ImageInterface;
|
use Intervention\Image\Interfaces\ImageInterface;
|
||||||
use Intervention\Image\Interfaces\ModifierInterface;
|
use Intervention\Image\Interfaces\ModifierInterface;
|
||||||
use Intervention\Image\Interfaces\ProfileInterface;
|
use Intervention\Image\Interfaces\ProfileInterface;
|
||||||
use Intervention\Image\Interfaces\ResolutionInterface;
|
use Intervention\Image\Interfaces\ResolutionInterface;
|
||||||
use Intervention\Image\Interfaces\SizeInterface;
|
use Intervention\Image\Interfaces\SizeInterface;
|
||||||
use Intervention\Image\Modifiers\SharpenModifier;
|
use Intervention\Image\Modifiers\SharpenModifier;
|
||||||
|
use Intervention\Image\Modifiers\TextModifier;
|
||||||
|
use Intervention\Image\Typography\FontFactory;
|
||||||
|
|
||||||
class Image implements ImageInterface, Countable
|
class Image implements ImageInterface, Countable
|
||||||
{
|
{
|
||||||
@@ -129,4 +133,15 @@ class Image implements ImageInterface, Countable
|
|||||||
{
|
{
|
||||||
return $this->modify(new SharpenModifier($amount));
|
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)),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@@ -9,5 +9,6 @@ interface DriverInterface
|
|||||||
public function createImage(int $width, int $height): ImageInterface;
|
public function createImage(int $width, int $height): ImageInterface;
|
||||||
public function handleInput(mixed $input): ImageInterface|ColorInterface;
|
public function handleInput(mixed $input): ImageInterface|ColorInterface;
|
||||||
public function colorProcessor(ColorspaceInterface $colorspace): ColorProcessorInterface;
|
public function colorProcessor(ColorspaceInterface $colorspace): ColorProcessorInterface;
|
||||||
|
public function fontProcessor(FontInterface $font): FontProcessorInterface;
|
||||||
public function colorToNative(ColorInterface $color, ColorspaceInterface $colorspace): mixed;
|
public function colorToNative(ColorInterface $color, ColorspaceInterface $colorspace): mixed;
|
||||||
}
|
}
|
||||||
|
@@ -2,8 +2,6 @@
|
|||||||
|
|
||||||
namespace Intervention\Image\Interfaces;
|
namespace Intervention\Image\Interfaces;
|
||||||
|
|
||||||
use Intervention\Image\Geometry\Polygon;
|
|
||||||
|
|
||||||
interface FontInterface
|
interface FontInterface
|
||||||
{
|
{
|
||||||
public function setColor(mixed $color): self;
|
public function setColor(mixed $color): self;
|
||||||
@@ -21,8 +19,4 @@ interface FontInterface
|
|||||||
public function valignment(): string;
|
public function valignment(): string;
|
||||||
public function setLineHeight(float $value): self;
|
public function setLineHeight(float $value): self;
|
||||||
public function lineHeight(): float;
|
public function lineHeight(): float;
|
||||||
public function leadingInPixels(): int;
|
|
||||||
public function fontSizeInPixels(): int;
|
|
||||||
public function capHeight(): int;
|
|
||||||
public function getBoxSize(string $text): Polygon;
|
|
||||||
}
|
}
|
||||||
|
17
src/Interfaces/FontProcessorInterface.php
Normal file
17
src/Interfaces/FontProcessorInterface.php
Normal 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;
|
||||||
|
}
|
16
src/Modifiers/TextModifier.php
Normal file
16
src/Modifiers/TextModifier.php
Normal 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
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
}
|
@@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
@@ -1,19 +1,24 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace Intervention\Image\Drivers;
|
namespace Intervention\Image\Typography;
|
||||||
|
|
||||||
use Intervention\Image\Interfaces\FontInterface;
|
use Intervention\Image\Interfaces\FontInterface;
|
||||||
|
|
||||||
abstract class AbstractFont implements FontInterface
|
class Font implements FontInterface
|
||||||
{
|
{
|
||||||
protected float $size = 12;
|
protected float $size = 12;
|
||||||
protected float $angle = 0;
|
protected float $angle = 0;
|
||||||
protected mixed $color = '000000';
|
protected mixed $color = '000000';
|
||||||
protected ?string $filename = null;
|
protected ?string $filename = null;
|
||||||
protected string $align = 'left';
|
protected string $alignment = 'left';
|
||||||
protected string $valign = 'bottom';
|
protected string $valignment = 'bottom';
|
||||||
protected float $lineHeight = 1.25;
|
protected float $lineHeight = 1.25;
|
||||||
|
|
||||||
|
public function __construct(?string $filename = null)
|
||||||
|
{
|
||||||
|
$this->filename = $filename;
|
||||||
|
}
|
||||||
|
|
||||||
public function setSize(float $size): FontInterface
|
public function setSize(float $size): FontInterface
|
||||||
{
|
{
|
||||||
$this->size = $size;
|
$this->size = $size;
|
||||||
@@ -67,30 +72,30 @@ abstract class AbstractFont implements FontInterface
|
|||||||
return $this->color;
|
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;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function valignment(): string
|
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;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function alignment(): string
|
|
||||||
{
|
|
||||||
return $this->align;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function setLineHeight(float $height): FontInterface
|
public function setLineHeight(float $height): FontInterface
|
||||||
{
|
{
|
||||||
$this->lineHeight = $height;
|
$this->lineHeight = $height;
|
||||||
@@ -102,19 +107,4 @@ abstract class AbstractFont implements FontInterface
|
|||||||
{
|
{
|
||||||
return $this->lineHeight;
|
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
73
src/Typography/FontFactory.php
Normal file
73
src/Typography/FontFactory.php
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
namespace Intervention\Image\Typography;
|
namespace Intervention\Image\Typography;
|
||||||
|
|
||||||
use Intervention\Image\Interfaces\FontInterface;
|
|
||||||
use Intervention\Image\Geometry\Point;
|
use Intervention\Image\Geometry\Point;
|
||||||
use Intervention\Image\Interfaces\PointInterface;
|
use Intervention\Image\Interfaces\PointInterface;
|
||||||
|
|
||||||
@@ -26,11 +25,6 @@ class Line
|
|||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function widthInFont(FontInterface $font): int
|
|
||||||
{
|
|
||||||
return $font->getBoxSize($this->text)->width();
|
|
||||||
}
|
|
||||||
|
|
||||||
public function __toString(): string
|
public function __toString(): string
|
||||||
{
|
{
|
||||||
return $this->text;
|
return $this->text;
|
||||||
|
@@ -3,10 +3,6 @@
|
|||||||
namespace Intervention\Image\Typography;
|
namespace Intervention\Image\Typography;
|
||||||
|
|
||||||
use Intervention\Image\Collection;
|
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
|
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
|
* Return array of lines in text block
|
||||||
*
|
*
|
||||||
|
@@ -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());
|
|
||||||
}
|
|
||||||
}
|
|
64
tests/Drivers/AbstractFontProcessorTest.php
Normal file
64
tests/Drivers/AbstractFontProcessorTest.php
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
38
tests/Drivers/Gd/FontProcessorTest.php
Normal file
38
tests/Drivers/Gd/FontProcessorTest.php
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
42
tests/Typography/FontFactoryTest.php
Normal file
42
tests/Typography/FontFactoryTest.php
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
82
tests/Typography/FontTest.php
Normal file
82
tests/Typography/FontTest.php
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
@@ -2,12 +2,8 @@
|
|||||||
|
|
||||||
namespace Intervention\Image\Tests\Typography;
|
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\Tests\TestCase;
|
||||||
use Intervention\Image\Typography\TextBlock;
|
use Intervention\Image\Typography\TextBlock;
|
||||||
use Mockery;
|
|
||||||
|
|
||||||
class TextBlockTest extends TestCase
|
class TextBlockTest extends TestCase
|
||||||
{
|
{
|
||||||
@@ -47,33 +43,4 @@ class TextBlockTest extends TestCase
|
|||||||
$result = $block->longestLine();
|
$result = $block->longestLine();
|
||||||
$this->assertEquals('FooBar', (string) $result);
|
$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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user