1
0
mirror of https://github.com/Intervention/image.git synced 2025-08-11 00:14:03 +02:00

text reorganization

This commit is contained in:
Oliver Vogel
2014-03-18 18:08:04 +01:00
parent c3ab2f72e9
commit 51a75c8e25
6 changed files with 931 additions and 3 deletions

View File

@@ -0,0 +1,8 @@
<?php
namespace Intervention\Image\Exception;
class FontNotApplicableException extends \RuntimeException
{
# nothing to override
}

View File

@@ -0,0 +1,8 @@
<?php
namespace Intervention\Image\Exception;
class FontNotFoundException extends \RuntimeException
{
# nothing to override
}

View File

@@ -0,0 +1,472 @@
<?php
namespace Intervention\Image;
class Font
{
/**
* Text to be written
*
* @var String
*/
private $text;
/**
* Text size in pixels
*
* @var integer
*/
private $size = 12;
/**
* Color of the text
*
* @var mixed
*/
private $color = '000000';
/**
* Rotation angle of the text
*
* @var integer
*/
private $angle = 0;
/**
* Horizontal alignment of the text
*
* @var String
*/
private $align;
/**
* Vertical alignment of the text
*
* @var String
*/
private $valign;
/**
* Path to TTF or GD library internal font file of the text
*
* @var mixed
*/
private $file;
/**
* Create a new instance of Font
*
* @param Strinf $text Text to be written
*/
public function __construct($text = null)
{
$this->text = $text;
}
/**
* Set text to be written
*
* @param String $text
* @return void
*/
public function text($text)
{
$this->text = $text;
}
/**
* Get text to be written
*
* @return String
*/
public function getText()
{
return $this->text;
}
/**
* Set font size in pixels
*
* @param integer $size
* @return void
*/
public function size($size)
{
$this->size = $size;
}
/**
* Get font size in pixels
*
* @return integer
*/
public function getSize()
{
return $this->size;
}
/**
* Get font size in points
*
* @return integer
*/
public function getPointSize()
{
return intval(ceil($this->size * 0.75));
}
/**
* Set color of text to be written
*
* @param mixed $color
* @return void
*/
public function color($color)
{
$this->color = $color;
}
/**
* Get color of text
*
* @return mixed
*/
public function getColor()
{
return $this->color;
}
/**
* Set rotation angle of text
*
* @param integer $angle
* @return void
*/
public function angle($angle)
{
$this->angle = $angle;
}
/**
* Get rotation angle of text
*
* @return integer
*/
public function getAngle()
{
return $this->angle;
}
/**
* Set horizontal text alignment
*
* @param string $align
* @return void
*/
public function align($align)
{
$this->align = $align;
}
/**
* Get horizontal text alignment
*
* @return string
*/
public function getAlign()
{
return $this->align;
}
/**
* Set vertical text alignment
*
* @param string $valign
* @return void
*/
public function valign($valign)
{
$this->valign = $valign;
}
/**
* Get vertical text alignment
*
* @return string
*/
public function getValign()
{
return $this->valign;
}
/**
* Set path to font file
*
* @param string $align
* @return void
*/
public function file($file)
{
$this->file = $file;
}
/**
* Get path to font file
*
* @return string
*/
public function getFile()
{
return $this->file;
}
/**
* Checks if current font has access to an applicable font file
*
* @return boolean [description]
*/
private function hasApplicableFontFile()
{
if (is_string($this->file)) {
return file_exists($this->file);
}
return false;
}
/**
* Filter function to access internal integer font values
*
* @return integer
*/
private function getInternalFont()
{
$internalfont = is_null($this->file) ? 1 : $this->file;
$internalfont = is_numeric($internalfont) ? $internalfont : false;
if ( ! in_array($internalfont, array(1, 2, 3, 4, 5))) {
throw new Exception\FontNotFoundException(sprintf('Internal font %s not available.', $internalfont));
}
return intval($internalfont);
}
/**
* Get width of an internal font character
*
* @return integer
*/
private function getInternalFontWidth()
{
return $this->getInternalFont() + 4;
}
/**
* Get height of an internal font character
*
* @return integer
*/
private function getInternalFontHeight()
{
switch ($this->getInternalFont()) {
case 1:
return 8;
case 2:
return 14;
case 3:
return 14;
case 4:
return 16;
case 5:
return 16;
}
}
/**
* Calculates bounding box of current font setting
*
* @return Array
*/
public function getBoxSize()
{
$box = array();
if ($this->hasApplicableFontFile()) {
// get bounding box with angle 0
$box = imagettfbbox($this->getPointSize(), 0, $this->file, $this->text);
// rotate points manually
if ($this->angle != 0) {
$angle = pi() * 2 - $this->angle * pi() * 2 / 360;
for ($i=0; $i<4; $i++) {
$x = $box[$i * 2];
$y = $box[$i * 2 + 1];
$box[$i * 2] = cos($angle) * $x - sin($angle) * $y;
$box[$i * 2 + 1] = sin($angle) * $x + cos($angle) * $y;
}
}
$box['width'] = intval(abs($box[4] - $box[0]));
$box['height'] = intval(abs($box[5] - $box[1]));
} else {
// get current internal font size
$width = $this->getInternalFontWidth();
$height = $this->getInternalFontHeight();
if (strlen($this->text) == 0) {
// no text -> no boxsize
$box['width'] = 0;
$box['height'] = 0;
} else {
// calculate boxsize
$box['width'] = strlen($this->text) * $width;
$box['height'] = $height;
}
}
return $box;
}
/**
* Draws font to given image at given position
* @param Image $image
* @param integer $posx
* @param integer $posy
* @return void
*/
public function applyToImage(Image $image, $posx = 0, $posy = 0)
{
// parse text color
$color = $image->parseColor($this->color);
if ($this->hasApplicableFontFile()) {
if ($this->angle != 0 || is_string($this->align) || is_string($this->valign)) {
$box = $this->getBoxSize();
$align = is_null($this->align) ? 'left' : strtolower($this->align);
$valign = is_null($this->valign) ? 'bottom' : strtolower($this->valign);
// correction on position depending on v/h alignment
switch ($align.'-'.$valign) {
case 'center-top':
$posx = $posx - round(($box[6]+$box[4])/2);
$posy = $posy - round(($box[7]+$box[5])/2);
break;
case 'right-top':
$posx = $posx - $box[4];
$posy = $posy - $box[5];
break;
case 'left-top':
$posx = $posx - $box[6];
$posy = $posy - $box[7];
break;
case 'center-center':
case 'center-middle':
$posx = $posx - round(($box[0]+$box[4])/2);
$posy = $posy - round(($box[1]+$box[5])/2);
break;
case 'right-center':
case 'right-middle':
$posx = $posx - round(($box[2]+$box[4])/2);
$posy = $posy - round(($box[3]+$box[5])/2);
break;
case 'left-center':
case 'left-middle':
$posx = $posx - round(($box[0]+$box[6])/2);
$posy = $posy - round(($box[1]+$box[7])/2);
break;
case 'center-bottom':
$posx = $posx - round(($box[0]+$box[2])/2);
$posy = $posy - round(($box[1]+$box[3])/2);
break;
case 'right-bottom':
$posx = $posx - $box[2];
$posy = $posy - $box[3];
break;
case 'left-bottom':
$posx = $posx - $box[0];
$posy = $posy - $box[1];
break;
}
}
// $image->rectangle(array(0,0,0,0.5), $posx+$box[6], $posy+$box[7], $posx+$box[2], $posy+$box[3]);
// enable alphablending for imagettftext
imagealphablending($image->resource, true);
// draw ttf text
imagettftext($image->resource, $this->getPointSize(), $this->angle, $posx, $posy, $color, $this->file, $this->text);
} else {
// get box size
$box = $this->getBoxSize();
$width = $box['width'];
$height = $box['height'];
// internal font specific position corrections
if ($this->getInternalFont() == 1) {
$top_correction = 1;
$bottom_correction = 2;
} elseif ($this->getInternalFont() == 3) {
$top_correction = 2;
$bottom_correction = 4;
} else {
$top_correction = 3;
$bottom_correction = 4;
}
// x-position corrections for horizontal alignment
switch (strtolower($this->align)) {
case 'center':
$posx = ceil($posx - ($width / 2));
break;
case 'right':
$posx = ceil($posx - $width) + 1;
break;
}
// y-position corrections for vertical alignment
switch (strtolower($this->valign)) {
case 'center':
case 'middle':
$posy = ceil($posy - ($height / 2));
break;
case 'top':
$posy = ceil($posy - $top_correction);
break;
default:
case 'bottom':
$posy = round($posy - $height + $bottom_correction);
break;
}
// draw text
imagestring($image->resource, $this->getInternalFont(), $posx, $posy, $this->text, $color);
}
}
}

View File

@@ -1183,7 +1183,50 @@ class Image
}
/**
* Write text in current image
* Compatibility method to decide old or new style of text writing
*
* @param string $text
* @param integer $posx
* @param integer $posy
* @param integer $angle
* @param integer $size
* @param string $color
* @param string $fontfile
* @return Image
*/
public function text($text, $posx = 0, $posy = 0, $size_or_callback = null, $color = '000000', $angle = 0, $fontfile = null)
{
if (is_numeric($size_or_callback)) {
return $this->legacyText($text, $posx, $posy, $size_or_callback, $color, $angle, $fontfile);
} else {
return $this->textCallback($text, $posx, $posy, $size_or_callback);
}
}
/**
* Write text in current image, define details via callback
*
* @param string $text
* @param integer $posx
* @param integer $posy
* @param Closure $callback
* @return Image
*/
public function textCallback($text, $posx = 0, $posy = 0, Closure $callback = null)
{
$font = new \Intervention\Image\Font($text);
if ($callback instanceof Closure) {
$callback($font);
}
$font->applyToImage($this, $posx, $posy);
return $this;
}
/**
* Legacy method to keep support of old style of text writing
*
* @param string $text
* @param integer $pos_x
@@ -1194,7 +1237,7 @@ class Image
* @param string $fontfile
* @return Image
*/
public function text($text, $pos_x = 0, $pos_y = 0, $size = 16, $color = '000000', $angle = 0, $fontfile = null)
public function legacyText($text, $pos_x = 0, $pos_y = 0, $size = 16, $color = '000000', $angle = 0, $fontfile = null)
{
if (is_null($fontfile)) {
@@ -1922,6 +1965,24 @@ class Image
is_resource($this->original) ? imagedestroy($this->original) : null;
}
/**
* Calculates checksum of current image
*
* @return String
*/
public function checksum()
{
$colors = array();
for ($x=0; $x <= ($this->width-1); $x++) {
for ($y=0; $y <= ($this->height-1); $y++) {
$colors[] = $this->pickColor($x, $y, 'int');
}
}
return md5(serialize($colors));
}
/**
* Returns image stream
*

345
tests/FontTest.php Normal file
View File

@@ -0,0 +1,345 @@
<?php
use Intervention\Image\Font;
use Intervention\Image\Image;
class FontTest extends PHPUnit_Framework_Testcase
{
public function testConstructorWithoutParameters()
{
$font = new Font;
$this->assertInstanceOf('Intervention\Image\Font', $font);
}
public function testConstructorWithParameters()
{
$font = new Font('The quick brown fox jumps over the lazy dog.');
$this->assertInstanceOf('Intervention\Image\Font', $font);
$this->assertEquals($font->getText(), 'The quick brown fox jumps over the lazy dog.');
}
public function testText()
{
$font = new Font;
$font->text('The quick brown fox jumps over the lazy dog.');
$this->assertEquals($font->getText(), 'The quick brown fox jumps over the lazy dog.');
}
public function testSize()
{
$font = new Font;
$font->size(24);
$this->assertInternalType('int', $font->getSize());
$this->assertEquals($font->getSize(), 24);
}
public function testPointSize()
{
$font = new Font;
$font->size(24);
$this->assertInternalType('int', $font->getPointSize());
$this->assertEquals($font->getPointSize(), 18);
}
public function testColor()
{
$font = new Font;
$font->color('b53717');
$this->assertInternalType('string', $font->getColor());
$this->assertEquals($font->getColor(), 'b53717');
}
public function testAngle()
{
$font = new Font;
$font->angle(45);
$this->assertInternalType('int', $font->getAngle());
$this->assertEquals($font->getAngle(), 45);
}
public function testAlign()
{
$font = new Font;
$font->align('center');
$this->assertInternalType('string', $font->getAlign());
$this->assertEquals($font->getAlign(), 'center');
}
public function testValign()
{
$font = new Font;
$font->valign('bottom');
$this->assertInternalType('string', $font->getValign());
$this->assertEquals($font->getValign(), 'bottom');
}
public function testFile()
{
$font = new Font;
$font->file('foo.ttf');
$this->assertInternalType('string', $font->getFile());
$this->assertEquals($font->getFile(), 'foo.ttf');
}
public function testGetBoxsizeInternal()
{
$font = new Font;
$box = $font->getboxsize();
$this->assertInternalType('int', $box['width']);
$this->assertInternalType('int', $box['height']);
$this->assertEquals(0, $box['width']);
$this->assertEquals(0, $box['height']);
$font = new Font('000');
$box = $font->getboxsize();
$this->assertInternalType('int', $box['width']);
$this->assertInternalType('int', $box['height']);
$this->assertEquals(15, $box['width']);
$this->assertEquals(8, $box['height']);
$font = new Font('000');
$font->file(1);
$box = $font->getboxsize();
$this->assertInternalType('int', $box['width']);
$this->assertInternalType('int', $box['height']);
$this->assertEquals(15, $box['width']);
$this->assertEquals(8, $box['height']);
$font = new Font('000');
$font->file(2);
$box = $font->getboxsize();
$this->assertInternalType('int', $box['width']);
$this->assertInternalType('int', $box['height']);
$this->assertEquals(18, $box['width']);
$this->assertEquals(14, $box['height']);
$font = new Font('000');
$font->file(3);
$box = $font->getboxsize();
$this->assertInternalType('int', $box['width']);
$this->assertInternalType('int', $box['height']);
$this->assertEquals(21, $box['width']);
$this->assertEquals(14, $box['height']);
$font = new Font('000');
$font->file(4);
$box = $font->getboxsize();
$this->assertInternalType('int', $box['width']);
$this->assertInternalType('int', $box['height']);
$this->assertEquals(24, $box['width']);
$this->assertEquals(16, $box['height']);
$font = new Font('000');
$font->file(5);
$box = $font->getboxsize();
$this->assertInternalType('int', $box['width']);
$this->assertInternalType('int', $box['height']);
$this->assertEquals(27, $box['width']);
$this->assertEquals(16, $box['height']);
}
/*
public function testGetBoxsizeFontfile()
{
$font = new Font;
$font->file('public/Vera.ttf');
$box = $font->getboxsize();
$this->assertInternalType('int', $box['width']);
$this->assertInternalType('int', $box['height']);
$this->assertEquals(0, $box['width']);
$this->assertEquals(0, $box['height']);
$font = new Font('000');
$font->file('public/Vera.ttf');
$box = $font->getboxsize();
$this->assertInternalType('int', $box['width']);
$this->assertInternalType('int', $box['height']);
$this->assertEquals(22, $box['width']);
$this->assertEquals(9, $box['height']);
$font = new Font('000');
$font->file('public/Vera.ttf');
$font->size(16);
$box = $font->getboxsize();
$this->assertInternalType('int', $box['width']);
$this->assertInternalType('int', $box['height']);
$this->assertEquals(28, $box['width']);
$this->assertEquals(12, $box['height']);
$font = new Font('000');
$font->file('public/Vera.ttf');
$font->size(24);
$box = $font->getboxsize();
$this->assertInternalType('int', $box['width']);
$this->assertInternalType('int', $box['height']);
$this->assertEquals(42, $box['width']);
$this->assertEquals(18, $box['height']);
}
public function testGetBoxsizeFontfileWithAngle()
{
$font = new Font('000');
$font->file('public/Vera.ttf');
$font->angle(45);
$box = $font->getboxsize();
$this->assertInternalType('int', $box['width']);
$this->assertInternalType('int', $box['height']);
$this->assertEquals(22, $box['width']);
$this->assertEquals(9, $box['height']);
$font = new Font('000');
$font->file('public/Vera.ttf');
$font->size(16);
$font->angle(45);
$box = $font->getboxsize();
$this->assertInternalType('int', $box['width']);
$this->assertInternalType('int', $box['height']);
$this->assertEquals(28, $box['width']);
$this->assertEquals(12, $box['height']);
$font = new Font('000');
$font->file('public/Vera.ttf');
$font->size(24);
$font->angle(45);
$box = $font->getboxsize();
$this->assertInternalType('int', $box['width']);
$this->assertInternalType('int', $box['height']);
$this->assertEquals(42, $box['width']);
$this->assertEquals(18, $box['height']);
}
*/
public function testAlignmentsInternalFonts()
{
$haligns = array('left', 'center', 'right');
$valigns = array('top', 'middle', 'bottom');
$fontfiles = array(1, 2, 3, 4, 5);
$position = array(80, 40);
$checksums = array(
'1' => array(
'top' => array(
'left' => 'fa8bfa9a8cce6071515d9aac18007a50',
'center' => '86c9fa152b5b3e1a637cb17b70a6edfc',
'right' => 'a386f9155ef5da46872c73958b668799',
),
'middle' => array(
'left' => '45af47bbda0ba771aa4963f447d3bbb9',
'center' => '700182599f461d37efb8297170ea7eda',
'right' => '288bab5bf92dead0ed909167d072b8bb',
),
'bottom' => array(
'left' => 'a85e0d2329957b27b3cf4c3f21721c45',
'center' => '507a590cdae9b6dce1a6fc9b30a744cb',
'right' => '530ac7978230241c555dd8c5374f029e',
),
),
'2' => array(
'top' => array(
'left' => '24e4dc99e7e2b9b7d553b0fce1abf3a3',
'center' => '1099aae99acad2dac796d33da99cb9d3',
'right' => '06a888438b2a6bc5e673f8c6d9b70e42',
),
'middle' => array(
'left' => 'ae2629402f215aef11226413b71b8b7a',
'center' => '8ce82b42da6ad6b5ceb7da7bf637022d',
'right' => '870034642cfd26ebd165ca0de5a8c12c',
),
'bottom' => array(
'left' => 'd6b53fdd3e68f64287e988937917a6e3',
'center' => '3a4c8fdaae0f056c3b24c2d13d09a865',
'right' => 'd4db9c844cf73a78b0ddadaa7a230c04',
),
),
'3' => array(
'top' => array(
'left' => 'd6dd07b730f8793ba08b5f2052965254',
'center' => 'b8ae1ca956b74b066572dd76c1fbc2cb',
'right' => '1e3327bf7e540487aa1514e490339ff3',
),
'middle' => array(
'left' => '2b69adba28256ac1b10d67bde6f2ac71',
'center' => '55cdc747bd51513640be777d15561f58',
'right' => '1918bcf0810b38454d84591b356a5f1e',
),
'bottom' => array(
'left' => 'aac8e8a56e9f464b5223c568fe660ff9',
'center' => 'e9c381bfac9690e1b7c330b9ed9bd6fa',
'right' => '3966301d2c445fe5f73c10bda7ba7993',
),
),
'4' => array(
'top' => array(
'left' => '9b61dcbbf8c1d61db7301f38677cb094',
'center' => 'b3c6f738493d38ba6e658100fa5592f7',
'right' => '65b7525ee23c7e4d3db3e82b24b3f175',
),
'middle' => array(
'left' => '060933279d8a34d0234c1d0e25c41357',
'center' => 'f06c06b4604a72f7b8b9068ffa306990',
'right' => '3cc4f152c671021decca21656ac078a2',
),
'bottom' => array(
'left' => '83201c48862f4ccf218b7ae018cfc61f',
'center' => 'e07fd632d487f1fe507c4adf7c4a8f71',
'right' => 'da2cf30237fcd2724ba2b7248026d73b',
),
),
'5' => array(
'top' => array(
'left' => '02cabb064130730206bfc05d86842bcd',
'center' => '50c46cf1ccf9776cc118ad1102a3f259',
'right' => 'e1e7bc8a72c0b64b20cc3e96e4cb7573',
),
'middle' => array(
'left' => '45f263ba8a4ae63f4275a8b76a0f526b',
'center' => 'a76f4d65901b30ed5eb58a9975560b80',
'right' => '7d04bceecc1c576e84b6184a89b2b2c7',
),
'bottom' => array(
'left' => '285741dbdb636724dc56b2ff8a0c5814',
'center' => 'ed274bfc9d9e3e7644baedc043312f7b',
'right' => 'fa00447bb085ec03d6a865bbc39faf36',
),
),
);
foreach ($haligns as $halign) {
foreach ($valigns as $valign) {
foreach ($fontfiles as $file) {
$canvas = new Image(null, 160, 80, 'ffffff');
$font = new Font('00000');
$font->file($file);
$font->align($halign);
$font->valign($valign);
$font->applyToImage($canvas, $position[0], $position[1]);
$checksum = $canvas->checksum();
$this->assertEquals($checksum, $checksums[$file][$valign][$halign]);
}
}
}
}
/**
* @expectedException Intervention\Image\Exception\FontNotFoundException
*/
public function testInternalFontNotAvailable()
{
$image = new Image(null, 25, 25);
$font = new Font;
$font->file(10);
$font->applyToImage($image);
}
/**
* @expectedException Intervention\Image\Exception\FontNotFoundException
*/
public function testFontfileNotAvailable()
{
$image = new Image(null, 25, 25);
$font = new Font;
$font->file('foo/bar.ttf');
$font->applyToImage($image);
}
}

View File

@@ -1292,7 +1292,7 @@ class ImageTest extends PHPUnit_Framework_Testcase
$this->assertEquals('#ffffff', $img->pickColor($coords[1][0], $coords[1][1], 'hex'));
}
public function testTextImage()
public function testLegacyTextImage()
{
$img = $this->getTestImage();
$img = $img->text('Fox', 10, 10, 16, '000000', 0, null);
@@ -1305,6 +1305,33 @@ class ImageTest extends PHPUnit_Framework_Testcase
$this->assertInstanceOf('Intervention\Image\Image', $img);
}
public function testTextImage()
{
$img = new Image(null, 160, 80, 'ffffff');
$img = $img->text('00000', 80, 40);
$this->assertInstanceOf('Intervention\Image\Image', $img);
$this->assertEquals('a85e0d2329957b27b3cf4c3f21721c45', $img->checksum());
$img = new Image(null, 160, 80, 'ffffff');
$img = $img->text('00000', 80, 40, function($font) {
$font->align('center');
$font->valign('top');
$font->color('000000');
});
$this->assertInstanceOf('Intervention\Image\Image', $img);
$this->assertEquals('86c9fa152b5b3e1a637cb17b70a6edfc', $img->checksum());
$img = new Image(null, 160, 80, 'ffffff');
$img = $img->text('00000', 80, 40, function($font) {
$font->align('right');
$font->valign('middle');
$font->file(2);
$font->color('000000');
});
$this->assertInstanceOf('Intervention\Image\Image', $img);
$this->assertEquals('870034642cfd26ebd165ca0de5a8c12c', $img->checksum());
}
public function testRectangleImage()
{
$img = $this->getTestImage();
@@ -1834,4 +1861,11 @@ class ImageTest extends PHPUnit_Framework_Testcase
$img->destroy();
$this->assertEquals(get_resource_type($img->resource), 'Unknown');
}
public function testChecksum()
{
$img = new Image('public/circle.png');
$checksum = $img->checksum();
$this->assertEquals($checksum, '149432c4e99e8bf8c295afb85be64e78');
}
}