mirror of
https://github.com/Intervention/image.git
synced 2025-08-31 09:31:53 +02:00
Refactor font processing
This commit is contained in:
103
src/Drivers/AbstractFontProcessor.php
Normal file
103
src/Drivers/AbstractFontProcessor.php
Normal file
@@ -0,0 +1,103 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers;
|
||||
|
||||
use Intervention\Image\Geometry\Point;
|
||||
use Intervention\Image\Geometry\Rectangle;
|
||||
use Intervention\Image\Interfaces\FontInterface;
|
||||
use Intervention\Image\Interfaces\FontProcessorInterface;
|
||||
use Intervention\Image\Interfaces\PointInterface;
|
||||
use Intervention\Image\Typography\TextBlock;
|
||||
|
||||
abstract class AbstractFontProcessor implements FontProcessorInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see FontProcessorInterface::textBlock()
|
||||
*/
|
||||
public function textBlock(string $text, FontInterface $font, PointInterface $position): TextBlock
|
||||
{
|
||||
$lines = new TextBlock($text);
|
||||
$pivot = $this->buildPivot($lines, $font, $position);
|
||||
|
||||
$leading = $this->leading($font);
|
||||
$blockWidth = $this->boxSize((string) $lines->longestLine(), $font)->width();
|
||||
|
||||
$x = $pivot->x();
|
||||
$y = $font->hasFilename() ? $pivot->y() + $this->capHeight($font) : $pivot->y();
|
||||
$x_adjustment = 0;
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$line_width = $this->boxSize((string) $line, $font)->width();
|
||||
$x_adjustment = $font->alignment() == 'left' ? 0 : $blockWidth - $line_width;
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see FontProcessorInterface::typographicalSize()
|
||||
*/
|
||||
public function typographicalSize(FontInterface $font): int
|
||||
{
|
||||
return $this->boxSize('Hy', $font)->height();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see FontProcessorInterface::capHeight()
|
||||
*/
|
||||
public function capHeight(FontInterface $font): int
|
||||
{
|
||||
return $this->boxSize('T', $font)->height();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see FontProcessorInterface::leading()
|
||||
*/
|
||||
public function leading(FontInterface $font): int
|
||||
{
|
||||
return intval(round($this->typographicalSize($font) * $font->lineHeight()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Build pivot point of textblock according to the font settings and based on given position
|
||||
*
|
||||
* @param TextBlock $block
|
||||
* @param FontInterface $font
|
||||
* @param PointInterface $position
|
||||
* @return PointInterface
|
||||
*/
|
||||
protected function buildPivot(TextBlock $block, FontInterface $font, PointInterface $position): PointInterface
|
||||
{
|
||||
// bounding box
|
||||
$box = (new Rectangle(
|
||||
$this->boxSize((string) $block->longestLine(), $font)->width(),
|
||||
$this->leading($font) * ($block->count() - 1) + $this->capHeight($font)
|
||||
));
|
||||
|
||||
// set position
|
||||
$box->setPivot($position);
|
||||
|
||||
// alignment
|
||||
$box->align($font->alignment());
|
||||
$box->valign($font->valignment());
|
||||
$box->rotate($font->angle());
|
||||
|
||||
return $box->last();
|
||||
}
|
||||
}
|
@@ -1,124 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
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\ModifierInterface;
|
||||
use Intervention\Image\Typography\TextBlock;
|
||||
|
||||
/**
|
||||
* @property FontInterface $font
|
||||
*/
|
||||
abstract class AbstractTextModifier extends DriverSpecialized implements ModifierInterface
|
||||
{
|
||||
/**
|
||||
* Calculate size of bounding box of given text
|
||||
*
|
||||
* @return Polygon
|
||||
*/
|
||||
abstract protected function boxSize(string $text): Polygon;
|
||||
|
||||
/**
|
||||
* Calculates typographical leanding
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function leadingInPixels(): int
|
||||
{
|
||||
return intval(round($this->fontSizeInPixels() * $this->font->lineHeight()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates typographical cap height
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function capHeight(): int
|
||||
{
|
||||
return $this->boxSize('T')->height();
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the font size in pixels
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function fontSizeInPixels(): int
|
||||
{
|
||||
return $this->boxSize('Hy')->height();
|
||||
}
|
||||
|
||||
/**
|
||||
* Build TextBlock object from text string and align every line
|
||||
* according to text modifier's font object and position.
|
||||
*
|
||||
* @param Point $position
|
||||
* @param string $text
|
||||
* @return TextBlock
|
||||
*/
|
||||
public function alignedTextBlock(Point $position, string $text, ?int $width = null): TextBlock
|
||||
{
|
||||
$lines = new TextBlock($text);
|
||||
|
||||
// wrap lines
|
||||
$lines = $lines->wrap($width);
|
||||
|
||||
$boundingBox = $this->boundingBox($lines, $position);
|
||||
$pivot = $boundingBox->last();
|
||||
|
||||
$leading = $this->leadingInPixels();
|
||||
$blockWidth = $this->boxSize((string) $lines->longestLine())->width();
|
||||
|
||||
$x = $pivot->x();
|
||||
$y = $this->font->hasFilename() ? $pivot->y() + $this->capHeight() : $pivot->y();
|
||||
$x_adjustment = 0;
|
||||
|
||||
foreach ($lines as $line) {
|
||||
$line_width = $this->boxSize((string) $line)->width();
|
||||
$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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns bounding box of the given text block according to text modifier's
|
||||
* font settings and given pivot point
|
||||
*
|
||||
* @param TextBlock $block
|
||||
* @param Point|null $pivot
|
||||
* @return Polygon
|
||||
*/
|
||||
public function boundingBox(TextBlock $block, ?Point $pivot = null): Polygon
|
||||
{
|
||||
$pivot = $pivot ? $pivot : new Point();
|
||||
|
||||
// bounding box
|
||||
$box = (new Rectangle(
|
||||
$this->boxSize((string) $block->longestLine())->width(),
|
||||
$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;
|
||||
}
|
||||
}
|
@@ -11,6 +11,7 @@ use Intervention\Image\Interfaces\ColorInterface;
|
||||
use Intervention\Image\Interfaces\ColorProcessorInterface;
|
||||
use Intervention\Image\Interfaces\ColorspaceInterface;
|
||||
use Intervention\Image\Interfaces\DriverInterface;
|
||||
use Intervention\Image\Interfaces\FontProcessorInterface;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
|
||||
class Driver extends AbstractDriver
|
||||
@@ -120,4 +121,14 @@ class Driver extends AbstractDriver
|
||||
{
|
||||
return new ColorProcessor($colorspace);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DriverInterface::fontProcessor()
|
||||
*/
|
||||
public function fontProcessor(): FontProcessorInterface
|
||||
{
|
||||
return new FontProcessor();
|
||||
}
|
||||
}
|
||||
|
88
src/Drivers/Gd/FontProcessor.php
Normal file
88
src/Drivers/Gd/FontProcessor.php
Normal file
@@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers\Gd;
|
||||
|
||||
use Intervention\Image\Drivers\AbstractFontProcessor;
|
||||
use Intervention\Image\Geometry\Rectangle;
|
||||
use Intervention\Image\Interfaces\FontInterface;
|
||||
use Intervention\Image\Interfaces\SizeInterface;
|
||||
|
||||
class FontProcessor extends AbstractFontProcessor
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see FontProcessorInterface::boxSize()
|
||||
*/
|
||||
public function boxSize(string $text, FontInterface $font): SizeInterface
|
||||
{
|
||||
// if the font has no ttf file the box size is calculated
|
||||
// with gd's internal font system: integer values from 1-5
|
||||
if (!$font->hasFilename()) {
|
||||
// calculate box size from gd font
|
||||
$box = new Rectangle(0, 0);
|
||||
$chars = mb_strlen($text);
|
||||
if ($chars > 0) {
|
||||
$box->setWidth(
|
||||
$chars * $this->gdCharacterWidth((int) $font->filename())
|
||||
);
|
||||
$box->setHeight(
|
||||
$this->gdCharacterHeight((int) $font->filename())
|
||||
);
|
||||
}
|
||||
return $box;
|
||||
}
|
||||
|
||||
// calculate box size from ttf font file with angle 0
|
||||
$box = imageftbbox(
|
||||
$this->nativeFontSize($font),
|
||||
0,
|
||||
$font->filename(),
|
||||
$text
|
||||
);
|
||||
|
||||
// build size from points
|
||||
return new Rectangle(
|
||||
intval(abs($box[4] - $box[0])),
|
||||
intval(abs($box[5] - $box[1]))
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see FontProcessorInterface::nativeFontSize()
|
||||
*/
|
||||
public function nativeFontSize(FontInterface $font): float
|
||||
{
|
||||
return floatval(ceil($font->size() * .75));
|
||||
}
|
||||
|
||||
/**
|
||||
* Return width of a single character
|
||||
*
|
||||
* @param int $gdfont
|
||||
* @return int
|
||||
*/
|
||||
protected function gdCharacterWidth(int $gdfont): int
|
||||
{
|
||||
return $gdfont + 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return height of a single character
|
||||
*
|
||||
* @param int $gdfont
|
||||
* @return int
|
||||
*/
|
||||
protected function gdCharacterHeight(int $gdfont): int
|
||||
{
|
||||
return match ($gdfont) {
|
||||
2, 3 => 14,
|
||||
4, 5 => 16,
|
||||
default => 8,
|
||||
};
|
||||
}
|
||||
}
|
@@ -4,24 +4,28 @@ declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers\Gd\Modifiers;
|
||||
|
||||
use Intervention\Image\Drivers\AbstractTextModifier;
|
||||
use Intervention\Image\Drivers\DriverSpecialized;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
use Intervention\Image\Geometry\Point;
|
||||
use Intervention\Image\Geometry\Polygon;
|
||||
use Intervention\Image\Geometry\Rectangle;
|
||||
use Intervention\Image\Interfaces\FontInterface;
|
||||
use Intervention\Image\Interfaces\ModifierInterface;
|
||||
|
||||
/**
|
||||
* @property Point $position
|
||||
* @property string $text
|
||||
* @property FontInterface $font
|
||||
*/
|
||||
class TextModifier extends AbstractTextModifier
|
||||
class TextModifier extends DriverSpecialized implements ModifierInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ModifierInterface::apply()
|
||||
*/
|
||||
public function apply(ImageInterface $image): ImageInterface
|
||||
{
|
||||
$lines = $this->alignedTextBlock($this->position, $this->text);
|
||||
|
||||
$fontProcessor = $this->driver()->fontProcessor();
|
||||
$lines = $fontProcessor->textBlock($this->text, $this->font, $this->position);
|
||||
$color = $this->driver()->colorProcessor($image->colorspace())->colorToNative(
|
||||
$this->driver()->handleInput($this->font->color())
|
||||
);
|
||||
@@ -32,7 +36,7 @@ class TextModifier extends AbstractTextModifier
|
||||
imagealphablending($frame->native(), true);
|
||||
imagettftext(
|
||||
$frame->native(),
|
||||
$this->adjustedFontSize(),
|
||||
$fontProcessor->nativeFontSize($this->font),
|
||||
$this->font->angle() * -1,
|
||||
$line->position()->x(),
|
||||
$line->position()->y(),
|
||||
@@ -45,7 +49,7 @@ class TextModifier extends AbstractTextModifier
|
||||
foreach ($lines as $line) {
|
||||
imagestring(
|
||||
$frame->native(),
|
||||
$this->getGdFont(),
|
||||
$this->gdFont(),
|
||||
$line->position()->x(),
|
||||
$line->position()->y(),
|
||||
(string) $line,
|
||||
@@ -58,58 +62,12 @@ class TextModifier extends AbstractTextModifier
|
||||
return $image;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see AbstractTextModifier::boxSize()
|
||||
*/
|
||||
protected function boxSize(string $text): Polygon
|
||||
{
|
||||
if (!$this->font->hasFilename()) {
|
||||
// calculate box size from gd font
|
||||
$box = new Rectangle(0, 0);
|
||||
$chars = mb_strlen($text);
|
||||
if ($chars > 0) {
|
||||
$box->setWidth($chars * $this->getGdFontWidth());
|
||||
$box->setHeight($this->getGdFontHeight());
|
||||
}
|
||||
return $box;
|
||||
}
|
||||
|
||||
// calculate box size from font file with angle 0
|
||||
$box = imageftbbox(
|
||||
$this->adjustedFontSize(),
|
||||
0,
|
||||
$this->font->filename(),
|
||||
$text
|
||||
);
|
||||
|
||||
// build polygon from points
|
||||
$polygon = new Polygon();
|
||||
$polygon->addPoint(new Point($box[6], $box[7]));
|
||||
$polygon->addPoint(new Point($box[4], $box[5]));
|
||||
$polygon->addPoint(new Point($box[2], $box[3]));
|
||||
$polygon->addPoint(new Point($box[0], $box[1]));
|
||||
|
||||
return $polygon;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculate font size for `imagettftext` from given font size
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
private function adjustedFontSize(): float
|
||||
{
|
||||
return floatval(ceil($this->font->size() * .75));
|
||||
}
|
||||
|
||||
/**
|
||||
/**
|
||||
* Return GD's internal font size (if no ttf file is set)
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function getGdFont(): int
|
||||
private function gdFont(): int
|
||||
{
|
||||
if (is_numeric($this->font->filename())) {
|
||||
return intval($this->font->filename());
|
||||
@@ -117,40 +75,4 @@ class TextModifier extends AbstractTextModifier
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Font width to calculate box size, only applicable when no ttf file is set
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function getGdFontWidth(): int
|
||||
{
|
||||
return $this->getGdFont() + 4;
|
||||
}
|
||||
|
||||
/**
|
||||
* Font height to calculate box size, only applicable when no ttf file is set
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
private function getGdFontHeight(): int
|
||||
{
|
||||
switch ($this->getGdFont()) {
|
||||
case 2:
|
||||
return 14;
|
||||
|
||||
case 3:
|
||||
return 14;
|
||||
|
||||
case 4:
|
||||
return 16;
|
||||
|
||||
case 5:
|
||||
return 16;
|
||||
|
||||
default:
|
||||
case 1:
|
||||
return 8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -13,6 +13,7 @@ use Intervention\Image\Interfaces\ColorInterface;
|
||||
use Intervention\Image\Interfaces\ColorProcessorInterface;
|
||||
use Intervention\Image\Interfaces\ColorspaceInterface;
|
||||
use Intervention\Image\Interfaces\DriverInterface;
|
||||
use Intervention\Image\Interfaces\FontProcessorInterface;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
|
||||
class Driver extends AbstractDriver
|
||||
@@ -123,4 +124,14 @@ class Driver extends AbstractDriver
|
||||
{
|
||||
return new ColorProcessor($colorspace);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see DriverInterface::fontProcessor()
|
||||
*/
|
||||
public function fontProcessor(): FontProcessorInterface
|
||||
{
|
||||
return new FontProcessor();
|
||||
}
|
||||
}
|
||||
|
79
src/Drivers/Imagick/FontProcessor.php
Normal file
79
src/Drivers/Imagick/FontProcessor.php
Normal file
@@ -0,0 +1,79 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers\Imagick;
|
||||
|
||||
use Imagick;
|
||||
use ImagickDraw;
|
||||
use ImagickDrawException;
|
||||
use ImagickException;
|
||||
use ImagickPixel;
|
||||
use Intervention\Image\Drivers\AbstractFontProcessor;
|
||||
use Intervention\Image\Exceptions\FontException;
|
||||
use Intervention\Image\Geometry\Rectangle;
|
||||
use Intervention\Image\Interfaces\FontInterface;
|
||||
use Intervention\Image\Interfaces\SizeInterface;
|
||||
|
||||
class FontProcessor extends AbstractFontProcessor
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see FontProcessorInterface::boxSize()
|
||||
*/
|
||||
public function boxSize(string $text, FontInterface $font): SizeInterface
|
||||
{
|
||||
// no text - no box size
|
||||
if (mb_strlen($text) === 0) {
|
||||
return (new Rectangle(0, 0));
|
||||
}
|
||||
|
||||
$draw = $this->toImagickDraw($font);
|
||||
$draw->setStrokeAntialias(true);
|
||||
$draw->setTextAntialias(true);
|
||||
$dimensions = (new Imagick())->queryFontMetrics($draw, $text);
|
||||
|
||||
return new Rectangle(
|
||||
intval(round($dimensions['textWidth'])),
|
||||
intval(round($dimensions['ascender'] + $dimensions['descender'])),
|
||||
);
|
||||
}
|
||||
|
||||
public function nativeFontSize(FontInterface $font): float
|
||||
{
|
||||
return $font->size();
|
||||
}
|
||||
|
||||
/**
|
||||
* Imagick::annotateImage() needs an ImagickDraw object - this method takes
|
||||
* the font object as the base and adds an optional passed color to the new
|
||||
* ImagickDraw object.
|
||||
*
|
||||
* @param FontInterface $font
|
||||
* @param null|ImagickPixel $color
|
||||
* @return ImagickDraw
|
||||
* @throws FontException
|
||||
* @throws ImagickDrawException
|
||||
* @throws ImagickException
|
||||
*/
|
||||
public function toImagickDraw(FontInterface $font, ?ImagickPixel $color = null): ImagickDraw
|
||||
{
|
||||
if (!$font->hasFilename()) {
|
||||
throw new FontException('No font file specified.');
|
||||
}
|
||||
|
||||
$draw = new ImagickDraw();
|
||||
$draw->setStrokeAntialias(true);
|
||||
$draw->setTextAntialias(true);
|
||||
$draw->setFont($font->filename());
|
||||
$draw->setFontSize($this->nativeFontSize($font));
|
||||
$draw->setTextAlignment(Imagick::ALIGN_LEFT);
|
||||
|
||||
if ($color) {
|
||||
$draw->setFillColor($color);
|
||||
}
|
||||
|
||||
return $draw;
|
||||
}
|
||||
}
|
@@ -4,35 +4,35 @@ declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Drivers\Imagick\Modifiers;
|
||||
|
||||
use Imagick;
|
||||
use ImagickDraw;
|
||||
use ImagickDrawException;
|
||||
use ImagickException;
|
||||
use ImagickPixel;
|
||||
use Intervention\Image\Drivers\AbstractTextModifier;
|
||||
use Intervention\Image\Drivers\DriverSpecialized;
|
||||
use Intervention\Image\Drivers\Imagick\FontProcessor;
|
||||
use Intervention\Image\Exceptions\FontException;
|
||||
use Intervention\Image\Geometry\Point;
|
||||
use Intervention\Image\Geometry\Polygon;
|
||||
use Intervention\Image\Geometry\Rectangle;
|
||||
use Intervention\Image\Interfaces\FontInterface;
|
||||
use Intervention\Image\Interfaces\ImageInterface;
|
||||
use Intervention\Image\Interfaces\ModifierInterface;
|
||||
|
||||
/**
|
||||
* @property Point $position
|
||||
* @property string $text
|
||||
* @property FontInterface $font
|
||||
*/
|
||||
class TextModifier extends AbstractTextModifier
|
||||
class TextModifier extends DriverSpecialized implements ModifierInterface
|
||||
{
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*
|
||||
* @see ModifierInterface::apply()
|
||||
*/
|
||||
public function apply(ImageInterface $image): ImageInterface
|
||||
{
|
||||
$lines = $this->alignedTextBlock($this->position, $this->text);
|
||||
|
||||
$fontProcessor = $this->processor();
|
||||
$lines = $fontProcessor->textBlock($this->text, $this->font, $this->position);
|
||||
$color = $this->driver()->colorProcessor($image->colorspace())->colorToNative(
|
||||
$this->driver()->handleInput($this->font->color())
|
||||
);
|
||||
|
||||
$draw = $this->toImagickDraw($color);
|
||||
$draw = $fontProcessor->toImagickDraw($this->font, $color);
|
||||
|
||||
foreach ($image as $frame) {
|
||||
foreach ($lines as $line) {
|
||||
@@ -50,56 +50,18 @@ class TextModifier extends AbstractTextModifier
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
* Return imagick font processor
|
||||
*
|
||||
* @see AbstractTextModifier::boxSize()
|
||||
* @return FontProcessor
|
||||
*/
|
||||
protected function boxSize(string $text): Polygon
|
||||
private function processor(): FontProcessor
|
||||
{
|
||||
// no text - no box size
|
||||
if (mb_strlen($text) === 0) {
|
||||
return (new Rectangle(0, 0));
|
||||
$processor = $this->driver()->fontProcessor();
|
||||
|
||||
if (!($processor instanceof FontProcessor)) {
|
||||
throw new FontException('Font processor does not match the driver.');
|
||||
}
|
||||
|
||||
$draw = $this->toImagickDraw();
|
||||
$draw->setStrokeAntialias(true);
|
||||
$draw->setTextAntialias(true);
|
||||
$dimensions = (new Imagick())->queryFontMetrics($draw, $text);
|
||||
|
||||
return (new Rectangle(
|
||||
intval(round($dimensions['textWidth'])),
|
||||
intval(round($dimensions['ascender'] + $dimensions['descender'])),
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Imagick::annotateImage() needs an ImagickDraw object - this method takes
|
||||
* the text color as the base and adds the text modifiers font settings
|
||||
* to the new ImagickDraw object.
|
||||
*
|
||||
* @param null|ImagickPixel $color
|
||||
* @return ImagickDraw
|
||||
* @throws FontException
|
||||
* @throws ImagickDrawException
|
||||
* @throws ImagickException
|
||||
*/
|
||||
private 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;
|
||||
return $processor;
|
||||
}
|
||||
}
|
||||
|
@@ -63,6 +63,13 @@ interface DriverInterface
|
||||
*/
|
||||
public function colorProcessor(ColorspaceInterface $colorspace): ColorProcessorInterface;
|
||||
|
||||
/**
|
||||
* Return font processor of the current driver
|
||||
*
|
||||
* @return FontProcessorInterface
|
||||
*/
|
||||
public function fontProcessor(): FontProcessorInterface;
|
||||
|
||||
/**
|
||||
* Check whether all requirements for operating the driver are met and
|
||||
* throw exception if the check fails.
|
||||
|
58
src/Interfaces/FontProcessorInterface.php
Normal file
58
src/Interfaces/FontProcessorInterface.php
Normal file
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Interfaces;
|
||||
|
||||
use Intervention\Image\Typography\TextBlock;
|
||||
|
||||
interface FontProcessorInterface
|
||||
{
|
||||
/**
|
||||
* Calculate size of bounding box of given text in conjuction with the given font
|
||||
*
|
||||
* @return SizeInterface
|
||||
*/
|
||||
public function boxSize(string $text, FontInterface $font): SizeInterface;
|
||||
|
||||
/**
|
||||
* Build TextBlock object from text string and align every line
|
||||
* according to text modifier's font object and position.
|
||||
*
|
||||
* @param string $text
|
||||
* @param FontInterface $font
|
||||
* @param PointInterface $position
|
||||
* @return TextBlock
|
||||
*/
|
||||
public function textBlock(string $text, FontInterface $font, PointInterface $position): TextBlock;
|
||||
|
||||
/**
|
||||
* Calculate the actual font size to pass at the driver level
|
||||
*
|
||||
* @return float
|
||||
*/
|
||||
public function nativeFontSize(FontInterface $font): float;
|
||||
|
||||
/**
|
||||
* Calculate the typographical font size in pixels
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function typographicalSize(FontInterface $font): int;
|
||||
|
||||
/**
|
||||
* Calculates typographical cap height
|
||||
*
|
||||
* @param FontInterface $font
|
||||
* @return int
|
||||
*/
|
||||
public function capHeight(FontInterface $font): int;
|
||||
|
||||
/**
|
||||
* Calculates typographical leanding
|
||||
*
|
||||
* @param FontInterface $font
|
||||
* @return int
|
||||
*/
|
||||
public function leading(FontInterface $font): int;
|
||||
}
|
@@ -9,6 +9,11 @@ use Intervention\Image\Interfaces\PointInterface;
|
||||
|
||||
class Line
|
||||
{
|
||||
/**
|
||||
* Segments (usually individual words) of the line
|
||||
*/
|
||||
protected array $segments = [];
|
||||
|
||||
/**
|
||||
* Create new text line object with given text & position
|
||||
*
|
||||
@@ -17,9 +22,10 @@ class Line
|
||||
* @return void
|
||||
*/
|
||||
public function __construct(
|
||||
protected string $text,
|
||||
string $text,
|
||||
protected PointInterface $position = new Point()
|
||||
) {
|
||||
$this->segments = explode(" ", $text);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,6 +51,16 @@ class Line
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Count segments of line
|
||||
*
|
||||
* @return int
|
||||
*/
|
||||
public function count(): int
|
||||
{
|
||||
return count($this->segments);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cast line to string
|
||||
*
|
||||
@@ -52,6 +68,6 @@ class Line
|
||||
*/
|
||||
public function __toString(): string
|
||||
{
|
||||
return $this->text;
|
||||
return implode(" ", $this->segments);
|
||||
}
|
||||
}
|
||||
|
95
tests/Drivers/Gd/FontProcessorTest.php
Normal file
95
tests/Drivers/Gd/FontProcessorTest.php
Normal file
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Tests\Drivers\Gd;
|
||||
|
||||
use Intervention\Image\Drivers\Gd\FontProcessor;
|
||||
use Intervention\Image\Geometry\Point;
|
||||
use Intervention\Image\Interfaces\SizeInterface;
|
||||
use Intervention\Image\Tests\TestCase;
|
||||
use Intervention\Image\Typography\Font;
|
||||
use Intervention\Image\Typography\TextBlock;
|
||||
|
||||
class FontProcessorTest extends TestCase
|
||||
{
|
||||
public function testBoxSizeGdOne(): void
|
||||
{
|
||||
$processor = new FontProcessor();
|
||||
$size = $processor->boxSize('test', new Font());
|
||||
$this->assertInstanceOf(SizeInterface::class, $size);
|
||||
$this->assertEquals(16, $size->width());
|
||||
$this->assertEquals(8, $size->height());
|
||||
}
|
||||
|
||||
public function testBoxSizeGdTwo(): void
|
||||
{
|
||||
$processor = new FontProcessor();
|
||||
$size = $processor->boxSize('test', new Font('2'));
|
||||
$this->assertInstanceOf(SizeInterface::class, $size);
|
||||
$this->assertEquals(24, $size->width());
|
||||
$this->assertEquals(14, $size->height());
|
||||
}
|
||||
|
||||
public function testBoxSizeGdThree(): void
|
||||
{
|
||||
$processor = new FontProcessor();
|
||||
$size = $processor->boxSize('test', new Font('3'));
|
||||
$this->assertInstanceOf(SizeInterface::class, $size);
|
||||
$this->assertEquals(28, $size->width());
|
||||
$this->assertEquals(14, $size->height());
|
||||
}
|
||||
|
||||
public function testBoxSizeGdFour(): void
|
||||
{
|
||||
$processor = new FontProcessor();
|
||||
$size = $processor->boxSize('test', new Font('4'));
|
||||
$this->assertInstanceOf(SizeInterface::class, $size);
|
||||
$this->assertEquals(32, $size->width());
|
||||
$this->assertEquals(16, $size->height());
|
||||
}
|
||||
|
||||
public function testBoxSizeGdFive(): void
|
||||
{
|
||||
$processor = new FontProcessor();
|
||||
$size = $processor->boxSize('test', new Font('5'));
|
||||
$this->assertInstanceOf(SizeInterface::class, $size);
|
||||
$this->assertEquals(36, $size->width());
|
||||
$this->assertEquals(16, $size->height());
|
||||
}
|
||||
|
||||
public function testNativeFontSize(): void
|
||||
{
|
||||
$processor = new FontProcessor();
|
||||
$size = $processor->nativeFontSize(new Font('5'));
|
||||
$this->assertEquals(9.0, $size);
|
||||
}
|
||||
|
||||
public function testTextBlock(): void
|
||||
{
|
||||
$processor = new FontProcessor();
|
||||
$result = $processor->textBlock('test', new Font(), new Point(0, 0));
|
||||
$this->assertInstanceOf(TextBlock::class, $result);
|
||||
}
|
||||
|
||||
public function testTypographicalSize(): void
|
||||
{
|
||||
$processor = new FontProcessor();
|
||||
$result = $processor->typographicalSize(new Font());
|
||||
$this->assertEquals(8, $result);
|
||||
}
|
||||
|
||||
public function testCapHeight(): void
|
||||
{
|
||||
$processor = new FontProcessor();
|
||||
$result = $processor->capHeight(new Font());
|
||||
$this->assertEquals(8, $result);
|
||||
}
|
||||
|
||||
public function testLeading(): void
|
||||
{
|
||||
$processor = new FontProcessor();
|
||||
$result = $processor->leading(new Font());
|
||||
$this->assertEquals(10, $result);
|
||||
}
|
||||
}
|
21
tests/Drivers/Imagick/FontProcessorTest.php
Normal file
21
tests/Drivers/Imagick/FontProcessorTest.php
Normal file
@@ -0,0 +1,21 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace Intervention\Image\Tests\Drivers\Imagick;
|
||||
|
||||
use Intervention\Image\Drivers\Imagick\FontProcessor;
|
||||
use Intervention\Image\Tests\TestCase;
|
||||
use Intervention\Image\Typography\Font;
|
||||
|
||||
class FontProcessorTest extends TestCase
|
||||
{
|
||||
public function testNativeFontSize(): void
|
||||
{
|
||||
$processor = new FontProcessor();
|
||||
$font = new Font();
|
||||
$font->setSize(14.2);
|
||||
$size = $processor->nativeFontSize($font);
|
||||
$this->assertEquals(14.2, $size);
|
||||
}
|
||||
}
|
@@ -32,4 +32,13 @@ class LineTest extends TestCase
|
||||
$this->assertEquals(10, $line->position()->x());
|
||||
$this->assertEquals(11, $line->position()->y());
|
||||
}
|
||||
|
||||
public function testCount(): void
|
||||
{
|
||||
$line = new Line("foo");
|
||||
$this->assertEquals(1, $line->count());
|
||||
|
||||
$line = new Line("foo bar");
|
||||
$this->assertEquals(2, $line->count());
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user