|
@@ -27,6 +27,46 @@ use BotKit\Entities;
|
|
|
|
|
|
class Commands {
|
|
|
|
|
|
+ #region Темы
|
|
|
+
|
|
|
+ // Темы необходимы для настройки отрисовки таблиц - например расписания
|
|
|
+ // Каждая тема ОБЯЗАНА иметь следующие ключи:
|
|
|
+ // line_spacing - расстояние в пикселях между двумя строками текста
|
|
|
+ // padding - сколько пикселей добавлять яйчейкам таблицы (см. css box model)
|
|
|
+ // colors - массив цветов
|
|
|
+ // в colors тоже есть обязательные ключи
|
|
|
+ // background - цвет заднего фона
|
|
|
+ // title_color - цвет текста заголовка
|
|
|
+ // body_bg_even - фон чётных строк таблицы
|
|
|
+ // body_bg_odd - фон нечётных строк таблицы
|
|
|
+ // body_fg - цвет текста в строках таблиц
|
|
|
+ // title_font_size - размер шрифта (pt) заголовка
|
|
|
+ // body_font_size - размер шрифта (pt) в таблице
|
|
|
+ // body_line_constraints - ограничения на символы в строках таблицы
|
|
|
+ // title_line_size - ограничения на символы в заголовке таблицы
|
|
|
+ // font - путь к файлу шрифта
|
|
|
+
|
|
|
+ // В тему разрешается добавлять и другие ключи, как, например в теме оценок
|
|
|
+
|
|
|
+ private array $theme_schedule =
|
|
|
+ [
|
|
|
+ "line_spacing"=> 5,
|
|
|
+ "padding"=> 10,
|
|
|
+ "colors"=> [
|
|
|
+ "background"=> [220,220,220],
|
|
|
+ "title_color"=> [30, 30, 30],
|
|
|
+ "body_bg_even"=> [180, 180, 170],
|
|
|
+ "body_bg_odd" => [210, 210, 200],
|
|
|
+ "body_fg" => [40, 40, 40]
|
|
|
+ ],
|
|
|
+ "title_font_size"=> 30,
|
|
|
+ "body_font_size"=> 20,
|
|
|
+ "body_line_constraints" => [0, 40, 25],
|
|
|
+ "title_line_constraints" => 35,
|
|
|
+ "font" => rootdir.'resources/Lato-Regular.ttf'
|
|
|
+ ];
|
|
|
+ #endregion
|
|
|
+
|
|
|
#region Callback
|
|
|
|
|
|
// Отправка условий использования
|
|
@@ -450,5 +490,247 @@ class Commands {
|
|
|
// TODO: вернуть ID сообщения
|
|
|
}
|
|
|
|
|
|
+ // Разбивает длинную строку на линии, перенося слова
|
|
|
+ // (слова - это участки текста, разделённые пробелами)
|
|
|
+ private function splitLongString($text, $line_size) : array {
|
|
|
+
|
|
|
+ // Не разделять слова
|
|
|
+ if ($line_size == 0) {
|
|
|
+ return [$text];
|
|
|
+ }
|
|
|
+
|
|
|
+ $output = [];
|
|
|
+ $current_line = "";
|
|
|
+ $words = explode(" ", $text);
|
|
|
+
|
|
|
+ for ($i = 0; $i < count($words); $i++) {
|
|
|
+
|
|
|
+ // Если текущая строка после прибавления будет больше чем line_size,
|
|
|
+ // то её нужно будет перенести на новую строку.
|
|
|
+ // Если после переноса строка не вмещается в line_size, то разбить
|
|
|
+ // строку вручную на участки по line_size символов.
|
|
|
+ // А если строка вмещается, просто прибавить её.
|
|
|
+
|
|
|
+ $current_line_size = mb_strlen($current_line);
|
|
|
+ if ($current_line_size + mb_strlen($words[$i]) + 1 <= $line_size) {
|
|
|
+ // Слово вмещается в текущую строку
|
|
|
+ $current_line .= $words[$i]." ";
|
|
|
+ } else {
|
|
|
+ // Слово не вмещается. Завершаем строку
|
|
|
+ if ($current_line_size > 0) {
|
|
|
+ $output[] = $current_line;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (mb_strlen($words[$i]) + 1 > $line_size) {
|
|
|
+ // Строка после переноса не вмещается
|
|
|
+ while (mb_strlen($words[$i]) > $line_size) {
|
|
|
+ $output[] = mb_substr($words[$i], 0, $line_size);
|
|
|
+ $words[$i] = mb_substr($words[$i], $line_size);
|
|
|
+ }
|
|
|
+ $current_line = $words[$i]." ";
|
|
|
+ } else {
|
|
|
+ // Добавляем новую строку, сразу помещаем на неё слово
|
|
|
+ $current_line = $words[$i]." ";
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // Добавление оставшихся данных
|
|
|
+ $output[] = $current_line;
|
|
|
+
|
|
|
+ return $output;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Запускает процесс генерации, сохранения и загрузки таблицы
|
|
|
+ // $data - данные, которые помещаются в таблицу (квадратная матрица)
|
|
|
+ // $title - название
|
|
|
+ // $theme - настройки рисования
|
|
|
+ // $draw_body_line - функция отрисовки заднего фона таблицы
|
|
|
+ private function generateTable(
|
|
|
+ array $data,
|
|
|
+ string $title,
|
|
|
+ array $theme,
|
|
|
+ callable $draw_body_line) {
|
|
|
+
|
|
|
+ $width = count($data[0]);
|
|
|
+ $height = count($data);
|
|
|
+
|
|
|
+ // Для хранения высоты строк таблицы (некоторые яйчейки имеют несколько
|
|
|
+ // строк)
|
|
|
+ $row_sizes = [];
|
|
|
+
|
|
|
+ // Для хранения ширины столбцов таблицы (для полного вмещения текста)
|
|
|
+ $col_sizes = array_fill(0, $width, 0);
|
|
|
+
|
|
|
+ // Для хранения яйчеек текста
|
|
|
+ $cells = [];
|
|
|
+
|
|
|
+ #region Определение размеров тела таблицы
|
|
|
+ // заодно разбиваем длинный текст на строки
|
|
|
+ for ($y = 0; $y < $height; $y++) {
|
|
|
+ // -- Обработка одной строки таблицы --
|
|
|
+
|
|
|
+ $row_height = 0; // Высота отрисованной строки в пикселях
|
|
|
+ $row_lines = []; // Массив, содержащий строки яйчеек
|
|
|
+
|
|
|
+ for ($x = 0; $x < $width; $x++) {
|
|
|
+ // 1. Узнаём какие строки текста должны быть в яйчейке
|
|
|
+ if ($data[$y][$x] == null) {
|
|
|
+ $cell_lines = [' '];
|
|
|
+ } else {
|
|
|
+ $cell_lines = self::splitLongString(
|
|
|
+ $data[$y][$x],
|
|
|
+ $theme['body_line_constraints'][$x]
|
|
|
+ );
|
|
|
+ }
|
|
|
+ $row_lines[] = $cell_lines;
|
|
|
+
|
|
|
+ // 2. Вычисление и сохранение размеров текста в яйчейке
|
|
|
+ list($cell_width, $cell_height) = self::getTextSize(
|
|
|
+ $cell_lines,
|
|
|
+ $theme['line_spacing'],
|
|
|
+ $theme['font'],
|
|
|
+ $theme['body_font_size']
|
|
|
+ );
|
|
|
+
|
|
|
+ $cell_width += $theme['padding'] * 2;
|
|
|
+ $cell_height += $theme['padding'] * 2;
|
|
|
+
|
|
|
+ // 3. Определение макс. размера ширины колонки и высоты текущей
|
|
|
+ // строки
|
|
|
+ $col_sizes[$x] = max($cols_sizes[$x], $cell_width);
|
|
|
+ $row_height = max($row_height, $cell_height);
|
|
|
+ }
|
|
|
+
|
|
|
+ $row_sizes[] = $row_height;
|
|
|
+ $cells[] = $row_lines;
|
|
|
+ }
|
|
|
+ $body_width = array_sum($col_sizes) + $theme['padding'] * 2;
|
|
|
+ $body_height = array_sum($row_sizes) + $theme['padding'] * 2;
|
|
|
+
|
|
|
+ // Обработка названия таблицы
|
|
|
+ $title_lines = self::splitLongString($title, $theme['title_line_constraints']);
|
|
|
+ list($title_width, $title_height) = self::getTextSize(
|
|
|
+ $title_lines,
|
|
|
+ $theme['line_spacing'],
|
|
|
+ $theme['font'],
|
|
|
+ $theme['title_font_size']
|
|
|
+ );
|
|
|
+ $title_width += $theme['padding'] * 2;
|
|
|
+ $title_height += $theme['padding'] * 2;
|
|
|
+
|
|
|
+ // Вычисление размеров таблицы. Пытаемся сделать квадрат
|
|
|
+ $table_width = max($body_width, $title_width);
|
|
|
+ // Заголовок+пробел+тело
|
|
|
+ $table_height = $body_height + $title_height + $theme['padding'];
|
|
|
+
|
|
|
+ if ($table_width > $table_height) {
|
|
|
+ $table_height = $table_width;
|
|
|
+ } else {
|
|
|
+ $table_width = $table_height;
|
|
|
+ }
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ #region Отрисовка
|
|
|
+ $im = imagecreatetruecolor($table_width, $table_height);
|
|
|
+
|
|
|
+ $gdcolors = [];
|
|
|
+ foreach ($theme['colors'] as $color_name => $color) {
|
|
|
+ $gdcolors[$color_name] = imagecolorallocate(
|
|
|
+ $im,
|
|
|
+ $color[0],
|
|
|
+ $color[1],
|
|
|
+ $color[2]
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ // Заполнение заднего фона
|
|
|
+ imagefilledrectangle(
|
|
|
+ $im,
|
|
|
+ 0,
|
|
|
+ 0,
|
|
|
+ $table_width - 1,
|
|
|
+ $table_height - 1,
|
|
|
+ $gdcolors['background']
|
|
|
+ );
|
|
|
+
|
|
|
+ // Дальше мы всё рисуем снизу вверх, потому что зачем то разработчики
|
|
|
+ // GD решили в отрисовку текста передавать как Y координату
|
|
|
+ // нижний край, а не верхний
|
|
|
+
|
|
|
+ // Отрисовка названия
|
|
|
+ $line_y = $title_height - $theme['padding'];
|
|
|
+ foreach (array_reverse($title_lines) as $line) {
|
|
|
+ $area = imagettftext(
|
|
|
+ $im,
|
|
|
+ $theme['title_font_size'],
|
|
|
+ 0,
|
|
|
+ $theme['padding'],
|
|
|
+ $line_y,
|
|
|
+ $gdcolors['title_color'],
|
|
|
+ $theme['font'],
|
|
|
+ $line
|
|
|
+ );
|
|
|
+ $line_y -= abs($area[1] - $area[5]) + $theme['line_spacing'];
|
|
|
+ }
|
|
|
+
|
|
|
+ // Отрисовка тела таблицы
|
|
|
+ $line_y = $body_height + $title_height;//+$theme['padding']-$theme['padding'];
|
|
|
+ for ($y = $height - 1; $y > -1; $y--) {
|
|
|
+ call_user_func(
|
|
|
+ $draw_body_line,
|
|
|
+ $im,
|
|
|
+ $gdcolors,
|
|
|
+ $data[$y],
|
|
|
+ ($y % 2 == 0),
|
|
|
+
|
|
|
+ $theme['padding'], // x1
|
|
|
+ $line_y, // y1
|
|
|
+
|
|
|
+ $body_width - $theme['padding'], // x2
|
|
|
+ $line_y - $row_sizes[$y] // y2
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ #endregion
|
|
|
+
|
|
|
+ return $im;
|
|
|
+ }
|
|
|
+
|
|
|
+ // Вычисляет высоту и ширину текста без padding в пикселях
|
|
|
+ private function getTextSize($lines, $line_spacing, $font, $font_size) {
|
|
|
+ $width = 0;
|
|
|
+ $height = 0;
|
|
|
+
|
|
|
+ foreach ($lines as $line) {
|
|
|
+ $box = imagettfbbox($font_size, 0, $font, $line);
|
|
|
+ $width = max($width, abs($box[2] - $box[0]));
|
|
|
+ $height += abs($box[1] - $box[5]) + $line_spacing;
|
|
|
+ }
|
|
|
+
|
|
|
+ // В последней строке не нужно добавлять расстояния между строками
|
|
|
+ $height -= $line_spacing;
|
|
|
+
|
|
|
+ return [$width, $height];
|
|
|
+ }
|
|
|
+
|
|
|
+ // Отрисовывает обычный задний фон строки таблицы
|
|
|
+ private function bodyLineDefault(
|
|
|
+ $im,
|
|
|
+ $colors,
|
|
|
+ $data,
|
|
|
+ $is_even,
|
|
|
+ $x1,
|
|
|
+ $y1,
|
|
|
+ $x2,
|
|
|
+ $y2)
|
|
|
+ {
|
|
|
+ if ($is_even) {
|
|
|
+ imagefilledrectangle($im, $x1, $y1, $x2, $y2, $colors['body_bg_even']);
|
|
|
+ } else {
|
|
|
+ imagefilledrectangle($im, $x1, $y1, $x2, $y2, $colors['body_bg_odd']);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
#endregion
|
|
|
}
|