Как я учился позиционировать текст нанесенный на изображение в зависимости от размеров блока и длины текста или Расширение библиотеки для работы с изображениями в Codeigniter

Fork me on GitHub

Недавно по работе и по доброй воле пришлось столкнуться с заполнением изображения-шаблона.

То есть имеется изображение с отведенными пустыми местами под надписи и другие «встроенные» изображения. Нужно этот шаблон наполнить пользовательскими данными и отдать пользователю для печати.

Все это уже почти по традиции выполнялось в рамках проекта, стоящего на Codeigniter 2. На сервере был только gdlib.

Встроенная библиотека Image_lib.php умеет делать thumbnails и водяные знаки. Естественно я начал искать альтернативы. И нашел их в php-классе PHP-Image-Class, о котором дальше и пойдет речь.

Библиотека

Почему я собственно выбрал именно эту библиотеку я вполне себе представляю. Это простота использования. Кстати с таким же успехом, как я подключил ее к Codeigniter, она подключается и к чему угодно, написанному на php, ибо никаких зависимостей, кроме php5 и gdlib не имеет.

Ясность, понятные названия методов, минимум лишних настроек.

Примеры кстати можно найти здесь

Довольно немного, но достаточно. Следующим примером я воспользовался для размещения всех изображений поверх основного изображения-шаблона:

<?php
    require_once('../image.php');        // Load image class

    $b = new Image();            // Create new Image object b
    $b->createFromFile('img/b.png');    // Create image from file     file  : b.png
    $b->resize(0, 150);            // Scale image            height: 150px

    $a = new Image();            // Create new Image object a
    $a->createFromFile('img/a.jpg');    // Create image from file     file    : a.jpg
    $a->merge($b, 'right', 30);        // Put image b on image a    position: right/30
    $a->resize(200, 0);            // Scale image            width   : 200px

    $a->show();                // Show result
?>

 

С текстом тоже на первый взгляд все казалось просто. Да, разместил я текст просто изменив пример с сайта. Но потом появились нюансы.

Нюансы и расширение класса

Текст в шаблоне должен был быть центрирован. Кроме того, он мог быть как однострочным, так и двух, трех и даже четырехстрочным. Причем высота блока, в котором он находился была ограничена, а значит в зависимости от количества строк должен был менятяться еще и размер шрифта. Так, чтобы надпись была центрированой и всегда влезала по ширине и высоте в блок.

Центрирование

Первую задачу я решил довольно просто. Я добавил метод Image::getTextSize(), фактически просто враппер для встроенной функции gdlib, просто возвращающий не координаты, а ширину и высоту.

Потом я дополнил метод Image::write() так, чтобы была возможность вместо X-координаты добавлять параметры ‘center’, ‘left’ и ‘right’ для соответственного выравнивания по центру и краям родительского блока.

Перенос слов и динамическое изменение размера текста

Для этого я создал метод Image::writeMultiline().

Для переноса слов пришлось поделить всю строку на слова и составлять строки программно, начиная новую строку каждый раз, когда она могла превысить допустимый предел блока. Длину строки я получал с помощью описанной выше Image::getTextSize().

Для динамического изменения размера шрифта, я считал высоту получаемых строк, и если она превышала допустимый предел блока, размеры которого указаны в настройках, то тогда программной размер шрифта уменьшался на заданный  шаг и процесс начинался с начала. Так происходило до тех пор, пока весь текст не помещался в заданный блок.

Собственно сам метод:

/**
 * Writes on image
 *
 * @param integer $x X-coordinate, string: 'center', 'left', 'right'
 * @param integer $y Y-coordinate
 * @param integer $boxWidth the width of the box for text
 * @param integer $boxHeight the height of the box for text
 * @param string $text Text
 * @param array $params Text
                'font' => string, path to font
                'startFontSize' => integer, font size for starting. If text wouldn't fit the height, then font size will be decreased
                'stepFontSize' => integer, step for font size decreasing
                'angle' => font angle
                'color' => conf color
                'lineSpacing' => integer, spacing between lines
                'padding' => string in format: '20 10', where first - padding for top and bottom, and second - for left and right
 * @acces public
 */
