шрифты — текст из коробки для преобразования в изображение в Stack Overflow

Я пытаюсь преобразовать текст в изображение. Я уже сделал это, но есть несколько случаев, когда текст выходит из поля изображенияОбраз

«Е» слова «The» отрезано. Я попытался уменьшить размер шрифта или увеличить ширину изображения, но в некоторых случаях это повторяется с другим текстом. Это код:

    $new_line_position = 61;
$angle = 0;
$left = 20;
$top = 45;
$image_width = 1210;
$image_line_height = 45;

$content_input = wordwrap($content_input,    $new_line_position, "\n", true);

$lineas = preg_split('/\\n/', $content_input);
$lines_breaks = count($lineas);
$image_height = $image_line_height * $lines_breaks;
$im = imagecreatetruecolor($image_width, $image_height);

// Create some colors
$white = imagecolorallocate($im, 255, 255, 255);
$black = imagecolorallocate($im, 0, 0, 0);
imagefilledrectangle($im, 0, 0, $image_width, $image_height, $white);

$font_ttf =  public_path().'/fonts/'.$ttf_font;foreach($lineas as $linea){
imagettftext($im, $font_size, $angle, $left, $top, $black, $font_ttf, $linea);
$top = $top + $image_line_height;
}

// Add the text
imagepng($im);
imagedestroy($im);

Спасибо.

11

Решение

Проблема в том, что каждый отдельный символ может иметь немного другую ширину, например W а также i, В связи с тем, что вы не можете разбить строку на количество букв в строке, вам нужен более точный метод.

Основная хитрость заключается в использовании

imagettfbbox

который дает ограничивающую рамку текста с использованием шрифтов TrueType, и из этого вы можете получить реальную ширину, которую будет использовать текст.

Вот функция для идеального разделения пикселей на http://php.net/manual/en/function.wordwrap.php
используйте его вместо переноса слов и передайте дополнительные значения, такие как ширина изображения, размер шрифта и путь к шрифту

<?php

/**
* Wraps a string to a given number of pixels.
*
* This function operates in a similar fashion as PHP's native wordwrap function; however,
* it calculates wrapping based on font and point-size, rather than character count. This
* can generate more even wrapping for sentences with a consider number of thin characters.
*
* @static $mult;
* @param string $text - Input string.
* @param float $width - Width, in pixels, of the text's wrapping area.
* @param float $size - Size of the font, expressed in pixels.
* @param string $font - Path to the typeface to measure the text with.
* @return string The original string with line-breaks manually inserted at detected wrapping points.
*/
function pixel_word_wrap($text, $width, $size, $font)
{

#    Passed a blank value? Bail early.
if (!$text)
return $text;

#    Check if imagettfbbox is expecting font-size to be declared in points or pixels.
static $mult;
$mult = $mult ?: version_compare(GD_VERSION, '2.0', '>=') ? .75 : 1;

#    Text already fits the designated space without wrapping.
$box = imagettfbbox($size * $mult, 0, $font, $text);
if ($box[2] - $box[0] / $mult < $width)
return $text;

#    Start measuring each line of our input and inject line-breaks when overflow's detected.
$output = '';
$length = 0;

$words      = preg_split('/\b(?=\S)|(?=\s)/', $text);
$word_count = count($words);
for ($i = 0; $i < $word_count; ++$i) {

#    Newline
if (PHP_EOL === $words[$i])
$length = 0;

#    Strip any leading tabs.
if (!$length)
$words[$i] = preg_replace('/^\t+/', '', $words[$i]);

$box = imagettfbbox($size * $mult, 0, $font, $words[$i]);
$m   = $box[2] - $box[0] / $mult;

#    This is one honkin' long word, so try to hyphenate it.
if (($diff = $width - $m) <= 0) {
$diff = abs($diff);

#    Figure out which end of the word to start measuring from. Saves a few extra cycles in an already heavy-duty function.
if ($diff - $width <= 0)
for ($s = strlen($words[$i]); $s; --$s) {
$box = imagettfbbox($size * $mult, 0, $font, substr($words[$i], 0, $s) . '-');
if ($width > ($box[2] - $box[0] / $mult) + $size) {
$breakpoint = $s;
break;
}
}

else {
$word_length = strlen($words[$i]);
for ($s = 0; $s < $word_length; ++$s) {
$box = imagettfbbox($size * $mult, 0, $font, substr($words[$i], 0, $s + 1) . '-');
if ($width < ($box[2] - $box[0] / $mult) + $size) {
$breakpoint = $s;
break;
}
}
}

if ($breakpoint) {
$w_l = substr($words[$i], 0, $s + 1) . '-';
$w_r = substr($words[$i], $s + 1);

$words[$i] = $w_l;
array_splice($words, $i + 1, 0, $w_r);
++$word_count;
$box = imagettfbbox($size * $mult, 0, $font, $w_l);
$m   = $box[2] - $box[0] / $mult;
}
}

#    If there's no more room on the current line to fit the next word, start a new line.
if ($length > 0 && $length + $m >= $width) {
$output .= PHP_EOL;
$length = 0;

#    If the current word is just a space, don't bother. Skip (saves a weird-looking gap in the text).
if (' ' === $words[$i])
continue;
}

#    Write another word and increase the total length of the current line.
$output .= $words[$i];
$length += $m;
}

return $output;
}
;

?>

Ниже приведен пример работы кода:
Я модифицировал эту функцию pixel_word_wrap совсем немного. Также, изменены некоторые вычисления в вашем коде. Прямо сейчас дает мне идеальное изображение с правильно рассчитанными полями. Я не очень доволен кодом заметил, что есть переменная $ Adjust, которая должна быть больше, когда вы используете больший размер шрифта. Я думаю, что это до несовершенства в imagettfbbox функция. Но это практичный подход, который хорошо работает с большинством размеров шрифта.

<?php

$angle = 0;
$left_margin = 20;
$top_margin = 20;
$image_width = 1210;
$image_line_height = 42;
$font_size = 32;
$top = $font_size + $top_margin;

$font_ttf = './OpenSans-Regular.ttf';

$text = 'After reading Mr. Gatti`s interview I finally know what bothers me so much about his #elenaFerrante`s unamsking. The whole thing is about him, not the author, not the books, just himself and his delusion of dealing with some sort of unnamed corruption';$adjustment=  $font_size *2; //

$adjustment=  $font_size *2; // I think because imagettfbbox is buggy adding extra adjustment value for text width calculations,

function pixel_word_wrap($text, $width, $size, $font) {

#    Passed a blank value? Bail early.
if (!$text) {
return $text;
}$mult = 1;
#    Text already fits the designated space without wrapping.
$box = imagettfbbox($size * $mult, 0, $font, $text);

$g = $box[2] - $box[0] / $mult < $width;

if ($g) {
return $text;
}

#    Start measuring each line of our input and inject line-breaks when overflow's detected.
$output = '';
$length = 0;

$words = preg_split('/\b(?=\S)|(?=\s)/', $text);
$word_count = count($words);
for ($i = 0; $i < $word_count; ++$i) {

#    Newline
if (PHP_EOL === $words[$i]) {
$length = 0;
}

#    Strip any leading tabs.
if (!$length) {
$words[$i] = preg_replace('/^\t+/', '', $words[$i]);
}

$box = imagettfbbox($size * $mult, 0, $font, $words[$i]);
$m = $box[2] - $box[0] / $mult;

#    This is one honkin' long word, so try to hyphenate it.
if (($diff = $width - $m) <= 0) {
$diff = abs($diff);

#    Figure out which end of the word to start measuring from. Saves a few extra cycles in an already heavy-duty function.
if ($diff - $width <= 0) {
for ($s = strlen($words[$i]); $s; --$s) {
$box = imagettfbbox($size * $mult, 0, $font,
substr($words[$i], 0, $s) . '-');
if ($width > ($box[2] - $box[0] / $mult) + $size) {
$breakpoint = $s;
break;
}
}
}

else {
$word_length = strlen($words[$i]);
for ($s = 0; $s < $word_length; ++$s) {
$box = imagettfbbox($size * $mult, 0, $font,
substr($words[$i], 0, $s + 1) . '-');
if ($width < ($box[2] - $box[0] / $mult) + $size) {
$breakpoint = $s;
break;
}
}
}

if ($breakpoint) {
$w_l = substr($words[$i], 0, $s + 1) . '-';
$w_r = substr($words[$i], $s + 1);

$words[$i] = $w_l;
array_splice($words, $i + 1, 0, $w_r);
++$word_count;
$box = imagettfbbox($size * $mult, 0, $font, $w_l);
$m = $box[2] - $box[0] / $mult;
}
}

#    If there's no more room on the current line to fit the next word, start a new line.
if ($length > 0 && $length + $m >= $width) {
$output .= PHP_EOL;
$length = 0;

#    If the current word is just a space, don't bother. Skip (saves a weird-looking gap in the text).
if (' ' === $words[$i]) {
continue;
}
}

#    Write another word and increase the total length of the current line.
$output .= $words[$i];
$length += $m;
}

return $output;
}$out = pixel_word_wrap($text, $image_width -$left_margin-$adjustment,
$font_size, $font_ttf);$lineas = preg_split('/\\n/', $out);
$lines_breaks = count($lineas);
$image_height = $image_line_height * $lines_breaks;
$im = imagecreatetruecolor($image_width, $image_height + $top);

// Create some colors
$white = imagecolorallocate($im, 255, 255, 255);
$black = imagecolorallocate($im, 0, 0, 0);
imagefilledrectangle($im, 0, 0, $image_width, $image_height + $top, $white);foreach ($lineas as $linea) {
imagettftext($im, $font_size, $angle, $left_margin, $top, $black, $font_ttf,
$linea);
$top = $top + $image_line_height;
}header('Content-Type: image/png');
imagepng($im);

Вот пример

введите описание изображения здесь
введите описание изображения здесь

Вы также можете использовать моноширинный шрифт. Моноширинный шрифт — это шрифт, буквы и символы которого занимают одинаковое количество горизонтального пространства.

9

Другие решения

Проблема в том, что ваш шрифт имеет переменную ширину для каждой буквы, но вы усекаете его по количеству букв, а не по ширине шрифта.

Возьмите следующий пример: десять «я» против десять «W», второй будет более чем вдвое длиннее.

iiiiiiiiii

WWWWWWWWWW

«Простой» вариант — использовать моноширинный шрифт, такой как Courier, который используется в блоке ниже:

iiiiiiiiii
WWWWWWWWWW

Но это скучный шрифт! Так что вам нужно использовать ìmagettfbbox (Функция True Image Font Bounding Box » http://php.net/manual/en/function.imagettfbbox.php) на каждой строке, чтобы получить ширину. Вы должны запускать эту функцию по одной строке за раз, уменьшая размеры, пока не получите нужный размер.

Небольшой фрагмент кода (обратите внимание: написанный от руки и не проверенный, вам нужно будет переделать его, чтобы сделать его идеальным):

$targetPixelWidth = 300;
$maximumChactersPerLine = 200;  // Make this larger then you expect, but too large will slow it down!
$textToDisplay = "Your long bit of text goes here"$aLinesToDisplay = array();
while (strlen(textToDisplay) > 0) {
$hasTextToShow = false;
$charactersToTest = $maximumChactersPerLine;
while (!$hasTextToShow && $maximumChactersPerLine>0) {
$wrappedText = wordwrap($textToDisplay, $maximumChactersPerLine);
$aSplitWrappedText = explode("\n", $wrappedText);
$firstLine = trim($aSplitWrappedText[0]);
if (strlen($firstLine) == 0) {
// Fallback to "default"$charactersToTest = 0;
} else {
$aBoundingBox = imagettfbbox($fontSize, 0, $firstLine, $yourTTFFontFile);
$width = abs($aBoundingBox[2] - $aBoundingBox[0]);
if ($width <= $targetPixelWidth) {
$hasTextToShow = true;
$aLinesToDisplay[] = $firstLine;
$textToDisplay = trim(substr($textToDisplay, strlen($firstLine));
} else {
--$charactersToTest;
}
}
}
if (!$hasTextToShow) {
// You need to handle this by getting SOME text (e.g. first word) and decreasing the length of $textToDisplay, otherwise you'll stay in the loop forever!
$firstLine = ???; // Suggest split at first "space" character (Use preg_split on \s?)
$aLinesToDisplay[] = $firstLine;
$textToDisplay = trim(substr($textToDisplay, strlen($firstLine));
}
}
// Now run the "For Each" to print the lines.

Предостережение: функция TTF Bounding box тоже не идеальна — так что позвольте немного «подвисать», но вы все равно получите намного, гораздо лучшие результаты, которые вы делаете выше (то есть + -10 пикселей). Это также зависит от информации о кернинге (пробелах между буквами) шрифтового файла. Немного потрясений и прочтения комментариев в руководстве поможет вам получить более точные результаты, если вам это нужно.

Вы также должны оптимизировать функцию выше (начните с 10 символов и увеличивайте, взяв последнюю подходящую строку, вы можете получить более быстрый ответ, чем уменьшать, пока что-то не подходит, и уменьшить количество strlen звонки например).


Приложение в ответ на комментарий «Можете ли вы расширить» Функция ограничивающего прямоугольника TTF тоже не идеальна? » (ответ слишком длинный для комментария)

Функция опирается на информацию о кернинге в шрифте. Например, вы хотите, чтобы V сидело ближе к A (VA — посмотрите, как они «слегка перекрываются»), чем вы бы V и W (VW — посмотрите, как W начинается после V). Есть много правил, встроенных в шрифты, касающиеся этого интервала. Некоторые из этих правил также гласят: «Я знаю, что« прямоугольник »начинается с 0, но для этой буквы нужно начать рисовать с -3 пикселей».

PHP лучше всего читает правила, но иногда ошибается и поэтому дает неправильные измерения. Это причина того, почему вы можете сказать PHP писать с «0,0», но на самом деле он начинается с «-3,0» и, похоже, обрезает шрифт. Самое простое решение — разрешить несколько пикселей.

Да, это хорошо отмеченная «проблема» (https://www.google.com/webhp?q=php%20bounding%20box%20incorrect)

4

По вопросам рекламы [email protected]