Hi. Thanks for the reply. I'm not sure if the negative widths is the cause of this or not- it seems odd that I can't find anyone else having these issues. I've been experimenting all day and have found a workaround.
The offset to the right only happens when the string starts with certain characters - "1" is one of the culprits. If the string starts with a space, then the dimensions are always correct, in every font, but if it starts with a "1", then the offset happens in 90% of the fonts I've tried.
So I used imagettfbbox to calculate the width of the string with a leading space, then calculate the space individually, and subtracted it. This gave me the bound box all the way to the right-hand edge, although the start was still shifted to the right. Subtracting this width from the original width gave me exactly how many pixels the text had been shifted, meaning I was able to compensate for this issue when drawing the string.
The code below correctly places every string within the bounding box. It's a messy workaround, but it works.
<?php
// Create image and colours
$im = imagecreatetruecolor(200, 100);
$white = imagecolorallocate($im, 255, 255, 255);
$black = imagecolorallocate($im, 0, 0, 0);
$red = imagecolorallocate($im, 255, 0, 0);
//Set the background to be white
imagefilledrectangle($im, 0, 0, 200, 100, $white);
//font file
$font = "ARIAL.TTF";
//set sample text and fontsize
$text="102";
$size = 25;
//calculate height and width of text
$bbox = imagettfbbox($size, 0, $font, $text);
$width = $bbox[2] - $bbox[0];
$height = $bbox[5] - $bbox[3];
//calculate the width of the text with a leading space
$bbox2 = imagettfbbox($size, 0, $font, " ".$text);
$width2 = $bbox2[2] - $bbox2[0];
//calculate the width of a space by itself
$bbox3 = imagettfbbox($size, 0, $font, " ");
$width3 = $bbox3[2] - $bbox3[0];
//subtract space from long string
$newwidth = $width2 - $width3;
//subtract the new width from the original to get the offset
$offset = $newwidth-$width;
//draw rectangle that should house the text
imagefilledrectangle($im, 50, 40, 50+$width, 40+$height, $red);
//draw text at same startpoint as rectangle - adding the offset to the x coordinate
imagettftext($im, $size, 0, 50-$offset, 40, $black, $font, $text);
// Output to browser
header('Content-type: image/png');
imagepng($im);
imagedestroy($im);
?>