Files
image_table/image_table.go
Vladimir V Maksimov b13d113be6 alpha
2025-12-11 23:08:34 +03:00

229 lines
4.8 KiB
Go
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package image_table
import (
_ "embed"
"fmt"
"image"
"image/color"
"image/draw"
"math"
"reflect"
"golang.org/x/image/font"
"golang.org/x/image/font/opentype"
"golang.org/x/image/math/fixed"
)
//go:embed assets/jb.ttf
var jbTTF []byte
var globalFace font.Face
// ---- Цвета ----
var (
headerBg = color.RGBA{0xD8, 0xEC, 0xFF, 255} // голубой фон заголовка
rowBg1 = color.RGBA{0xFF, 0xFF, 0xFF, 255} // белый фон строки 1
rowBg2 = color.RGBA{0xF7, 0xFA, 0xFC, 255} // мягкий фон строки 2
borderCol = color.RGBA{0xB8, 0xC8, 0xD8, 255} // рамка
dividerCol = color.RGBA{0xB8, 0xC8, 0xD8, 255} // линии
textCol = color.RGBA{0x34, 0x34, 0x32, 255} // тёплый тёмно-серый текст
)
// ---- Инициализация шрифта ----
func init() {
f, err := opentype.Parse(jbTTF)
if err != nil {
panic(err)
}
globalFace, err = opentype.NewFace(f, &opentype.FaceOptions{
Size: 10,
DPI: 96,
Hinting: font.HintingFull,
})
if err != nil {
panic(err)
}
}
// ---- Утилиты ----
func toString(v any) string {
return fmt.Sprintf("%v", v)
}
func rowToStrings(v any) []string {
val := reflect.ValueOf(v)
switch val.Kind() {
case reflect.Slice, reflect.Array:
out := make([]string, val.Len())
for i := 0; i < val.Len(); i++ {
out[i] = toString(val.Index(i).Interface())
}
return out
case reflect.Struct:
out := make([]string, val.NumField())
for i := 0; i < val.NumField(); i++ {
out[i] = toString(val.Field(i).Interface())
}
return out
}
return []string{toString(v)}
}
func drawText(img *image.RGBA, x, y int, text string) {
d := &font.Drawer{
Dst: img,
Src: image.NewUniform(textCol),
Face: globalFace,
Dot: fixed.P(x, y),
}
d.DrawString(text)
}
func measureText(s string) int {
d := &font.Drawer{Face: globalFace}
return d.MeasureString(s).Round()
}
func roundedRect(img *image.RGBA, rect image.Rectangle, r int, col color.Color) {
w := rect.Dx()
h := rect.Dy()
for y := 0; y < h; y++ {
for x := 0; x < w; x++ {
if (x < r && y < r && dist(x, y, r, r) > float64(r)) ||
(x >= w-r && y < r && dist(x, y, w-r-1, r) > float64(r)) ||
(x < r && y >= h-r && dist(x, y, r, h-r-1) > float64(r)) ||
(x >= w-r && y >= h-r && dist(x, y, w-r-1, h-r-1) > float64(r)) {
continue
}
img.Set(rect.Min.X+x, rect.Min.Y+y, col)
}
}
}
func dist(x1, y1, x2, y2 int) float64 {
return math.Hypot(float64(x1-x2), float64(y1-y2))
}
// ---- Основная функция ----
func DrawTableWarm(header []string, rows []any) image.Image {
padX := 8
padY := 10
rowHeight := 26
headerHeight := 32
colCount := len(header)
colWidths := make([]int, colCount)
// ---- Автоширина ----
for i, h := range header {
w := measureText(h)
if w > colWidths[i] {
colWidths[i] = w
}
}
for _, r := range rows {
cells := rowToStrings(r)
for i := 0; i < len(cells) && i < colCount; i++ {
w := measureText(cells[i])
if w > colWidths[i] {
colWidths[i] = w
}
}
}
for i := range colWidths {
colWidths[i] += padX * 2
}
imgWidth := 0
for _, w := range colWidths {
imgWidth += w
}
imgHeight := headerHeight + len(rows)*rowHeight
img := image.NewRGBA(image.Rect(0, 0, imgWidth, imgHeight))
// общий фон
draw.Draw(img, img.Bounds(), &image.Uniform{rowBg1}, image.Point{}, draw.Src)
// ---- Заголовок ----
roundedRect(img, image.Rect(0, 0, imgWidth, headerHeight), 8, headerBg)
// Горизонтальная линия под заголовком
for xx := 0; xx < imgWidth; xx++ {
img.Set(xx, headerHeight-1, dividerCol)
}
x := 0
for i, h := range header {
drawText(img, x+padX, headerHeight-padY, h)
x += colWidths[i]
// Вертикальный разделитель
for yy := 0; yy < headerHeight; yy++ {
img.Set(x, yy, dividerCol)
}
}
// ---- Строки ----
y := headerHeight
for rowIndex, row := range rows {
bg := rowBg1
if rowIndex%2 == 1 {
bg = rowBg2
}
// фон строки
for xx := 0; xx < imgWidth; xx++ {
for yy := 0; yy < rowHeight; yy++ {
img.Set(xx, y+yy, bg)
}
}
// горизонтальный разделитель ВВЕРХУ строки
for xx := 0; xx < imgWidth; xx++ {
img.Set(xx, y, dividerCol)
}
cells := rowToStrings(row)
x = 0
for i := 0; i < colCount && i < len(cells); i++ {
drawText(img, x+padX, y+rowHeight-padY, cells[i])
x += colWidths[i]
// вертикальная линия
for yy := 0; yy < rowHeight; yy++ {
img.Set(x, y+yy, dividerCol)
}
}
y += rowHeight
}
// Рамка
for xx := 0; xx < imgWidth; xx++ {
img.Set(xx, 0, borderCol)
img.Set(xx, imgHeight-1, borderCol)
}
for yy := 0; yy < imgHeight; yy++ {
img.Set(0, yy, borderCol)
img.Set(imgWidth-1, yy, borderCol)
}
return img
}