daShangDao_psiServer/service/barcode.go

212 lines
5.7 KiB
Go
Raw 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 service
import (
"bytes"
"encoding/base64"
"fmt"
"image"
"image/color"
"image/draw"
"image/png"
"os"
"path/filepath"
systemRes "psi/models/response"
"strings"
"github.com/boombuler/barcode"
"github.com/boombuler/barcode/code128"
"golang.org/x/image/font"
"golang.org/x/image/font/opentype"
"golang.org/x/image/math/fixed"
)
type BarcodeService struct{}
// Config 条形码配置
type Config struct {
BarcodeHeight int
ModuleWidth int
Padding int
FontPath string
FontSize float64
BgColor color.Color
BarColor color.Color
}
// GenerateBarcode 根据货号生成条形码图片(Base64)
func (s *BarcodeService) GenerateBarcode(content string) (systemRes.BarcodeResponse, error) {
// 配置参数
cfg := &Config{
BarcodeHeight: 120,
ModuleWidth: 2,
Padding: 20,
FontSize: 50,
BgColor: color.White,
BarColor: color.Black,
}
// 若内容包含isbn则使用较小字号且不加粗
if strings.Contains(strings.ToLower(content), "9787") {
cfg.FontSize = 35
}
// 生成条形码图片的Base64
base64Image, err := generateBase64Barcode(content, cfg)
if err != nil {
return systemRes.BarcodeResponse{}, fmt.Errorf("生成条形码失败: %w", err)
}
return systemRes.BarcodeResponse{
ImageBase64: base64Image,
Content: content,
}, nil
}
func generateBase64Barcode(content string, cfg *Config) (string, error) {
// 1. 生成条形码图像
barcodeImg, err := generateBarcodeImage(content, cfg)
if err != nil {
return "", err
}
barcodeBounds := barcodeImg.Bounds()
barcodeWidth := barcodeBounds.Dx()
barcodeHeight := barcodeBounds.Dy()
// 2. 尝试加载字体(自定义字体 -> 系统字体 -> 跳过文字)
face, fontErr := loadFont(cfg.FontSize)
var textHeight, ascent, descent int
var textWidth int
if fontErr == nil && face != nil {
defer face.Close()
// 3. 获取文字度量信息
metrics := face.Metrics()
ascent = metrics.Ascent.Ceil()
descent = metrics.Descent.Ceil()
textHeight = ascent + descent
// 计算文字宽度
drawer := &font.Drawer{Face: face}
advance := drawer.MeasureString(content)
textWidth = advance.Ceil()
}
// 4. 计算最终图片尺寸
gapBetween := 12
textBottomPadding := 12
finalWidth := barcodeWidth + cfg.Padding*2
finalHeight := barcodeHeight + cfg.Padding*2
if face != nil {
maxContentWidth := barcodeWidth
if textWidth > maxContentWidth {
maxContentWidth = textWidth
}
finalWidth = maxContentWidth + cfg.Padding*2
finalHeight = barcodeHeight + gapBetween + textHeight + cfg.Padding*2 + textBottomPadding
}
// 5. 创建最终图片
finalImg := image.NewRGBA(image.Rect(0, 0, finalWidth, finalHeight))
draw.Draw(finalImg, finalImg.Bounds(), &image.Uniform{cfg.BgColor}, image.Point{}, draw.Src)
// 6. 绘制条形码(居中)
barcodeStartX := (finalWidth - barcodeWidth) / 2
barcodeStartY := cfg.Padding
draw.Draw(finalImg,
image.Rect(barcodeStartX, barcodeStartY, barcodeStartX+barcodeWidth, barcodeStartY+barcodeHeight),
barcodeImg,
image.Point{},
draw.Over)
// 7. 绘制货号文字(居中,加粗字体)
if face != nil {
textAreaTop := barcodeStartY + barcodeHeight + gapBetween
textAreaHeight := textHeight + textBottomPadding
baselineY := textAreaTop + (textAreaHeight / 2) + (ascent / 2) - 2
textX := (finalWidth - textWidth) / 2
if textX < 0 {
textX = cfg.Padding
}
textDrawer := &font.Drawer{
Dst: finalImg,
Src: image.NewUniform(cfg.BarColor),
Face: face,
Dot: fixed.Point26_6{
X: fixed.I(textX),
Y: fixed.I(baselineY),
},
}
textDrawer.DrawString(content)
}
// 8. 转换为Base64
buf := new(bytes.Buffer)
if err := png.Encode(buf, finalImg); err != nil {
return "", err
}
return base64.StdEncoding.EncodeToString(buf.Bytes()), nil
}
// generateBarcodeImage 生成条形码图像
func generateBarcodeImage(content string, config *Config) (image.Image, error) {
// 使用Code128编码
code128Barcode, err := code128.Encode(content)
if err != nil {
return nil, fmt.Errorf("编码失败: %w", err)
}
rawBounds := code128Barcode.Bounds()
rawWidth := rawBounds.Dx()
targetWidth := rawWidth * config.ModuleWidth
targetHeight := config.BarcodeHeight
return barcode.Scale(code128Barcode, targetWidth, targetHeight)
}
// loadFont 尝试加载字体,按优先级:项目字体目录 -> Windows系统字体 -> Linux系统字体
// 如果都失败,返回 nil调用方会跳过文字绘制
func loadFont(fontSize float64) (font.Face, error) {
// 按优先级尝试的字体路径列表
fontPaths := []string{
"fonts/youaimoshouheiti-regular.ttf", // 项目自定义字体
"C:\\Windows\\Fonts\\arial.ttf", // Windows Arial
"C:\\Windows\\Fonts\\simhei.ttf", // Windows 黑体(支持中文)
"/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf", // Linux DejaVu
"/usr/share/fonts/truetype/liberation/LiberationSans-Regular.ttf", // Linux Liberation
}
// 如果是相对路径,尝试基于可执行文件目录解析
exePath, exeErr := os.Executable()
for i, p := range fontPaths {
if !filepath.IsAbs(p) && exeErr == nil {
fontPaths[i] = filepath.Join(filepath.Dir(exePath), p)
}
}
for _, fontPath := range fontPaths {
fontBytes, err := os.ReadFile(fontPath)
if err != nil {
continue
}
parsedFont, err := opentype.Parse(fontBytes)
if err != nil {
continue
}
face, err := opentype.NewFace(parsedFont, &opentype.FaceOptions{
Size: fontSize,
DPI: 96,
Hinting: font.HintingFull,
})
if err != nil {
continue
}
return face, nil
}
return nil, nil // 所有字体都加载失败,返回 nil不绘制文字
}