package service import ( "bytes" "encoding/base64" "fmt" "image" "image/color" "image/draw" "image/png" "os" 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) { // 配置参数 config := &Config{ BarcodeHeight: 120, ModuleWidth: 2, Padding: 20, FontPath: "fonts/youaimoshouheiti-regular.ttf", FontSize: 50, BgColor: color.White, BarColor: color.Black, } // 若内容包含isbn,则使用较小字号且不加粗 if strings.Contains(strings.ToLower(content), "9787") { config.FontSize = 35 } // 生成条形码图片的Base64 base64Image, err := generateBase64Barcode(content, config) if err != nil { return systemRes.BarcodeResponse{}, fmt.Errorf("生成条形码失败: %w", err) } return systemRes.BarcodeResponse{ ImageBase64: base64Image, Content: content, }, nil } func generateBase64Barcode(content string, config *Config) (string, error) { // 1. 生成条形码图像 barcodeImg, err := generateBarcodeImage(content, config) if err != nil { return "", err } barcodeBounds := barcodeImg.Bounds() barcodeWidth := barcodeBounds.Dx() barcodeHeight := barcodeBounds.Dy() // 2. 加载字体 face, err := loadFont(config) if err != nil { return "", err } 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 maxContentWidth := barcodeWidth if textWidth > maxContentWidth { maxContentWidth = textWidth } finalWidth := maxContentWidth + config.Padding*2 finalHeight := barcodeHeight + gapBetween + textHeight + config.Padding*2 + textBottomPadding // 5. 创建最终图片 finalImg := image.NewRGBA(image.Rect(0, 0, finalWidth, finalHeight)) draw.Draw(finalImg, finalImg.Bounds(), &image.Uniform{config.BgColor}, image.Point{}, draw.Src) // 6. 绘制条形码(居中) barcodeStartX := (finalWidth - barcodeWidth) / 2 barcodeStartY := config.Padding draw.Draw(finalImg, image.Rect(barcodeStartX, barcodeStartY, barcodeStartX+barcodeWidth, barcodeStartY+barcodeHeight), barcodeImg, image.Point{}, draw.Over) // 7. 绘制货号文字(居中,加粗字体) textAreaTop := barcodeStartY + barcodeHeight + gapBetween textAreaHeight := textHeight + textBottomPadding baselineY := textAreaTop + (textAreaHeight / 2) + (ascent / 2) - 2 textX := (finalWidth - textWidth) / 2 if textX < 0 { textX = config.Padding } textDrawer := &font.Drawer{ Dst: finalImg, Src: image.NewUniform(config.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 加载加粗TrueType字体 func loadFont(config *Config) (font.Face, error) { fontBytes, err := os.ReadFile(config.FontPath) if err != nil { return nil, fmt.Errorf("读取字体文件失败: %w", err) } parsedFont, err := opentype.Parse(fontBytes) if err != nil { return nil, fmt.Errorf("解析字体失败: %w", err) } face, err := opentype.NewFace(parsedFont, &opentype.FaceOptions{ Size: config.FontSize, DPI: 96, Hinting: font.HintingFull, }) if err != nil { return nil, fmt.Errorf("创建字体失败: %w", err) } return face, nil }