Skip to content

Instantly share code, notes, and snippets.

@y2k-shubham
Created June 26, 2020 17:20
Show Gist options
  • Star 2 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save y2k-shubham/3c2e648c457d43ba0aba5f89c7a33caf to your computer and use it in GitHub Desktop.
Save y2k-shubham/3c2e648c457d43ba0aba5f89c7a33caf to your computer and use it in GitHub Desktop.
Utility PHP snippet to wrap text (meant to be rendered on image) using pixel width
<?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");
}
}
<?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