Skip to content

Instantly share code, notes, and snippets.

@clifgriffin
Last active May 4, 2022 18:04
Show Gist options
  • Star 18 You must be signed in to star a gist
  • Fork 6 You must be signed in to fork a gist
  • Save clifgriffin/728cc3a4ce7b81fa2d8a to your computer and use it in GitHub Desktop.
Save clifgriffin/728cc3a4ce7b81fa2d8a to your computer and use it in GitHub Desktop.
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;
}
@ibes
Copy link

ibes commented Nov 18, 2016

The font color is not set anywhere.

Can be set through:

$draw->setFillColor($pixel);

@encendedor
Copy link

Great script - thank you! I just have one problem and can't find a solution: If the text contains a very long word that's width is greater than the max_width the script dies on line 64. Any idea?

@y2kfresh
Copy link

y2kfresh commented Mar 8, 2018

^ same problem, subscribed

@pmashelkar
Copy link

pmashelkar commented Apr 12, 2018

does the alignment for the text always have to be "left" ?

@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

@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

@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