we are trying to justify a long text into a image and we need to justify it. We have solved the problem with this function, but it's too heavy and takes a long time to process the text justification. Any faster solution in order to justify a text?
$image = ImageCreateFromJPEG( "sample.jpg" );
$color = imagecolorallocate($image, 0, 0, 0);
$font = 'arial.ttf';
$text1 = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum';
$a = imagettftextjustified($image, 20, 0, 50, 50, $color, $font,$text1, 500, $minspacing=3,$linespacing=1);
header('Content-type: image/jpeg');
imagejpeg($image,NULL,100);
function imagettftextjustified(&$image, $size, $angle, $left, $top, $color, $font, $text, $max_width, $minspacing=3,$linespacing=1)
{
$wordwidth = array();
$linewidth = array();
$linewordcount = array();
$largest_line_height = 0;
$lineno=0;
$words=explode(" ",$text);
$wln=0;
$linewidth[$lineno]=0;
$linewordcount[$lineno]=0;
foreach ($words as $word)
{
$dimensions = imagettfbbox($size, $angle, $font, $word);
$line_width = $dimensions[2] - $dimensions[0];
$line_height = $dimensions[1] - $dimensions[7];
if ($line_height>$largest_line_height) $largest_line_height=$line_height;
if (($linewidth[$lineno]+$line_width+$minspacing)>$max_width)
{
$lineno++;
$linewidth[$lineno]=0;
$linewordcount[$lineno]=0;
$wln=0;
}
$linewidth[$lineno]+=$line_width+$minspacing;
$wordwidth[$lineno][$wln]=$line_width;
$wordtext[$lineno][$wln]=$word;
$linewordcount[$lineno]++;
$wln++;
}
for ($ln=0;$ln<=$lineno;$ln++)
{
$slack=$max_width-$linewidth[$ln];
if (($linewordcount[$ln]>1)&&($ln!=$lineno)) $spacing=($slack/($linewordcount[$ln]-1));
else $spacing=$minspacing;
$x=0;
for ($w=0;$w<$linewordcount[$ln];$w++)
{
imagettftext($image, $size, $angle, $left + intval($x), $top + $largest_line_height + ($largest_line_height * $ln * $linespacing), $color, $font, $wordtext[$ln][$w]);
$x+=$wordwidth[$ln][$w]+$spacing+$minspacing;
}
}
return true;
}
-
2\$\begingroup\$ Can you provide sample calls to this function? \$\endgroup\$ggorlen– ggorlen2024年02月27日 17:42:49 +00:00Commented Feb 27, 2024 at 17:42
-
\$\begingroup\$ We have edited and added a sample of a call to this function \$\endgroup\$Picture with words– Picture with words2024年02月27日 19:52:22 +00:00Commented Feb 27, 2024 at 19:52
1 Answer 1
This submission is about performance, yet it contains no timing measurements.
Your summary complaint seems to be: For N words we have N calls to bbox() and to render text(), which will take a Long Time.
Ok. Let's see how we could make fewer calls. Absent timing measurements I'm going to assume that both calls have approximately equal cost. I'm going to assume there's some call overhead, so that
imagettftext( ... , "hello world", ... )
runs quicker than
imagettftext( ... , "hello", ... )
imagettftext( ... , "world", ... )
OP approach
extract helper
foreach ($words as $word)
{
...
}
for ($ln = 0; $ln <= $lineno; $ln++)
{
...
for ($w = 0; $w < $linewordcount[$ln]; $w++)
Please break out the foreach
code as a helper,
and that first for
as another helper function.
Each function will have a
single responsibility.
composite a line at a time
That nested for
executes, IDK, more than ten times per line?
And yet you already know from bbox() which words will appear
on the line. Glue some of them together, at least in the
case where $spacing
is "small" or is equal to $minspacing
.
Pick one or two break locations, and send 1/2 the words
or 1/3 of the words into text() rendering at a time.
Downside is slightly different word spacing within the line,
even though you still justify to avoid ragged right.
LaTeX approach
Use a professional solution that achieves high quality typesetting results. Ask LaTeX to render several lines of text as a .PNG bitmap, and composite that into your final graphic image.
letter metrics approach
We don't exactly have to call bbox() at all, not in this program.
Write a program that calls bbox() for 26 upper + lower letters, plus a few punctuation marks and whatever other characters appear in your input corpus. Remember the widths, and write them out to a JSON database.
When compositing text, start by reading in that JSON file.
Author a helper function that given a word or phrase
will use the recorded widths to estimate what bbox() would say.
It won't get kerning 100% right, but it will be close enough,
as it will know that "l"
is skinnier than "m"
.
You might even feed the entire text to the helper,
and let it make line break decisions.