imagettfbbox / imagettftext bugs

Note: the bugs described on this page have been fixed. I didn't really document in which version of PHP / GD they occurred. At some point I'll drag out some of the images that demonstrated the bugs. But my advice for anyone experiencing font weirdness is to upgrade the PHP + GD version if you can, otherwise copy the function below, which kinda-sorta fixes it.

test
Rotation
Text
Font
Font Size
Method whether to (mostly) fix the bounding box coordinates

Kerning

Kerning, the spacing between letters, may change with some fonts as the text is rotated.

  • rotate 180 degrees
  • use "fixed" method
  • word still jumps out of the bounding box!

Alignment

Sometimes, the text baseline shifts throughout writing.

  • rotate 90 degrees
  • use text "YoYoYo" or "TaTaTa"
  • use Arial font
  • baseline shifts up (relative to text) as writing continues!

Varying Height

The height of the bounding box varies as it is rotated.

  • rotate 90 degrees
  • use text "hanging"
  • use "normal" method ("fixed" corrects this)
  • the box collapses to exclude hanging / tall text!

Incorrect Box Coordinates

  • rotate between 30-330 degrees
  • use "normal" method
  • anything beyond 30 degrees of rotation starts to get seriously wrong!

Fixing Function

The fix for some of the above problems can be handled with the function below, from helpful user comments on the php manual. The function gets the bounding box for non-rotated text, then rotates the whole box. This doesn't, of course, fix the change in kerning or the (presumably GD) bug that mis-aligns rotated text.

<?php
function imagettfbbox_t($size$angle$fontfile$text){
    
// compute size with a zero angle
    
$coords imagettfbbox($size0$fontfile$text);
    
// convert angle to radians
    
$a deg2rad($angle);
    
// compute some usefull values
    
$ca cos($a);
    
$sa sin($a);
    
$ret = array();
    
// perform transformations
    
for($i 0$i 7$i += 2){
        
$ret[$i] = round($coords[$i] * $ca $coords[$i+1] * $sa);
        
$ret[$i+1] = round($coords[$i+1] * $ca $coords[$i] * $sa);
    }
    return 
$ret;
}
?>

Generating the image

I've received a few mails asking about the source of the image. Here it is:

<?php
// dimensions
$w 500;
$h 500;

// create an image
$img imagecreatetruecolor($w$h);
switch(@
$_GET['font']){
    case 
'arial':
        
$font 'support/fonts/arial.ttf';
        break;
    case 
'castelar':
        
$font 'support/fonts/CASTELAR.TTF';
        break;
    case 
'comicbd':
        
$font 'support/fonts/comicbd.ttf';
        break;
    case 
'bradhitc':
        
$font 'support/fonts/BRADHITC.TTF';
        break;
    default: 
$font 'support/fonts/arial.ttf';
}
// BASIC COLORS
$black imagecolorallocate($img000);
$white imagecolorallocate($img255255255);
$red imagecolorallocate($img25500);
$green imagecolorallocate($img02550);
$purple imagecolorallocate($img2000255);
$blue imagecolorallocate($img6464255);
$orange imagecolorallocate($img2551000);
$yellow imagecolorallocate($img2552550);

// DATA FROM THE POST PASSED VIA GET
$str = isset($_GET['string']) ? $_GET['string'] : 'nadine';
if(!
$str){$str 'nadine';}

// CENTER THE TEXT
$x $w/2;
$y $h/2;
// ROTATION
$ang = isset($_GET['rotation']) ? $_GET['rotation'] : 0;
if(!
is_numeric($ang)){$ang 0;}
$ang $ang 360;
// FONT SIZE
$fontsize = isset($_GET['fontsize']) ? $_GET['fontsize'] : 50;
if(!
is_numeric($fontsize)){$fontsize 50;}
$dotsize 6;

// COMPUTES THE DISTANCE
function distance($x1$y1$x2$y2){
    
$x $x1 $x2;
    
$y $y1 $y2;
    
$dist sqrt(pow($x2) + pow($y2));
    return 
sprintf('%0.1f'$dist);
}

// WRITE TEXT AND GET BOUNDING BOX
imagettftext($img$fontsize$ang$x$y$white$font$str);
// if they chose to fix the bounding box, recalculate
if(@$_GET['method'] == 'fixed'){
    function 
imagettfbbox_t($size$angle$fontfile$text){
        
// compute size with a zero angle
        
$coords imagettfbbox($size0$fontfile$text);
        
// convert angle to radians
        
$a deg2rad($angle);
        
// compute some usefull values
        
$ca cos($a);
        
$sa sin($a);
        
$ret = array();
        
// perform transformations
        
for($i 0$i 7$i += 2){
            
$ret[$i] = round($coords[$i] * $ca $coords[$i+1] * $sa);
            
$ret[$i+1] = round($coords[$i+1] * $ca $coords[$i] * $sa);
        }
        return 
$ret;
    }
    
// get the fixed bounding box
    
$b imagettfbbox_t($fontsize$ang$font$str);
}else{
    
$b imagettfbbox($fontsize$ang$font$str);
}

// write some bbox vals on the image
imagestring($img21010"({$b[6]},{$b[7]})"$yellow);
imagestring($img26010'---'$red);
imagestring($img28510,"({$b[4]},{$b[5]})"$yellow);

// bottom
imagestring($img21030"({$b[0]},{$b[1]})"$orange);
imagestring($img26030'---'$red);
imagestring($img28530"({$b[2]},{$b[3]})"$orange);

// middle
imagestring($img22020'|'$blue);
imagestring($img210020'|'$blue);
imagestring($img25020'text'$white);

// SHIFT THE BOUNDING BOX
for($i=0$i<7$i+=2){
    
$b[$i] += $x;
    
$b[$i+1] += $y;
}

// WRITE WIDTH AND HEIGHT and DIAGONAL
$coords 'w: '.distance($b[4], $b[5], $b[6], $b[7]);
imagestring($img216010$coords$red);
$coords 'h: '.distance($b[0], $b[1], $b[6], $b[7]);
imagestring($img216020$coords$blue);
$coords 'diag: '.distance($b[0], $b[1], $b[4], $b[5]);
imagestring($img223010$coords$purple);

// PHP VERSION AND GD VERSION IN UPPER RIGHT CORNER
$coords 'PHP '.phpversion();
imagestring($img231010$coords$white);

$gdinfo gd_info();
$coords 'GD '.$gdinfo['GD Version'];
imagestring($img231020$coords$white);

// MAKE A NICE POLYGON
imageline($img$b[0], $b[1], $b[2], $b[3], $red);// bottom
imageline($img$b[4], $b[5], $b[6], $b[7], $red);// top
imageline($img$b[0], $b[1], $b[6], $b[7], $blue);// left
imageline($img$b[2], $b[3], $b[4], $b[5], $blue);// right
imageline($img$b[0], $b[1], $b[4], $b[5], $purple);// diagonal
// DOTS ON THE CORNER POINTS
for($i=0$i<8$i+=2){
    
$col $i $orange $yellow;
    
imagefilledellipse($img$b[$i], $b[$i+1], $dotsize$dotsize$col);
}

// WRITE THE EQUATION ON THE BOTTOM
$coords "imagettfbbox({$fontsize}{$ang}, \$font, '{$str}')";
imagestring($img210480$coords$green);
imagestring($img210468'bbox values relative to green dot (0, 0)'$green);
// CREATE A "STARTING POINT" FOR THE COORDINATES OF FONT WRITING
imagefilledellipse($img$x$y$dotsize$dotsize$green);

header('Content-type: image/gif');
imagegif($img);
imagedestroy($img);
exit;
?>