Created
April 11, 2015 18:22
-
-
Save jubstuff/633b07aa5aa3cdc14a4b to your computer and use it in GitHub Desktop.
Wrap text in imagick
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?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; | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
// http://codereview.stackexchange.com/questions/33424/performance-of-autofit-text-method-using-imagick-is-horrible-unfortunately | |
function fitImageAnnotation( Imagick $image, ImagickDraw $draw, $text, $maxHeight, $leading = 1, $strokeWidth = 0.04, $margins = array( 10, 10, 10, 10 ) ) | |
{ | |
if( strlen( $text ) < 1 ) | |
{ | |
return; | |
} | |
$imageWidth = $image->getImageWidth(); | |
$imageHeight = $image->getImageHeight(); | |
// margins are css-type margins: T, R, B, L | |
$boundingBoxWidth = $imageWidth - $margins[ 1 ] - $margins[ 3 ]; | |
$boundingBoxHeight = $imageHeight - $margins[ 0 ] - $margins[ 2 ]; | |
// We begin by setting the initial font size | |
// to the maximum allowed height, and work our way down | |
$fontSize = $maxHeight; | |
$textLength = strlen( $text ); | |
// Start the main routine where the culprits are | |
do | |
{ | |
$probeText = $text; | |
$probeTextLength = $textLength; | |
$lines = explode( "\n", $probeText ); | |
$lineCount = count( $lines ); | |
$draw->setFontSize( $fontSize ); | |
$draw->setStrokeWidth( $fontSize * $strokeWidth ); | |
$fontMetrics = $image->queryFontMetrics( $draw, $probeText, true ); | |
// This routine will try to wordwrap() text until it | |
// finds the ideal distibution of words over lines, | |
// given the current font size, to fit the bounding box width | |
// If it can't, it will fall through and the parent | |
// enclosing routine will try a smaller font size | |
while( $fontMetrics[ 'textWidth' ] >= $boundingBoxWidth ) | |
{ | |
// While there's no change in line lengths | |
// decrease wordwrap length (no point in | |
// querying font metrics if the dimensions | |
// haven't changed) | |
$lineLengths = array_map( 'strlen', $lines ); | |
do | |
{ | |
$probeText = wordwrap( $text, $probeTextLength ); | |
$lines = explode( "\n", $probeText ); | |
// This is one of the performance culprits | |
// I was hoping to find some kind of binary | |
// search type algorithm that eliminates | |
// the need to decrease the length only | |
// one character at a time | |
$probeTextLength--; | |
} | |
while( $lineLengths === array_map( 'strlen', $lines ) && $probeTextLength > 0 ); | |
// Get the font metrics for the current line distribution | |
$fontMetrics = $image->queryFontMetrics( $draw, $probeText, true ); | |
if( $probeTextLength <= 0 ) | |
{ | |
break; | |
} | |
} | |
// Ignore font metrics textHeight, we'll calculate our own | |
// based on our $leading argument | |
$lineHeight = $leading * $fontSize; | |
$lineSpacing = ( $leading - 1 ) * $fontSize; | |
$lineCount = count( $lines ); | |
$textHeight = ( $lineCount * $fontSize ) + ( ( $lineCount - 1 ) * $lineSpacing ); | |
// This is the other performance culprit | |
// Here I was also hoping to find some kind of | |
// binary search type algorithm that eliminates | |
// the need to decrease the font size only | |
// one pixel at a time | |
$fontSize -= 1; | |
} | |
while( $textHeight >= $maxHeight || $fontMetrics[ 'textWidth' ] >= $boundingBoxWidth ); | |
// The remaining part is no culprit, it just draws the final text | |
// based on our calculated parameters | |
$fontSize = $draw->getFontSize(); | |
$gravity = $draw->getGravity(); | |
if( $gravity < Imagick::GRAVITY_WEST ) | |
{ | |
$y = $margins[ 0 ] + $fontSize + $fontMetrics[ 'descender' ]; | |
} | |
else if( $gravity < Imagick::GRAVITY_SOUTHWEST ) | |
{ | |
$y = $margins[ 0 ] + ( $boundingBoxHeight / 2 ) - ( $textHeight / 2 ) + $fontSize + $fontMetrics[ 'descender' ]; | |
} | |
else | |
{ | |
$y = ( $imageHeight - $textHeight - $margins[ 2 ] ) + $fontSize; | |
} | |
$alignment = $gravity - floor( ( $gravity - .5 ) / 3 ) * 3; | |
if( $alignment == Imagick::ALIGN_LEFT ) | |
{ | |
$x = $margins[ 3 ]; | |
} | |
else if( $alignment == Imagick::ALIGN_CENTER ) | |
{ | |
$x = $margins[ 3 ] + ( $boundingBoxWidth / 2 ); | |
} | |
else | |
{ | |
$x = $imageWidth - $margins[ 1 ]; | |
} | |
$draw->setTextAlignment( $alignment ); | |
$draw->setGravity( 0 ); | |
foreach( $lines as $line ) | |
{ | |
$image->annotateImage( $draw, $x, $y, 0, $line ); | |
$y += $lineHeight; | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment