How can I wrap text using Imagick in PHP so that it is drawn as multiline text?
Usage:
list($lines, $lineHeight) = wordWrapAnnotation($image, $draw, $msg, 140);
for($i = 0; $i < count($lines); $i++)
$image->annotateImage($draw, $xpos, $ypos + $i*$lineHeight, 0, $lines[$i]);
Function:
/* Implement word wrapping... Ughhh... why is this NOT done for me!!!
OK... I know the algorithm sucks at efficiency, but it's for short messages, okay?
Make sure to set the font on the ImagickDraw Object first!
@param image the Imagick Image Object
@param draw the ImagickDraw Object
@param text the text you want to wrap
@param maxWidth the maximum width in pixels for your wrapped "virtual" text box
@return an array of lines and line heights
*/
function wordWrapAnnotation(&$image, &$draw, $text, $maxWidth)
{
$words = explode(" ", $text);
$lines = array();
$i = 0;
$lineHeight = 0;
while($i < count($words) )
{
$currentLine = $words[$i];
if($i+1 >= count($words))
{
$lines[] = $currentLine;
break;
}
//Check to see if we can add another word to this line
$metrics = $image->queryFontMetrics($draw, $currentLine . ' ' . $words[$i+1]);
while($metrics['textWidth'] <= $maxWidth)
{
//If so, do it and keep doing it!
$currentLine .= ' ' . $words[++$i];
if($i+1 >= count($words))
break;
$metrics = $image->queryFontMetrics($draw, $currentLine . ' ' . $words[$i+1]);
}
//We can't add the next word to this line, so loop to the next line
$lines[] = $currentLine;
$i++;
//Finally, update line height
if($metrics['textHeight'] > $lineHeight)
$lineHeight = $metrics['textHeight'];
}
return array($lines, $lineHeight);
}
I found a bug with @BMiner's function where it returns a lineheight of 0 when there is only one word.
I ended up re-writing it in one loop using array functions. I kept the parameters the same so it works with current implementations.
I used preg_split instead so it works well with extra or double spaces, tabs, and line-breaks.
function wordWrapAnnotation($image, $draw, $text, $maxWidth)
{
$words = preg_split('%\s%', $text, -1, PREG_SPLIT_NO_EMPTY);
$lines = array();
$i = 0;
$lineHeight = 0;
while (count($words) > 0)
{
$metrics = $image->queryFontMetrics($draw, implode(' ', array_slice($words, 0, ++$i)));
$lineHeight = max($metrics['textHeight'], $lineHeight);
if ($metrics['textWidth'] > $maxWidth or count($words) < $i)
{
$lines[] = implode(' ', array_slice($words, 0, --$i));
$words = array_slice($words, $i);
$i = 0;
}
}
return array($lines, $lineHeight);
}
I have been using @Sarke's version successfully for a while, but I noticed there is an infinite loop if a word is longer than the $maxWidth. Here is a version that fixes the infinite loop:
function wordWrapAnnotation($image, $draw, $text, $maxWidth)
{
$text = trim($text);
$words = preg_split('%\s%', $text, -1, PREG_SPLIT_NO_EMPTY);
$lines = array();
$i = 0;
$lineHeight = 0;
while (count($words) > 0)
{
$metrics = $image->queryFontMetrics($draw, implode(' ', array_slice($words, 0, ++$i)));
$lineHeight = max($metrics['textHeight'], $lineHeight);
// check if we have found the word that exceeds the line width
if ($metrics['textWidth'] > $maxWidth or count($words) < $i)
{
// handle case where a single word is longer than the allowed line width (just add this as a word on its own line?)
if ($i == 1)
$i++;
$lines[] = implode(' ', array_slice($words, 0, --$i));
$words = array_slice($words, $i);
$i = 0;
}
}
return array($lines, $lineHeight);
}