Created
June 26, 2020 17:20
Utility PHP snippet to wrap text (meant to be rendered on image) using pixel width
This file contains 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 | |
namespace Utils; | |
class ImageTextRenderUtils { | |
/** | |
* Returns expected width of rendered text in pixels | |
* @param string $text | |
* @param string $font | |
* @param int $font_size | |
* @return int width in pixels | |
*/ | |
public static function getWidthPixels(string $text, string $font, int $font_size): int { | |
// https://www.php.net/manual/en/function.imageftbbox.php#refsect1-function.imageftbbox-returnvalues | |
$bbox = imageftbbox($font_size, 0, $font, " " . $text); | |
return $bbox[2] - $bbox[0]; | |
} | |
/** | |
* Returns wrapped format (with newlines) of a piece of text | |
* (meant to be rendered on an image) | |
* using the width of rendered bounding box of text | |
* | |
* The returned wrapped text (with newlines inserted at appropriate places) | |
* when rendered on image will fit within a width given by $line_max_pixels | |
* | |
* The method does NOT break long words (that cannot be accomodated within | |
* $line_max_pixels) into multiple lines | |
* | |
* @param string $text Text to be wrapped | |
* @param int $line_max_pixels Maximum no of pixels (horizontally) that the | |
* text is allowed to occupy before words are forced into newline | |
* @param int $font_size | |
* @param string $font | |
* @return string Wrapped text containing newlines inserted at appropriate places | |
*/ | |
public static function wrapTextByPixels( | |
string $text, | |
int $line_max_pixels, | |
int $font_size, | |
string $font | |
): string { | |
$words = explode(' ', $text); // tokenize the text into words | |
$lines = []; // Array[Array[string]]: array to store lines of words | |
// each element of array is an array of words Array[string] | |
// representing a single lines of words in wrapped output | |
$crr_line_idx = 0; // (zero-based) index of current lines in which words are being added | |
$crr_line_pixels = 0; // width of current line (in which words are being added) in pixels | |
foreach ($words as $word) { | |
// determine the new width of current line (in pixels) which will | |
// result if the current word is added to it (including space) | |
$crr_line_new_pixels = $crr_line_pixels + ImageTextRenderUtils::getWidthPixels(' ' . $word, $font, $font_size); | |
// determine the width of current word in pixels | |
$crr_word_pixels = ImageTextRenderUtils::getWidthPixels($word, $font, $font_size); | |
if ($crr_word_pixels > $line_max_pixels) { | |
// if the current word itself is too long to fit in single line | |
// then we have no option: it must still be put in oneline only | |
if ($crr_line_pixels == 0) { | |
// but it is put into current line only if current line is empty | |
$lines[$crr_line_idx] = array($word); | |
$crr_line_idx++; | |
} else { | |
// otherwise if current line is non-empty, | |
// then the extra long word is put into a newline | |
$crr_line_idx++; | |
$lines[$crr_line_idx] = array($word); | |
// (and $crr_line_idx is incremented so that new-words get | |
// shifted to fresh line after the current extra long word) | |
$crr_line_idx++; | |
$crr_line_pixels = 0; | |
} | |
} else if ($crr_line_new_pixels > $line_max_pixels) { | |
// otherwise if new width of current line (including current word and space) | |
// will exceed the maximum permissible width, | |
// then force the current word into newline | |
$crr_line_idx++; | |
$lines[$crr_line_idx] = array($word); | |
// and assign the length of current word as the length of that newline | |
$crr_line_pixels = $crr_word_pixels; | |
} else { | |
// else if the current word (including space) can fit in the current | |
// line, then put it there | |
$lines[$crr_line_idx][] = $word; | |
// and increment the width of current line | |
$crr_line_pixels = $crr_line_new_pixels; | |
} | |
} | |
// after the above foreach loop terminates, the $lines 2-d array Array[Array[string]] | |
// would contain words segregated into lines to preserve the $line_max_pixels | |
// now we just need to stitch together lines (array of word strings) into a single continuous piece of text with | |
// - spaces ' ' between words of single line and | |
// - newlines '\n' between words of separate lines | |
$concatenated_string = array_reduce( | |
$lines, | |
static function (string $wrapped_text, array $crr_line): string { | |
return $wrapped_text . PHP_EOL . implode(' ', $crr_line); | |
}, | |
'' | |
); | |
// the above process of concatenating lines into single piece of text will inadvertently | |
// add an extra newline '\n' character in the beginning; so we must remove that | |
return StringUtils::removeFirstOccurrence($concatenated_string, "\n"); | |
} | |
} |
This file contains 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 | |
namespace Utils; | |
class StringUtils { | |
/** | |
* Replaces the first occurrence of $needle from $haystack with $replace | |
* and returns the resultant string | |
* @param string $haystack | |
* @param string $needle | |
* @param string $replace | |
* @return string | |
*/ | |
public static function replaceFirstOccurence(string $haystack, string $needle, string $replace): string { | |
// reference: https://stackoverflow.com/a/1252710/3679900 | |
$pos = strpos($haystack, $needle); | |
if ($pos !== false) { | |
$new_string = substr_replace($haystack, $replace, $pos, strlen($needle)); | |
} | |
return $new_string; | |
} | |
/** | |
* Removes the first occurrence $needle from $haystack and returns the resulting string | |
* @param string $haystack | |
* @param string $needle | |
* @return string | |
*/ | |
public static function removeFirstOccurrence(string $haystack, string $needle): string { | |
return self::replaceFirstOccurence($haystack, $needle, ''); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment