Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Auto fit text to image using PHP / Imagick
<?php
/**
* Auto Fit Text To Image
*
* Write text to image using a target width, height, and starting font size.
*
* @author Clif Griffin
* @url http://cgd.io/2014/auto-fit-text-to-an-image-with-php-and-wordpress
*
* @access public
* @param bool $canvas_image_filename (default: false) The base image.
* @param string $dest_filename (default: 'output.jpg') The output filename.
* @param string $text (default: '') The text being written.
* @param int $starting_font_size (default: 60) The starting (max) font size.
* @param int $max_height (default: 500) The maximum height in lines of text.
* @param int $x_pos (default: 0) X position in pixels for first line of text.
* @param int $y_pos (default: 0) Y position in pixels for first line of text.
* @param bool $font_file (default: false) Path to font file (.ttf)
* @param string $font_color (default: 'black') The color. Also accepts rgba values. Example: "rgba(0,0,0,0.5)"
* @param int $line_height_ratio (default: 1) Allows scaling the line height. Should be between 0.1 and 1.
* @param string $dest_format (default: 'jpg') Output format.
* @return bool True: success, False: failure
*/
function autofit_text_to_image( $canvas_image_filename = false, $dest_filename = 'output.jpg', $text = '', $starting_font_size = 60, $max_width = 500, $max_height = 500, $x_pos = 0, $y_pos = 0, $font_file = false, $font_color = 'black', $line_height_ratio = 1, $dest_format = 'jpg' ) {
// Bail if any essential parameters are missing
if ( ! $canvas_image_filename || ! $dest_filename || empty($text) || ! $font_file || empty($font_color) || empty($max_width) || empty($max_height) ) return false;
// Do we have a valid canvas image?
if ( ! file_exists($canvas_image_filename) ) return;
$canvas_handle = fopen( $canvas_image_filename, 'rb' );
// Load image into Imagick
$NewImage = new Imagick();
$NewImage->readImageFile($canvas_handle);
// Instantiate Imagick utility objects
$draw = new ImagickDraw();
$pixel = new ImagickPixel( $font_color );
// Load Font
$font_size = $starting_font_size;
$draw->setFont($font_file);
$draw->setFontSize($font_size);
// Holds calculated height of lines with given font, font size
$total_height = 0;
// Run until we find a font size that doesn't exceed $max_height in pixels
while ( 0 == $total_height || $total_height > $max_height ) {
if ( $total_height > 0 ) $font_size--; // we're still over height, decrement font size and try again
$draw->setFontSize($font_size);
// Calculate number of lines / line height
// Props users Sarke / BMiner: http://stackoverflow.com/questions/5746537/how-can-i-wrap-text-using-imagick-in-php-so-that-it-is-drawn-as-multiline-text
$words = preg_split('%\s%', $text, -1, PREG_SPLIT_NO_EMPTY);
$lines = array();
$i = 0;
$line_height = 0;
while ( count($words) > 0 ) {
$metrics = $NewImage->queryFontMetrics( $draw, implode(' ', array_slice($words, 0, ++$i) ) );
$line_height = max( $metrics['textHeight'], $line_height );
if ( $metrics['textWidth'] > $max_width || count($words) < $i ) {
$lines[] = implode( ' ', array_slice($words, 0, --$i) );
$words = array_slice( $words, $i );
$i = 0;
}
}
$total_height = count($lines) * $line_height * $line_height_ratio;
if ( $total_height === 0 ) return false; // don't run endlessly if something goes wrong
}
// Writes text to image
for( $i = 0; $i < count($lines); $i++ ) {
$NewImage->annotateImage( $draw, $x_pos, $y_pos + ($i * $line_height * $line_height_ratio), 0, $lines[$i] );
}
$NewImage->setImageFormat($dest_format);
$result = $NewImage->writeImage($dest_filename);
return $result;
}
@konoknir
Copy link

konoknir commented Aug 1, 2018

for very long words to work the second while loop should look like this:

if ( $metrics['textWidth'] > $max_width || count($words) < $i ) {
	// this indicates long words and forces the font to decrease in the first loop
	if($i == 1){
		$total_height = $max_height + 1;
		continue 2;
	}
	$lines[] = implode( ' ', array_slice($words, 0, --$i) );
	$words = array_slice( $words, $i );
	$i = 0;
}

Center alignment could be done with this:

$draw->setGravity(\Imagick::GRAVITY_CENTER);

But you need to adjust annotations vertical position because first line is placed right in the middle

for( $i = 0, $c = count($lines), $gravity_fix = ($total_height / 2 - $line_height / 2) ; $i < $c; $i++ ) 
{
	$layerTextHeader->annotateImage( $draw, $x, $y + ($i * $line_height * $line_height_ratio) - $gravity_fix, 0, $lines[$i] );	
}

Also I would check the $font_size for positive values - just return if $font_size < 1 right after decreasing

@alexandrly
Copy link

alexandrly commented Dec 13, 2018

@konoknir Hi, Thank you for your solution. Сan you suggest solutions so that with transition to another line, you can display the text, because now everything is together. Very necessary.

@alexandrly
Copy link

alexandrly commented Dec 13, 2018

@clifgriffin Thank you very much!
Сan you suggest solutions so that with transition to another line, you can display the text, because now everything is together. Very necessary.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment