Текст из коробки для преобразования в изображение в php

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

"e" слова "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);

Спасибо.

Ответ 1

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

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

imagettfbbox

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

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

<?php

/**
 * Wraps a string to a given number of pixels.
 * 
 * This function operates in a similar fashion as PHP 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 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 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 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 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 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);

Вот пример

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

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

Ответ 2

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

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

iiiiiiiiii

WWWWWWWWWW

"Простой" вариант заключается в использовании моноширинного шрифта, такого как Courier, который используется в следующем блоке:

iiiiiiiiii
WWWWWWWWWW

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

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

$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 также не идеальна, поэтому разрешите немного "lee way", но вы все равно получите гораздо лучшие результаты, которые вы делаете выше (т.е. + -10 пикселей), Это также зависит от информации о кернинге шрифтов (пробелов между буквами). Немного огласки и чтение комментариев в руководстве помогут вам получить более точные результаты, если вам это нужно.

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


Добавление в ответ на комментарий "Можете ли вы продолжить", функция TTF Bounding box не идеальна ни "?" (ответ слишком длинный для комментария)

Функция полагается на информацию о кернинге в шрифте. Например, вы хотите, чтобы 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)