Sfoglia il codice sorgente

Добавлен генератор таблиц

Вадим Королёв 1 anno fa
parent
commit
1c9bfd0291
1 ha cambiato i file con 282 aggiunte e 0 eliminazioni
  1. 282 0
      src/Common/Commands.php

+ 282 - 0
src/Common/Commands.php

@@ -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
 }