How can I wrap text using Imagick in PHP so that it is drawn as multi-line text?

The Imagick library in PHP allows you to draw text on top of an image.

How can I tell Imagick to wrap text based on a limited text field so that words appear as multi-line text, rather than as a single line?

+5
source share
5 answers

Using:

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]); 

Functions:

 /* Implement word wrapping... Ughhh... why is this NOT done for me!!! OK... I know the algorithm sucks at efficiency, but it 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); } 
+24
source

I found an error with the @BMiner function, where it returns a string with 0 when there is only one word.

I ended up rewriting it in a single loop using array functions. I saved the settings the same way, so it works with current implementations.

Instead, I used preg_split to work 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); } 
+7
source

I have successfully used the @Sarke version, but I noticed that there is an infinite loop if the word is longer than $ maxWidth. Here is a version that captures an 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); } 
+4
source

here is my version for one text container

 function GetTextSize($font,$text,$max_weight,$max_width){ $size = $max_weight; $imagick=new Imagick(); while (true){ $draw = new ImagickDraw(); $draw->setFontSize($size); $draw->setfont($font); $bbox2=$imagick->queryFontMetrics($draw,$text); $width_of_text = $bbox2[textWidth]; if ($width_of_text > $max_width){ $size -= 1; }else{ break; } } return $size; } $draw = new ImagickDraw(); $font="path_to_font.ttf"; $text="Love Happyness Freedom"; $output = new Imagick('path_to_image.jpg'); $output->setGravity(Imagick::GRAVITY_CENTER); $fontsize=GetTextSize($font,$text,70,600); $draw->setfont($font); $draw->setFontSize($fontsize); $draw->annotation(000, 000, $text); $output->drawImage($draw); $output->setImageFormat('jpg'); header('Content-Type: image/jpg'); print $output; 
0
source

Hi, I found thanks for BMinner for his code, I edit his code and get a good labor solution.

USING

 <? $w = 210; $h = 520; $canvas = new Imagick(); $canvas->newImage($w,$h,new ImagickPixel('green'),'png'); $draw = new ImagickDraw(); $draw->setFontSize(25); $text="SomeTextWithoutSpacesAndGoingOn..xxxxx <br><br>some short words with spaces <br><br>and some text<br>with manuel page<br>break <br><br>and also multiple spaces spaces end. also witonec ha ra c ter"; list($lines, $lineHeight)= wordWrapAnnotation($canvas, $draw, $text, $w-20); $canvas->annotateImage($draw, 10, $lineHeight , 0, $lines); header("Content-Type: image/png"); echo $canvas; ?> 

FUNCTIONS RELATED TO BMinner

 <? //this is unicode split method for out of english latin characters function str_split_unicode($str, $l = 0) { if ($l > 0) { $ret = array(); $len = mb_strlen($str, "UTF-8"); for ($i = 0; $i < $len; $i += $l) { $ret[] = mb_substr($str, $i, $l, "UTF-8"); } return $ret; } return preg_split("//u", $str, -1, PREG_SPLIT_NO_EMPTY); } //this is my function detects long words and split them function check_long_words($image,$draw,$text,$maxWidth) { $metrics = $image->queryFontMetrics($draw, $text); if($metrics['textWidth'] <= $maxWidth) return array($text); $words = str_split_unicode($text); $i = 0; while($i < count($words) ) { $currentLine = $words[$i]; if($i+1 >= count($words)) { $lines[] = $currentLine; //$lines = $lines + $checked; 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]); $t++; } //We can't add the next word to this line, so loop to the next line $lines[] = $currentLine; $i++; } return $lines; } //this is BMiner code some fixes for manule breaks function wordWrapAnnotation(&$image, &$draw, $text, $maxWidth) { $brler = explode("<br>", $text); $lines = array(); foreach($brler as $br) { $i = 0; $words = explode(" ", $br); while($i < count($words) ) { $currentLine = $words[$i]; $metrics = $image->queryFontMetrics($draw, $currentLine . ' ' . $words[$i+1]); if($i+1 >= count($words)) { $checked=check_long_words($image,$draw,$currentLine,$maxWidth); $lines = array_merge($lines, $checked); if($metrics['textHeight'] > $lineHeight) $lineHeight = $metrics['textHeight']; //$lines = $lines + $checked; break; } //Check to see if we can add another word to this line 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]); $t++; } //We can't add the next word to this line, so loop to the next line $checked=check_long_words($image,$draw,$currentLine,$maxWidth); $lines = array_merge($lines, $checked); $i++; //Finally, update line height if($metrics['textHeight'] > $lineHeight) $lineHeight = $metrics['textHeight']; } } return array(join("\n",$lines), $lineHeight); } ?> 

AND EXIT

enter image description here

-one
source

All Articles