PHP Tutorial: Creating A MemeGenerator / Cheezburger Clone - Part 3
| May 10, 2012 | Posted by Greg Bulmash under PHP |
Part III: Resizing and Wrapping Text With Imagick
In part 2, I dealt with creating a stroke around the text and getting it onto the picture. In this part, I'll make sure that text doesn't get too big for the photo. The main way do do this is with the Imagick::queryFontMetrics() method.
With queryFontMetrics(), you can feed ImageMagick a theoretical text object and get back the stats on how it would render before you actually add it to the photo. You'll remember from part 2 that $img was the Imagick object I created for the photo and $draw was the ImagickDraw object I created to hold all the font properties.
To get back an array with the font object's properties, I use:
$metrics = $img->queryFontMetrics($draw,$text);
Instead of simply setting a font size, I set a maximum and minimum, setting the initial font size to the maximum. How did I come up with them? Trial and error.
$maxfont=30; $minfont=18;
Right before I start drawing the text on the image, I added this code to resize and wrap the text.
//Before we draw the text, //make it no wider than 94% of image size $imgSize = $img->getImageGeometry(); $maxwidth = intval($imgSize['width'] * .94); $printtext = false; $metrics = $img->queryFontMetrics($draw,$text); $fontratio = $maxfont/$minfont; $textratio = $metrics['textWidth'] / $maxwidth;
Now we know the ratio of the maximum font size to the minimum font size and the ratio of the text width to the maximum 94% width. If text width divided by max width is less than max font size divided by minimum font size, we can just change the font size to make it fit. But if it's greater, even re-sizing won't handle it. We need to split the text. For that, I use PHP's wordwrap() function.
The wordwrap() function takes 4 arguments:
- the string being split
- how many characters in (max) the first split should occur
- the split character or tag
- an optional true/false value (default = false) whether it should split words that are longer than the max character limit in argument 2.
Rather than leave one word at the bottom, we'll set argument two at 55% of the length of the text. We also shrink the font by 20% just because two lines of the max size is huge.
// split the text if the textwidth/maxlength ratio
// is greater than the maxfont/minfont ratio
if($textratio > $fontratio){
$wraplength = intval(strlen($text) *.55);
$text = wordwrap($text,$wraplength,"\n",true);
// make it a bit smaller since it will be multiline
$maxfont = floor($maxfont *.8);
$draw->setFontSize($maxfont);
//get new metrics
$metrics = $img->queryFontMetrics($draw,$text);
}
Now that we've possibly changed the result of the queryFontMetrics() by splitting and slightly shrinking the text, we'll check once more to make sure it fits. If it doesn't, we'll shrink some more.
// See if additional shrinking is needed $textratio = $metrics['textWidth'] / $maxwidth; if($textratio > 1) $maxfont = floor($maxfont * (1/$textratio)); $draw->setFontSize($maxfont);
And here's the result...

There won't be a tip on Friday (extenuating circumstances), but on Monday I'll finish up with how to save the photo to disk, and how to handle escaped characters (try an apostrophe in your test text).
thanks, for great script. but in my script queryFontMetrics return 0 for $metrics['textWidth']. what should i do. plz help.
I haven't run into that before and it's not easily Googled. Are you having this problem on all fonts or just one/some?