imagettfbbox calculates wrong rectangle when text begins with number
Asked Answered
A

1

17

The issue is, that when using imagettfbbox to calculate text dimensions, too small rectangle is returned when input text begins with numbers. This is my code:

$fontSize = 150;
$font = "font/courier_new.ttf";
$text = $_GET["text"];

//Determine font dimensions
$bbox = imagettfbbox($fontSize, 0, $font, $text);   
$bbox["width"]=  abs($bbox[4]-$bbox[0]);
$bbox["height"]= abs($bbox[5]-$bbox[1]);

$im = imagecreatetruecolor($bbox["width"], $bbox["height"]);

echo "<b>Image size:</b>\n";
print_r($bbox);

// This part makes transparent background
imagesavealpha($im, true);
$bg = imagecolorallocatealpha($im, 254, 254, 254,127); 
$text_color= imagecolorallocate($im, 0, 0, 0);
imagealphablending($im, false);
imagefilledrectangle($im, 0, 0, imagesx($im), imagesy($im), $bg );
imagealphablending($im, true); 


header("X-Img-Size: ".$bbox["width"]."x".$bbox["height"]."");

imagettftext($im, $fontSize, 0, 0, $bbox["height"], $text_color, $font, $text);

// This is output    
header("Content-Type: text/html");  
ob_start();
imagepng($im);
$image_data = ob_get_contents();
ob_end_clean();
imagedestroy($im);

echo "<img src=\"data:image/png;base64,".base64_encode($image_data)."\" />";

Online test here

These are results I get for different input text:

image description

image description

image description

How can I fix that?

Annam answered 29/4, 2016 at 4:37 Comment(6)
This appears to be a known problem; see php.net/manual/en/function.imagettfbbox.php, esp. peterjwest3's answer with a proposed fix. The writer of the function must have taken some shortcuts... It appears to use FT_BBox, except that this would "give the glyph's descender" correctly.Marquardt
@TomášZato - did you go to php.net/manual/en/function.imagettfbbox.php and read through the suggested workarounds, esp. the one from "mike at mikeleigh dot com" from 8 years ago? If so is there some reason that his suggested workaround/fix won't workaround/fix the issue you're having?Suburbia
@BobJarvis I can't swear I used every workaround there, but this code already applies some of the suggested code - those equations for width and height come from that page. I'll try the exact workaround you mentioned.Detriment
when text begins with number - For what it's worth, this doesn't seem to like anything other that the letter 'M'. Almost everything else, including other letters, seems to make the height slightly too small and it really doesn't like punctuation. Some letters even cause the width issue.Salmon
@BobJarvis Ok, I guess the problem was I never used the x and y coordinates and assumed [0, 0] when drawing. If I use x and y from the function, it works as it should: u8.8u.cz/testing/ttf/extended.phpDetriment
Just an idea for a very dirty hacky workaround: If your text starts with a number, prepend a zero-width character, for example the zero-width space: en.wikipedia.org/wiki/Zero-width_spaceSeverson
A
9

The problem was caused by misconception. The values of imagettfbbox also define where do you have to start drawing - and often those coordinates are even negative. I always assumed you can start at [0, 0] coordinates. That is not true, drawing coordinates can be negative.

This function, also mentioned in comments and originating from PHP.net user contributions calculates the start coordinates, as well as the width and height (which was correct in the code in question).

// Source: http://php.net/manual/en/function.imagettfbbox.php#75407
function imagettfbboxextended($size, $angle, $fontfile, $text) {
    /*this function extends imagettfbbox and includes within the returned array
    the actual text width and height as well as the x and y coordinates the
    text should be drawn from to render correctly.  This currently only works
    for an angle of zero and corrects the issue of hanging letters e.g. jpqg*/
    $bbox = imagettfbbox($size, $angle, $fontfile, $text);

    //calculate x baseline
    if($bbox[0] >= -1) {
        $bbox['x'] = abs($bbox[0] + 1) * -1;
    } else {
        //$bbox['x'] = 0;
        $bbox['x'] = abs($bbox[0] + 2);
    }

    //calculate actual text width
    $bbox['width'] = abs($bbox[2] - $bbox[0]);
    if($bbox[0] < -1) {
        $bbox['width'] = abs($bbox[2]) + abs($bbox[0]) - 1;
    }

    //calculate y baseline
    $bbox['y'] = abs($bbox[5] + 1);

    //calculate actual text height
    $bbox['height'] = abs($bbox[7]) - abs($bbox[1]);
    if($bbox[3] > 0) {
        $bbox['height'] = abs($bbox[7] - $bbox[1]) - 1;
    }

    return $bbox;
}

But it is imperative that you use x and y coordinates given by this function when drawing:

imagettftext($im, $fontSize, 0, $bbox["x"], $bbox["y"], $text_color, $font, $text);
Annam answered 7/5, 2016 at 12:23 Comment(2)
Great on you for self-answering what appears to be an expert-level problem. Thanks!Pilothouse
@StijndeWitt I wouldn't have figured it out without comments on my question though.Detriment

© 2022 - 2024 — McMap. All rights reserved.