用于在php中转换为图像的开箱即用文本

时间:2016-10-06 16:49:42

标签: php fonts gd true-type-fonts imagettftext

我正在尝试将文字转换为图片。我已经这样做了,但有些情况下文字不在图像框Image

" e" ""被削减。我尝试过减小字体大小或增加图像的宽度,但在某些情况下,这会再次发生在另一个文本中。这是代码:

    $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);

谢谢。

2 个答案:

答案 0 :(得分:9)

问题是每个字符的宽度可能略有不同,例如//functions are using #defined values (...) //functions are using #defined values (...) 。因为你不能按行数字符串分割字符串,所以你需要一个更准确的方法。

主要技巧是使用

W

使用TrueType字体给出文本的边界框,从中可以获得文本将使用的实际宽度。

这是http://php.net/manual/en/function.wordwrap.php找到的像素完美分割功能 使用它而不是wordwrap并传递额外的值,如图像宽度,字体大小和字体路径

i

下面是工作代码示例: 我稍微修改了这个函数imagettfbbox 。此外,修改了代码中的一些计算。现在正在给我一个正确计算边距的完美图像。我不是很满意代码注意到有一个$调整变量,当你使用更大的字体大小时应该更大。我认为这归结于<?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

这是一个例子

enter image description here enter image description here

您也可以使用等宽字体。等宽字体是一种字体,其字母和字符各自占据相同数量的水平空间。

答案 1 :(得分:4)

问题是你的字体是每个字母的可变宽度,但你是根据字母数而不是字体宽度截断的。

采取以下示例,10&#34;我&#34; vs&#34; W&#34;,第二个将超过两倍。

IIIIIIIIII

WWWWWWWWWW

&#34;简单&#34;选项是使用等宽字体,例如Courier,它在下面的块中使用:

iiiiiiiiii
WWWWWWWWWW

但那是一个无聊的字体!所以你需要的是在每一行上使用ìmagettfbbox(图像真实类型字体边界框&#34;函数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边界框功能也不完美 - 所以允许一些&#34; lee way&#34;但是你仍然会得到远远好于你上面做的更好的结果(即+ -10像素)。它还取决于字体文件字距调整(字母之间的间隙)信息。如果您需要,有点Goggling并阅读手册中的注释将帮助您获得更准确的结果。

你还应该优化上面的功能(以10个字符开头并增加,取适合的最后一个字符串可以让你得到一个更快的答案,直到某些东西适合,并减少strlen个调用的数量,例如)

回复评论的附录&#34;你可以扩展&#34; TTF边界框功能也不完美&#34;?&#34; (回复太长,无法发表评论)

该功能依赖于&#34;字距调整&#34;字体中的信息。例如,你希望V更接近A(VA - 看他们如何&#34;重叠&#34;稍微),而不是V和W(VW - 看看W在V之后如何开始)。关于该间距的字体中嵌入了许多规则。其中一些规则也说'#34;我知道&#39;框&#39;从0开始,但是对于这个字母,你需要开始绘制-3像素&#34;。

PHP最好阅读规则,但有时会出错,因此会给你错误的维度。这就是为什么你可以告诉PHP写出&#34; 0,0&#34;但它实际上始于&#34; -3,0&#34;并似乎切断了字体。最简单的解决方案是允许几个像素宽限。

是的,这是一个引人注目的问题&#34; (https://www.google.com/webhp?q=php%20bounding%20box%20incorrect