public function writeMultiline($x, $y, $boxWidth = null, $boxHeight = null, $text, $params = array())
{
        $config = array(
                'font' => ( isset($params['font']) ? $params['font'] : null ),
                'startFontSize' => ( isset($params['startFontSize']) ? $params['startFontSize'] : 12 ),
                'stepFontSize' => ( isset($params['stepFontSize']) ? $params['stepFontSize'] : 1 ),
                'angle' => ( isset($params['angle']) ? $params['angle'] : null ),
                'color' => ( isset($params['color']) ? $params['color'] : '#000000' ),
                'lineSpacing' => ( isset($params['lineSpacing']) ? $params['lineSpacing'] : null ),
                'padding' => ( isset($params['padding']) ? $params['padding'] : '0 0' ),
        );
        
        // parsing padding
                $padding = array();
                $config['padding'] = str_replace('px','',$config['padding']);
                if (strstr($config['padding'],' '))
                        list($padding['top_bottom'],$padding['left_right']) = explode(' ',$config['padding']);
                else
                {
                        $padding['top_bottom'] = $config['padding'];
                        $padding['left_right'] = $config['padding'];
                }
        
        // if lineSpacing not defined
                if (is_null($config['lineSpacing']))
                {
                        $config['lineSpacing'] = ceil($config['startFontSize']/10);
                }
        
        // box width with padding
                $boxWidth = !is_null($boxWidth) ? $boxWidth : $this->width;
                $boxWidth = $boxWidth - $padding['left_right']*2;
                
        // box height with padding
                $boxHeight = !is_null($boxHeight) ? $boxHeight : $this->height;
                $boxHeight = $boxHeight - $padding['top_bottom']*2;
        
        $words = explode(' ',$text);
        $forever = true;
        
        while($forever)
        {
                $lines = array();
                $i = 0;
                $line_length = 0;
                $lines_height = 0;
                foreach($words as $word)
                {
                        if (!isset($lines[$i]['string']))
                        {
                                $lines[$i]['string'] = '';
                                $lines[$i]['words'] = array();
                                $lines[$i]['width'] = 0;
                                $lines[$i]['height'] = 0;
                        }
                        
                        $word = $word.' ';
                        
                        $inf = $this->getTextSize($config['font'], $config['startFontSize'], $config['angle'], $lines[$i]['string'].$word);
                        
                        if ($inf['width'] > $boxWidth)
                        {
                                $lines[$i]['fontSize'] = $config['startFontSize'];
                                $lines[$i]['width'] = $inf['width'];
                                $lines[$i]['height'] = $inf['height'];
                                
                                $lines_height += $inf['height'] + $config['lineSpacing'];
                                $i++;
                        }
                        
                        
                                
                        if (!isset($lines[$i]['string']))
                        {
                                $lines[$i]['string'] = '';
                                $lines[$i]['words'] = array();
                                $lines[$i]['width'] = 0;
                                $lines[$i]['height'] = 0;
                        }
                                
                        $lines[$i]['words'][] = $word;
                        
                        $lines[$i]['string'] .= $word;
                }
                
                $lines[$i]['fontSize'] = $config['startFontSize'];
                $lines[$i]['width'] = $inf['width'];
                $lines[$i]['height'] = $inf['height'];
                
                // decreasing font size
                if ($lines_height > $boxHeight)
                {
                        $config['startFontSize'] -= $config['stepFontSize'];
                        continue;
                }
                else
                        break;

        }
        
        // start real writing to image
                $startY = $y;
                foreach($lines as $line)
                {
                        $this->write($x, $startY, $config['font'], $line['fontSize'], $config['angle'], $config['color'], $line['string']);
                        $startY += $line['height'];
                }
}

 

Для наглядности я добавил пример использования для этого метода:

<?php
    require_once('../image.php'); // Load image class
    
    $a = new Image(); // Create new Image object
    
    $small_string = 'Hello world!';   
    $large_string = 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam vestibulum velit sodales turpis feugiat dignissim pharetra dui consectetur. Aenean nec ante at enim sollicitudin ornare vel porta metus. Fusce lorem turpis, tristique nec consectetur vel, sodales cursus sapien. Curabitur in laoreet ante. ';

    // simple write small text on the image
        $a->write(100,200,'times.ttf',120,null,'#FFFFFF',$small_string);

    // write large text to the some area on the image with automaticaly word-wrap
    // and font size decreasing to fit all the text in specified box
        $config = array(
        'font' => 'times.ttf',
                'startFontSize' => 150, // If text wouldn't fit the $boxHeight, then font size will be decreased
                'stepFontSize' => 10,   // step for font size decreasing
                'angle' => null,
                'color' => '#FFFFFF',
                'lineSpacing' => null,  // if not definedm then will be startFontSize/10
                'padding' => '20 10',   // almost in css format :), acceptable two or one parameters
        );
        $boxWidth = null; // will be fetched from the $a->width
        $boxHeight = 300;
            $a->writeMultiline('center', 240, $boxWidth, $boxHeight, $large_string, $config);
    
    $a->show(); // Show image
?>

 

Таким образом задача была успешно решена. Библиотеку я форкнул, ее как обычно можно найти на github’e ->

Полезно(0)Бесполезно(0)

Добавить комментарий

4 Responses to “ Как я учился позиционировать текст нанесенный на изображение в зависимости от размеров блока и длины текста или Расширение библиотеки для работы с изображениями в Codeigniter ”

  1. Здравствуйте.

    Столкнулся с такой же проблемой, Не могли бы мне помочь?

    Полезно(0)Бесполезно(0)
    • Городецкий

      Городецкий

      Могу попробовать, если вы опишите ситуацию поподробнее.

      Полезно(0)Бесполезно(0)
  2. Пример Multiline не работает в просто PHP, выдаёт ошибки. То есть может в Codeigniter работает, но так - ошибки. Исправил, как мне кажется, ошибки (нужно сначала создать this->image, прежде чем вызывать write, использование $this в коде примера (вне контекста)). Но после исправления скрипт выполняется дольше 180 секунд (соответственно, Maximum execution).

    Полезно(0)Бесполезно(0)
    • Городецкий

      Городецкий

      Да, спасибо, закралась ошибка, должен быть $a вместо $this->image. Изначально этот код был в методе Codeigniter'а, поэтому закрался this. Исправил в статье.

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

      Полезно(0)Бесполезно(0)