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:
parent
9fa8ef5aed
commit
8054bc813f
@ -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:
|
||||
|
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\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);
|
||||
|
@ -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;
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
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\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)),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
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
|
||||
|
||||
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();
|
||||
}
|
||||
}
|
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;
|
||||
|
||||
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;
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -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;
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user