package table2base64
import (
"bytes"
"encoding/base64"
"fmt"
"image"
"image/color"
"image/draw"
"image/png"
"io"
"os"
"strconv"
"strings"
"time"
"github.com/golang/freetype/truetype"
"golang.org/x/image/font"
"golang.org/x/image/math/fixed"
"icode.xxx.com/xx/xx/env"
"icode.xxx.com/xx/xx-xx-xx/xxx-xx/library/utils"
"icode.xxx.com/xx/xx-xx-xx/xxx-xx/library/utils/shence"
)
const (
DefaultClosWidth int = 103 //默认列宽
DefaultRowsHeight int = 36 //默认行高
DefaultTextRowsHeight int = 20 //默认文字描述行高
)
var (
DefaultTTFFile = ""
DefaultTTFFileBOLD = ""
)
// 初始化函数
func init() {
DefaultTTFFile = env.ConfDir() + "/fonts" + "/AlibabaPuHuiTi-3-55-Regular/AlibabaPuHuiTi-3-55-Regular.ttf"
DefaultTTFFileBOLD = env.ConfDir() + "/fonts" + "/AlibabaPuHuiTi-3-85-Bold/AlibabaPuHuiTi-3-85-Bold.ttf"
if !utils.FileExists(DefaultTTFFileBOLD) {
utils.DownloadFile("https://puhuiti.oss-cn-hangzhou.aliyuncs.com/AlibabaPuHuiTi-3/AlibabaPuHuiTi-3-85-Bold.zip", env.ConfDir()+"/fonts")
}
if !utils.FileExists(DefaultTTFFile) {
utils.DownloadFile("https://puhuiti.oss-cn-hangzhou.aliyuncs.com/AlibabaPuHuiTi-3/AlibabaPuHuiTi-3-55-Regular.zip", env.ConfDir()+"/fonts")
}
}
type DrawString interface {
string() string
}
// TabString 自定义字符串类型
type TabString string
// string 方法返回字符串 s 的值
func (s TabString) string() string {
return string(s)
}
// TabNumber 自定义数值类型
type TabNumber float64
// string 方法返回 TabNumber 类型的字符串表示形式
func (n TabNumber) string() string {
return strconv.FormatFloat(float64(n), 'f', -1, 64)
}
type TableContent [][]DrawString
type TableImg struct {
TTFFile string //字体文件路径
TTFFILEBold string //字体文件路径(粗体)
Header []string //表格头
Content [][]interface{} //外部初始化表格内容
TextDesc []string //文字描述
ClosWidth int //列宽
RowsHeight int //行高
TextRowsHeight int //文字描述行高
ShenCeData []*shence.ReportData //神策数据
selfContent TableContent //内部使用表格内容
imgWidth int //图片宽
tableHeight int //表格高
imgHeight int //图片高
cols int //列数
rows int //行数
base64Str string //base64数据
img *image.RGBA //图像资源对象
shenCeBase64Str string //神策数据base64
shenCeImg *image.RGBA //神策数据图像资源对象
}
// NewTableImg 返回一个TableImg对象
func NewTableImg(tab *TableImg) *TableImg {
var c TableContent
for _, con := range tab.Content {
var cSlice []DrawString
for _, cc := range con {
value := convertToDrawValue(cc)
cSlice = append(cSlice, value)
}
c = append(c, cSlice)
}
if tab.TTFFile == "" {
tab.TTFFile = DefaultTTFFile
}
if tab.TTFFILEBold == "" {
tab.TTFFILEBold = DefaultTTFFileBOLD
}
return &TableImg{
TTFFile: tab.TTFFile,
TTFFILEBold: tab.TTFFILEBold,
Header: tab.Header,
Content: tab.Content,
ClosWidth: tab.ClosWidth,
RowsHeight: tab.RowsHeight,
TextDesc: tab.TextDesc,
TextRowsHeight: tab.TextRowsHeight,
ShenCeData: tab.ShenCeData,
selfContent: c,
imgWidth: tab.ClosWidth * len(tab.Header),
tableHeight: tab.RowsHeight * (len(tab.Content) + 1),
imgHeight: tab.RowsHeight*(len(tab.Content)+1) + tab.TextRowsHeight*(len(tab.TextDesc)+1),
cols: len(tab.Header),
rows: len(tab.Content) + 1,
base64Str: "",
img: nil,
shenCeBase64Str: "",
shenCeImg: nil,
}
}
// convertToDrawValue 将一个输入转换为DrawString类型的值
func convertToDrawValue(cc interface{}) DrawString {
var value DrawString
switch v := cc.(type) {
case int:
value = TabNumber(v)
case int8:
value = TabNumber(v)
case int16:
value = TabNumber(v)
case int32:
value = TabNumber(v)
case int64:
value = TabNumber(v)
case float32:
value = TabNumber(v)
case float64:
value = TabNumber(v)
case string:
value = TabString(v)
default:
value = TabString("")
}
return value
}
// GenerateShenCeImageGetBase64 生成神策数据图片并返回base64
func (tab *TableImg) GenerateShenCeImageGetBase64() (string, error) {
marginx, marginy := 15, 20
height := tab.RowsHeight*7 + marginy
cellWidth, cellHeight := 320, tab.RowsHeight*4+marginy
tab.shenCeImg = image.NewRGBA(image.Rect(0, 0, tab.imgWidth, height))
// 以白色作为背景绘制整个图片
draw.Draw(tab.shenCeImg, tab.shenCeImg.Bounds(), &image.Uniform{color.Gray16{0xf5f8}}, image.Point{}, draw.Src)
f, err := os.OpenFile(tab.TTFFILEBold, os.O_RDONLY, 0644) // 0644表示文件权限
if err != nil {
return "", err
}
defer f.Close()
byteFile, _ := io.ReadAll(f)
for k, v := range tab.ShenCeData {
x := marginx + k*(cellWidth+marginx)
y := marginy * 2
x1 := x + cellWidth
y1 := y + cellHeight
// 创建格子的矩形区域并进行绘制
fillArea := image.Rect(x, y, x1, y1)
draw.Draw(tab.shenCeImg, fillArea, &image.Uniform{color.White}, image.Point{0, 0}, draw.Src)
if _, err := tab.drawText(v.Name, x+10, y+20, byteFile, tab.shenCeImg, 18, color.RGBA{A: 255}); err != nil {
return "", err
}
col := color.RGBA{0, 0, 0, 255}
if _, err := tab.drawText(fmt.Sprintf("%.0f", v.BaseNumber), x+10, y+20+tab.RowsHeight, byteFile, tab.shenCeImg, 18, col); err != nil {
return "", err
}
col = color.RGBA{255, 0, 0, 255}
hb := fmt.Sprintf("环比%.2f%%", v.MonthOnMonth)
if v.MonthOnMonth > 0 {
col = color.RGBA{25, 95, 31, 255}
hb = fmt.Sprintf("环比+%.2f%%", v.MonthOnMonth)
}
if _, err := tab.drawText(hb, x+10, y+20+tab.RowsHeight*2, byteFile, tab.shenCeImg, 18, col); err != nil {
return "", err
}
col = color.RGBA{255, 0, 0, 255}
tb := fmt.Sprintf("同比%.2f%%", v.YearOnYear)
if v.YearOnYear > 0 {
col = color.RGBA{25, 95, 31, 255}
tb = fmt.Sprintf("同比+%.2f%%", v.YearOnYear)
}
if _, err := tab.drawText(tb, x+10, y+20+tab.RowsHeight*3, byteFile, tab.shenCeImg, 18, col); err != nil {
return "", err
}
}
if _, err := tab.drawText("产品大盘增长数据:", marginx, marginy, byteFile, tab.shenCeImg, 18, color.RGBA{A: 255}); err != nil {
return "", err
}
if _, err := tab.drawText("投放大盘数据:", marginx, marginy*3+cellHeight, byteFile, tab.shenCeImg, 18, color.RGBA{A: 255}); err != nil {
return "", err
}
// 创建一个内存缓冲区
var buf bytes.Buffer
// 将图片编码为 PNG 格式并写入缓冲区
err = png.Encode(&buf, tab.shenCeImg)
if err != nil {
return "", err
}
// 将 PNG 图片转换为 base64 编码的字符串
tab.shenCeBase64Str = base64.StdEncoding.EncodeToString(buf.Bytes())
return tab.shenCeBase64Str, nil
}
// GenerateImageGetBase64 生成表格图片并返回base64
func (tab *TableImg) GenerateImageGetBase64() (string, error) {
cols := len(tab.Header)
rows := len(tab.Content) + 1
// 创建一个新的 RGBA 图片
tab.img = image.NewRGBA(image.Rect(0, 0, tab.imgWidth, tab.imgHeight))
// 以白色作为背景绘制整个图片
draw.Draw(tab.img, tab.img.Bounds(), &image.Uniform{color.White}, image.Point{}, draw.Src)
downMovePx := tab.imgHeight - tab.tableHeight
//填充表头背景色
fillArea := image.Rect(0, 0+downMovePx, tab.imgWidth, tab.RowsHeight+downMovePx)
draw.Draw(tab.img, fillArea, &image.Uniform{color.RGBA{162, 206, 232, 255}}, image.Point{0, 0}, draw.Src)
yesterDay := time.Now().AddDate(0, 0, -1).Format("20060102")
for i, v := range tab.selfContent {
i += 1
if strings.Contains(v[2].string(), "汇总") || strings.Contains(v[3].string(), "汇总") {
fillArea := image.Rect(0, tab.RowsHeight*i+downMovePx, tab.imgWidth, tab.RowsHeight*(i+1)+downMovePx)
draw.Draw(tab.img, fillArea, &image.Uniform{color.RGBA{204, 116, 79, 255}}, image.Point{0, 0}, draw.Src)
continue
}
if v[0].string() == "汇总" {
fillArea := image.Rect(0, tab.RowsHeight*(i)+downMovePx, tab.imgWidth, tab.RowsHeight*(i+1)+downMovePx)
draw.Draw(tab.img, fillArea, &image.Uniform{color.RGBA{192, 236, 252, 255}}, image.Point{0, 0}, draw.Src)
continue
}
//如果是昨天,则填充特殊背景色
if v[0].string() == yesterDay {
fillArea := image.Rect(0, tab.RowsHeight*i+downMovePx, tab.imgWidth, tab.RowsHeight*(i+1)+downMovePx)
draw.Draw(tab.img, fillArea, &image.Uniform{color.RGBA{233, 204, 206, 255}}, image.Point{0, 0}, draw.Src)
}
}
//绘制表格线
for i := 0; i <= cols; i++ {
x := i * (tab.imgWidth / cols)
if x >= tab.imgWidth {
x = tab.imgWidth - 1
}
tab.drawLine(x, 0+downMovePx, x, tab.tableHeight+downMovePx, color.RGBA{123, 123, 123, 255})
}
for i := 0; i <= rows; i++ {
y := i*(tab.tableHeight/rows) + downMovePx
if y >= tab.imgHeight {
y = tab.imgHeight - 1
}
tab.drawLine(0, y, tab.imgWidth, y, color.RGBA{123, 123, 123, 255})
}
f, err := os.OpenFile(tab.TTFFile, os.O_RDONLY, 0644) // 0644表示文件权限
if err != nil {
return "", err
}
defer f.Close()
byteFile, _ := io.ReadAll(f)
fBold, err := os.OpenFile(tab.TTFFILEBold, os.O_RDONLY, 0644) // 0644表示文件权限
if err != nil {
return "", err
}
defer fBold.Close()
byteFileBold, _ := io.ReadAll(fBold)
// 在图片上绘制内容
// 绘制 header 中的内容
for i, h := range tab.Header {
if _, err := tab.drawText(h, (tab.imgWidth/cols)*i+10, 20+downMovePx, byteFileBold, tab.img, 14, color.RGBA{A: 255}); err != nil {
return "", err
}
}
textGreen := color.RGBA{0, 128, 0, 255} // 绿色
textRed := color.RGBA{255, 0, 0, 255} // 红色
// 绘制 content 中的内容
for i, c := range tab.selfContent {
bFile := byteFile
if c[0].string() == yesterDay || c[0].string() == "汇总" || strings.Contains(c[2].string(), "汇总") || strings.Contains(c[3].string(), "汇总") {
bFile = byteFileBold
}
y := (tab.tableHeight/rows)*(i+1) + 20 + downMovePx
n := 0
for j, item := range c {
x := (tab.imgWidth/cols)*j + 10
textColor := color.RGBA{A: 255}
if strings.HasPrefix(item.string(), "+") {
n++
textColor = textGreen // 绿色
if n%2 == 0 { //CPI 的颜色和其他的是相反的
textColor = textRed // 红色
}
} else if item.string() != "-" && strings.HasPrefix(item.string(), "-") {
n++
textColor = textRed // 红色
if n%2 == 0 { //CPI 的颜色和其他的是相反的
textColor = textGreen // 绿色
}
}
if _, err := tab.drawText(item.string(), x, y, bFile, tab.img, 14, textColor); err != nil {
return "", err
}
}
}
// 绘制 文字描述 中的内容
for i, c := range tab.TextDesc {
y := 20
y += i * tab.TextRowsHeight
x := 10
j := 0
for _, word := range strings.Split(c, " ") {
col := color.RGBA{0, 0, 0, 255}
if len(word) == 0 {
continue
}
if word[0] == '+' {
j++
if j%2 == 1 { //如果是激活,+用绿,如果是CPI,+用红
col = textGreen // 绿色
} else {
col = textRed // 红色
}
} else if word[0] == '-' {
j++
if j%2 == 1 { //如果是激活,-用红,如果是CPI,-用绿
col = textRed // 红色
} else {
col = textGreen // 绿色
}
}
width, err := tab.drawText(word, x, y, byteFileBold, tab.img, 14, col)
if err != nil {
return "", err
}
x += width
}
}
// 创建一个内存缓冲区
var buf bytes.Buffer
// 将图片编码为 PNG 格式并写入缓冲区
err = png.Encode(&buf, tab.img)
if err != nil {
return "", err
}
// 将 PNG 图片转换为 base64 编码的字符串
tab.base64Str = base64.StdEncoding.EncodeToString(buf.Bytes())
return tab.base64Str, nil
}
// drawText 方法绘制文本到图像上
func (tab *TableImg) drawText(text string, x, y int, fileByte []byte, img *image.RGBA, size float64, col color.RGBA) (width int, err error) {
point := image.Point{X: x, Y: y}
ff, _ := truetype.Parse(fileByte)
face := truetype.NewFace(ff, &truetype.Options{
Size: size,
})
drawer := &font.Drawer{
Dst: img,
Src: image.NewUniform(col),
Face: face,
Dot: fixed.P(point.X, point.Y),
}
drawer.DrawString(text)
options := &font.Drawer{
Face: face,
}
options.Dot = fixed.P(0, 0)
return fixed.Int26_6(options.MeasureString(text)).Ceil(), nil
}
// drawLine 函数绘制一条直线。
// x0,y0,x1和y1表示起点和终点的坐标值,c为直线颜色。
func (tab *TableImg) drawLine(x0, y0, x1, y1 int, c color.Color) {
dx := abs(x1 - x0)
dy := abs(y1 - y0)
sx, sy := 1, 1
if x0 > x1 {
sx = -1
}
if y0 > y1 {
sy = -1
}
err := dx - dy
for {
tab.img.Set(x0, y0, c)
if x0 == x1 && y0 == y1 {
break
}
e2 := 2 * err
if e2 > -dy {
err -= dy
x0 += sx
}
if e2 < dx {
err += dx
y0 += sy
}
}
}
// abs 函数返回输入整数的绝对值。
func abs(x int) int {
if x < 0 {
return -x
}
return x
}
06-18
7876
