画像処理

【PHP】GDで文字列を右端で改行して範囲内に描画する方法

更新日:2023/10/03

PHPのGDを使用して、範囲指定された領域に文字列を描画します。
一行が右端を超えるときは折り返します。

このページの目次

 

考え方

今回は文字間隔を変更できるようにします。
GDの文字描画関数ImageTTFText()は文字間隔を変更できないので、一文字ずつ抜き出して幅と文字間隔を加算していき、右端を超えない文字までを1行と判断します。

描画するときは、1行に描画する文字のベースライン(y座標)を揃える必要があります。
ベースラインは文字のバウンドボックス( imagettfbbox()で取得 )の上座標を用いて算出します。
この値は文字ごとに異なるため、描画を行う全ての文字で最小の値を求める必要があります。

このため、文字の描画は1行分の文字を確定した後におこないます。

1行を描画したら次の行を描画します。

文字描画とベースラインの関係については、次のページを参考にしてください。
【PHP】ImageTTFText()で左上を起点として文字を描画する方法

 

コード

考え方を元に作成した関数コードです。

/*
* GDで範囲内に文字列を描画する
* $image:  GdImage オブジェクト
* $fontSize: フォントサイズ
* $left , $top , $right , $bottom : 描画範囲
* $color: 文字色
* $fontFile: フォントファイルのパス
* $texts: 描画する文字列
* $characterSpacing: 文字間隔
* $lineSpacing: 行間隔
* $lineHeight: 行の高さ 0は文字に合わせて調整
*/
function imageTtfTextArea($image , $fontSize , $left , $top , $right , $bottom
    ,  $color , $fontFile , $text ,$characterSpacing=0,$lineSpacing=0,$lineHeight=0){

    $len = mb_strlen($text);if( $len === 0 ) return;

        // 行の高さの既定値算出
    $bBox = imagettfbbox($fontSize ,0,$fontFile,'A');
    $defaultHeight = $bBox[1] - $bBox[7]; // 左下Y - 左上Y 

    $currentTop = $top;
    $count = 0;
    while( $count < $len ){
        $outputTexts = array(); // 行データ [ 文字 , x座標 ]の配列
        $textTop = PHP_INT_MAX; // 文字列の上端座標
        $textHeight = $defaultHeight; // 文字列の高さ
        $x = $left; // 現在の文字位置

            // 1行分の文字列抽出
        for(;  $count < $len ; $count++ ){
            $c = mb_substr( $text , $count , 1 );
            if( $c === "\n" ) {$count++;break;} // 改行→行終了

            $bBox = imagettfbbox($fontSize ,0 ,$fontFile,$c);
            $w = $bBox[4] - $bBox[6]; // 文字幅算出
            $wX = $x + $w;
            if( $wX  > $right || $wX < $left ) break; // 右端または左端を超えた

            $textHeight = max( $textHeight , $bBox[1] - $bBox[7] );
            if( $currentTop + $textHeight > $bottom ) return; // 下端を超えた

            $textTop = min( $textTop , $bBox[7] );
            $outputTexts[] = [$c,$x-$bBox[0]]; // [ 文字 , x座標 ]
            $x = $wX + $characterSpacing; // 次の文字位置に移動
            if( $x > $right || $x < $left ){ $count++; break;} // 右端または左端を超えた
        }

        $baseY = $currentTop - $textTop; // y座標(ベースライン)算出
            // 1行を描画
        foreach( $outputTexts as $v ){
            ImageTTFText($image, $fontSize, 0, $v[1], $baseY
                , $color, $fontFile, $v[0]);
        }
            // 次の行に移動
        $currentTop += ($lineHeight ===0 ? $textHeight : $lineHeight ) + $lineSpacing;
        if( $currentTop > $bottom || $currentTop < $top) return; // 下端または上端を超えた
    }
}

文字列は \n で改行します。
そのため改行が含まれる可能性があるときは、\n に統一する必要があります。

作成した関数の使用例です。

$left = 50; $top = 50; $right = 450; $bottom = 220;

$fontSize = 20;
$fontFile = './ZenMaruGothic-Bold.ttf';

$text =<<<EOF
文字列を指定範囲の右端で折り返して描画します。
abcd

↑空行も改行します。


この行は描画されません。
EOF;

    // 改行を \nに統一
$text = str_replace( ["\r\n","\r"], "\n" , $text );
    // 新規画像作成&背景を白で塗りつぶし
$image = imagecreatetruecolor($left*2+$right-$left, $top*2+$bottom-$top);
$white = imagecolorallocate($image, 255, 255, 255);
imagefill($image, 0, 0, $white);
    // 描画範囲を赤で可視化
$red = imagecolorallocate($image, 255, 0, 0);
imagerectangle($image,$left,$top,$right,$bottom,$red);

$black = imagecolorallocate($image, 0, 0, 0);

imageTtfTextArea($image,$fontSize,$left,$top,$right,$bottom
    ,$black,$fontFile,$text,10);

header("Content-type: image/jpeg");
imagejpeg($image);

次のような画像が生成されます。

実行結果

関数の最後のパラメーターは、値が0以外なら1行の高さを固定します。

例えば青で等間隔に入れた罫線に合わせて、文字を描画できます。

$blue = imagecolorallocate($image, 0, 0, 255);
for( $y = $top ; $y < $bottom ; $y += 25 )
    imageline($image,$left,$y,$right,$y,$blue);

$black = imagecolorallocate($image, 0, 0, 0);

imageTtfTextArea($image,$fontSize,$left,$top,$right,$bottom
    ,$black,$fontFile,$text,10,0,25);

罫線に合わせて文字を描画

更新日:2023/10/03

書いた人(管理人):けーちゃん

スポンサーリンク

記事の内容について

null

こんにちはけーちゃんです。
説明するのって難しいですね。

「なんか言ってることおかしくない?」
たぶん、こんなご意見あると思います。

裏付けを取りながら記事を作成していますが、僕の勘違いだったり、そもそも情報源の内容が間違えていたりで、正確でないことが多いと思います。
そんなときは、ご意見もらえたら嬉しいです。

掲載コードについては事前に動作確認をしていますが、貼り付け後に体裁を整えるなどをした結果動作しないものになっていることがあります。
生暖かい視線でスルーするか、ご指摘ください。

ご意見、ご指摘はこちら。
https://note.affi-sapo-sv.com/info.php

 

このサイトは、リンクフリーです。大歓迎です。