Initial commit: Modular personal toolbox with high-fidelity Chinese stroke order tool and CI/CD
Build and Push Docker Image / build (push) Successful in 2m42s
Build and Push Docker Image / build (push) Successful in 2m42s
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
package base
|
||||
|
||||
import (
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// Tool 定义了工具箱中每个子工具必须实现的接口
|
||||
type Tool interface {
|
||||
ID() string // 工具的唯一标识,用于路由前缀,如 "zitie"
|
||||
Name() string // 工具的显示名称
|
||||
Description() string // 工具的描述
|
||||
Init() error // 初始化逻辑,如加载 embed 的数据
|
||||
RegisterRoutes(r *gin.RouterGroup) // 注册该工具的 API 路由
|
||||
}
|
||||
|
||||
// Registry 存储所有已注册的工具
|
||||
var Registry = make(map[string]Tool)
|
||||
|
||||
// Register 用于工具在 init() 函数中注册自己
|
||||
func Register(t Tool) {
|
||||
Registry[t.ID()] = t
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,253 @@
|
||||
package logic
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"math"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/signintech/gopdf"
|
||||
"golang.org/x/image/font/sfnt"
|
||||
)
|
||||
|
||||
var (
|
||||
embeddedFont []byte
|
||||
fontMap = make(map[string]string)
|
||||
sfntMap = make(map[string]*sfnt.Font)
|
||||
)
|
||||
|
||||
func SetFontData(data []byte) { embeddedFont = data }
|
||||
func RegisterFontPath(id, path string) { fontMap[id] = path }
|
||||
func RegisterFontSFNT(id string, f *sfnt.Font) { sfntMap[id] = f }
|
||||
|
||||
// HasGlyph 检查字体是否包含某个字符
|
||||
func HasGlyph(fontId string, char rune) bool {
|
||||
f, ok := sfntMap[fontId]
|
||||
if !ok { return false }
|
||||
var buffer sfnt.Buffer
|
||||
idx, err := f.GlyphIndex(&buffer, char)
|
||||
return err == nil && idx != 0
|
||||
}
|
||||
|
||||
// GeneratePDFExtended 支持动态字体切换和缺字降级
|
||||
func GeneratePDFExtended(chars []HanziData, mode, paperSize, fontId string) ([]byte, error) {
|
||||
pdf := &gopdf.GoPdf{}
|
||||
rect := gopdf.Rect{W: 595.28, H: 841.89}
|
||||
if paperSize == "Letter" { rect = gopdf.Rect{W: 612, H: 792} }
|
||||
pdf.Start(gopdf.Config{PageSize: rect})
|
||||
|
||||
if len(embeddedFont) > 0 { _ = pdf.AddTTFFontData("font", embeddedFont) }
|
||||
|
||||
// 注册所有可用字体
|
||||
for id, path := range fontMap {
|
||||
_ = pdf.AddTTFFont(id, path)
|
||||
}
|
||||
|
||||
switch mode {
|
||||
case "step":
|
||||
addStepPages(pdf, chars, true, rect.W, rect.H)
|
||||
case "manuscript":
|
||||
addManuscriptPages(pdf, chars, rect.W, rect.H, fontId)
|
||||
default:
|
||||
addTeachingPages(pdf, chars, true, rect.W, rect.H)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
_, err := pdf.WriteTo(&buf)
|
||||
return buf.Bytes(), err
|
||||
}
|
||||
|
||||
func addTeachingPages(pdf *gopdf.GoPdf, chars []HanziData, flipY bool, pW, pH float64) {
|
||||
cols := 2; gap, margin := 17.0, 40.0
|
||||
size := math.Min((pW-2*margin-gap)/2, (pH-2*margin-2*gap)/3)
|
||||
totalW, totalH := 2*size+gap, 3*size+2*gap
|
||||
marginX, marginY := (pW-totalW)/2, (pH-totalH)/2
|
||||
for i := 0; i < len(chars); i += 6 {
|
||||
pdf.AddPage()
|
||||
end := i + 6; if end > len(chars) { end = len(chars) }
|
||||
for idx, data := range chars[i:end] {
|
||||
drawCharacter(pdf, data, marginX+float64(idx%cols)*(size+gap), marginY+float64(idx/cols)*(size+gap), size, flipY, len(data.Strokes), true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func addStepPages(pdf *gopdf.GoPdf, chars []HanziData, flipY bool, pW, pH float64) {
|
||||
cols, margin := 9, 30.0; size := (pW - 2*margin) / float64(cols)
|
||||
pdf.AddPage(); currY := margin
|
||||
for _, data := range chars {
|
||||
numS := len(data.Strokes); rowsN := 1; if numS > 8 { rowsN = 1 + (numS - 8 + 7) / 8 }
|
||||
if currY + float64(rowsN)*size > pH - margin { pdf.AddPage(); currY = margin }
|
||||
totalG := rowsN * cols; strokeC := 0
|
||||
for gIdx := 0; gIdx < totalG; gIdx++ {
|
||||
r, c := gIdx/cols, gIdx%cols; x, y := margin+float64(c)*size, currY+float64(r)*size
|
||||
if r == 0 && c == 0 { drawCharacter(pdf, data, x, y, size, flipY, len(data.Strokes), false); strokeC = 1
|
||||
} else if r > 0 && c == 0 { drawMiZiGe(pdf, x, y, size)
|
||||
} else if strokeC <= numS { drawStepBox(pdf, data, x, y, size, flipY, strokeC); strokeC++
|
||||
} else { drawMiZiGe(pdf, x, y, size) }
|
||||
}
|
||||
currY += float64(rowsN) * size
|
||||
}
|
||||
}
|
||||
|
||||
func addManuscriptPages(pdf *gopdf.GoPdf, chars []HanziData, pW, pH float64, targetFont string) {
|
||||
margin := 40.0; cols, rows := 10, 15
|
||||
colW, rowH := (pW-2*margin)/float64(cols), (pH-2*margin)/float64(rows)
|
||||
fontSize := colW * 0.75
|
||||
|
||||
pdf.AddPage(); drawManuscriptGrid(pdf, pW, pH, margin, cols, colW)
|
||||
cIdx, rIdx := 0, 0
|
||||
|
||||
for _, data := range chars {
|
||||
if data.Character == "\n" {
|
||||
cIdx++; rIdx = 0
|
||||
if cIdx >= cols { pdf.AddPage(); cIdx = 0; drawManuscriptGrid(pdf, pW, pH, margin, cols, colW) }
|
||||
continue
|
||||
}
|
||||
if rIdx >= rows {
|
||||
cIdx++; rIdx = 0
|
||||
if cIdx >= cols { pdf.AddPage(); cIdx = 0; drawManuscriptGrid(pdf, pW, pH, margin, cols, colW) }
|
||||
}
|
||||
|
||||
x, y := pW - margin - float64(cIdx+1)*colW, margin + float64(rIdx)*rowH
|
||||
charRune := []rune(data.Character)[0]
|
||||
|
||||
// 智能字体选择
|
||||
renderFont := targetFont
|
||||
isFallback := false
|
||||
|
||||
if !HasGlyph(targetFont, charRune) {
|
||||
// 尝试降级到字库最全的宋体或楷体
|
||||
if HasGlyph("songti", charRune) {
|
||||
renderFont = "songti"; isFallback = true
|
||||
} else if HasGlyph("kaiti", charRune) {
|
||||
renderFont = "kaiti"; isFallback = true
|
||||
} else {
|
||||
// 全部缺失,保留空白
|
||||
rIdx++; continue
|
||||
}
|
||||
}
|
||||
|
||||
pdf.SetFillColor(200, 200, 200)
|
||||
if isFallback {
|
||||
// 降级显示:小字 (1/2 大小),右上角对齐
|
||||
smallSize := fontSize * 0.5
|
||||
_ = pdf.SetFont(renderFont, "", smallSize)
|
||||
// 计算右上角位置:X靠右,Y靠上
|
||||
pdf.SetXY(x + colW - smallSize - 2, y + 2)
|
||||
_ = pdf.Cell(nil, data.Character)
|
||||
} else {
|
||||
// 正常显示
|
||||
_ = pdf.SetFont(renderFont, "", fontSize)
|
||||
pdf.SetXY(x + (colW-fontSize*0.9)/2, y + (rowH-fontSize)/2)
|
||||
_ = pdf.Cell(nil, data.Character)
|
||||
}
|
||||
rIdx++
|
||||
}
|
||||
}
|
||||
|
||||
func drawManuscriptGrid(pdf *gopdf.GoPdf, pW, pH, margin float64, cols int, colW float64) {
|
||||
pdf.SetStrokeColor(200, 0, 0); pdf.SetLineWidth(0.6)
|
||||
for c := 0; c <= cols; c++ { x := pW - margin - float64(c)*colW; pdf.Line(x, margin, x, pH-margin) }
|
||||
pdf.Line(margin, margin, pW-margin, margin); pdf.Line(margin, pH-margin, pW-margin, pH-margin)
|
||||
}
|
||||
|
||||
func drawCharacter(pdf *gopdf.GoPdf, data HanziData, x, y, size float64, flipY bool, strokeLimit int, showAnnotations bool) {
|
||||
if showAnnotations { drawGrid(pdf, x, y, size) } else { drawMiZiGe(pdf, x, y, size) }
|
||||
p, drawS := size*0.12, size-(size*0.12*2); scale := drawS/1024.0
|
||||
if strokeLimit == len(data.Strokes) {
|
||||
if showAnnotations { pdf.SetFillColor(240, 240, 240) } else { pdf.SetFillColor(0, 0, 0) }
|
||||
} else { pdf.SetFillColor(180, 180, 180) }
|
||||
for sIdx := 0; sIdx < len(data.Strokes); sIdx++ {
|
||||
if sIdx >= strokeLimit && strokeLimit != len(data.Strokes) { break }
|
||||
pts := parseSVGPath(data.Strokes[sIdx], scale, x+p, y+p, flipY)
|
||||
if len(pts) > 2 { pdf.Polygon(pts, "F") }
|
||||
}
|
||||
if showAnnotations && strokeLimit == len(data.Strokes) {
|
||||
fS := size * 0.035
|
||||
_ = pdf.SetFont("font", "", fS)
|
||||
for idx, median := range data.Medians {
|
||||
mP := transformPoints(median, scale, x+p, y+p, flipY); if len(mP) < 2 { continue }
|
||||
pdf.SetStrokeColor(255, 0, 0); pdf.SetLineWidth(0.6)
|
||||
for j := 0; j < len(mP)-1; j++ { pdf.Line(mP[j].X, mP[j].Y, mP[j+1].X, mP[j+1].Y) }
|
||||
pdf.SetFillColor(255, 0, 0); drawArrow(pdf, mP[len(mP)-1], mP[len(mP)-2], size*0.035)
|
||||
r := size*0.025; dx, dy := mP[1].X-mP[0].X, mP[1].Y-mP[0].Y; dist := math.Sqrt(dx*dx+dy*dy); ux, uy := -1.0, -1.0
|
||||
if dist > 0.001 { ux, uy = dx/dist, dy/dist }; cX, cY := mP[0].X-ux*r, mP[0].Y-uy*r
|
||||
pdf.SetStrokeColor(255, 0, 0); pdf.SetLineWidth(0.5); pdf.Oval(cX-r, cY-r, cX+r, cY+r)
|
||||
numS := strconv.Itoa(idx+1); tw := fS*0.6*float64(len(numS))/2; if len(numS) > 1 { tw = fS*0.5 }
|
||||
pdf.SetFillColor(255, 0, 0); pdf.SetXY(cX-tw/2, cY-fS/2); _ = pdf.Cell(nil, numS)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func drawStepBox(pdf *gopdf.GoPdf, data HanziData, x, y, size float64, flipY bool, limit int) {
|
||||
drawMiZiGe(pdf, x, y, size); p, scale := size*0.12, (size-(size*0.12*2))/1024.0
|
||||
pdf.SetFillColor(180, 180, 180); for i := 0; i < limit; i++ {
|
||||
pts := parseSVGPath(data.Strokes[i], scale, x+p, y+p, flipY); if len(pts) > 2 { pdf.Polygon(pts, "F") }
|
||||
}
|
||||
}
|
||||
|
||||
func drawGrid(pdf *gopdf.GoPdf, x, y, size float64) {
|
||||
pdf.SetStrokeColor(220, 220, 220); pdf.SetLineWidth(0.4); pdf.RectFromUpperLeft(x, y, size, size)
|
||||
mid := size/2; drawDashedLine(pdf, x, y+mid, x+size, y+mid); drawDashedLine(pdf, x+mid, y, x+mid, y+size)
|
||||
}
|
||||
|
||||
func drawMiZiGe(pdf *gopdf.GoPdf, x, y, size float64) {
|
||||
pdf.SetStrokeColor(220, 220, 220); pdf.SetLineWidth(0.4); pdf.RectFromUpperLeft(x, y, size, size)
|
||||
mid := size/2; drawDashedLine(pdf, x, y+mid, x+size, y+mid); drawDashedLine(pdf, x+mid, y, x+mid, y+size); drawDashedLine(pdf, x, y, x+size, y+size); drawDashedLine(pdf, x, y+size, x+size, y)
|
||||
}
|
||||
|
||||
func drawDashedLine(pdf *gopdf.GoPdf, x1, y1, x2, y2 float64) {
|
||||
dash := 2.0; dx, dy := x2-x1, y2-y1; dist := math.Sqrt(dx*dx + dy*dy); if dist < 0.001 { return }
|
||||
ux, uy := dx/dist, dy/dist; for i := 0.0; i < dist; i += dash * 2 { end := i + dash; if end > dist { end = dist }; pdf.Line(x1+ux*i, y1+uy*i, x1+ux*end, y1+uy*end) }
|
||||
}
|
||||
|
||||
func drawArrow(pdf *gopdf.GoPdf, target, prev gopdf.Point, length float64) {
|
||||
angle := math.Atan2(target.Y-prev.Y, target.X-prev.X); arrowA := math.Pi/6
|
||||
p1X := target.X+length*math.Cos(angle+math.Pi+arrowA); p1Y := target.Y+length*math.Sin(angle+math.Pi+arrowA)
|
||||
p2X := target.X+length*math.Cos(angle+math.Pi-arrowA); p2Y := target.Y+length*math.Sin(angle+math.Pi-arrowA)
|
||||
pdf.Polygon([]gopdf.Point{target, {X: p1X, Y: p1Y}, {X: p2X, Y: p2Y}}, "F")
|
||||
}
|
||||
|
||||
func transformPoints(pts []Point, scale, ox, oy float64, flipY bool) []gopdf.Point {
|
||||
res := make([]gopdf.Point, len(pts)); for i, p := range pts { y := p[1]; if flipY { y = 1024-y }; res[i] = gopdf.Point{X: ox+p[0]*scale, Y: oy+y*scale} }
|
||||
return res
|
||||
}
|
||||
|
||||
var reSVG = regexp.MustCompile(`([MLQCZ])|(-?\d+\.?\d*)`)
|
||||
|
||||
func parseSVGPath(path string, scale, ox, oy float64, flipY bool) []gopdf.Point {
|
||||
var pts []gopdf.Point; matches := reSVG.FindAllStringSubmatch(path, -1); var lastX, lastY float64
|
||||
for idx := 0; idx < len(matches); {
|
||||
item := matches[idx][0]
|
||||
if item == "M" || item == "L" {
|
||||
if idx+2 < len(matches) {
|
||||
x, _ := strconv.ParseFloat(matches[idx+1][0], 64); y, _ := strconv.ParseFloat(matches[idx+2][0], 64); lastX, lastY = x, y
|
||||
if flipY { y = 1024 - y }; pts = append(pts, gopdf.Point{X: ox + x*scale, Y: oy + y*scale}); idx += 3
|
||||
} else { idx++ }
|
||||
} else if item == "C" {
|
||||
if idx+6 < len(matches) {
|
||||
x1, _ := strconv.ParseFloat(matches[idx+1][0], 64); y1, _ := strconv.ParseFloat(matches[idx+2][0], 64)
|
||||
x2, _ := strconv.ParseFloat(matches[idx+3][0], 64); y2, _ := strconv.ParseFloat(matches[idx+4][0], 64)
|
||||
x, _ := strconv.ParseFloat(matches[idx+5][0], 64); y, _ := strconv.ParseFloat(matches[idx+6][0], 64)
|
||||
for t := 0.2; t <= 1.0; t += 0.2 {
|
||||
tx := math.Pow(1-t, 3)*lastX + 3*math.Pow(1-t, 2)*t*x1 + 3*(1-t)*math.Pow(t, 2)*x2 + math.Pow(t, 3)*x
|
||||
ty := math.Pow(1-t, 3)*lastY + 3*math.Pow(1-t, 2)*t*y1 + 3*(1-t)*math.Pow(t, 2)*y2 + math.Pow(t, 3)*y
|
||||
ty_f := ty; if flipY { ty_f = 1024 - ty }; pts = append(pts, gopdf.Point{X: ox + tx*scale, Y: oy + ty_f*scale})
|
||||
}
|
||||
lastX, lastY = x, y; idx += 7
|
||||
} else { idx++ }
|
||||
} else if item == "Q" {
|
||||
if idx+4 < len(matches) {
|
||||
x1, _ := strconv.ParseFloat(matches[idx+1][0], 64); y1, _ := strconv.ParseFloat(matches[idx+2][0], 64)
|
||||
x, _ := strconv.ParseFloat(matches[idx+3][0], 64); y, _ := strconv.ParseFloat(matches[idx+4][0], 64)
|
||||
for t := 0.2; t <= 1.0; t += 0.2 {
|
||||
tx := math.Pow(1-t, 2)*lastX + 2*(1-t)*t*x1 + math.Pow(t, 2)*x
|
||||
ty := math.Pow(1-t, 2)*lastY + 2*(1-t)*t*y1 + math.Pow(t, 2)*y
|
||||
ty_f := ty; if flipY { ty_f = 1024 - ty }; pts = append(pts, gopdf.Point{X: ox + tx*scale, Y: oy + ty_f*scale})
|
||||
}
|
||||
lastX, lastY = x, y; idx += 5
|
||||
} else { idx++ }
|
||||
} else { idx++ }
|
||||
}
|
||||
return pts
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
package logic
|
||||
|
||||
type HanziData struct {
|
||||
Character string `json:"character"`
|
||||
Strokes []string `json:"strokes"`
|
||||
Medians [][]Point `json:"medians"`
|
||||
}
|
||||
|
||||
type Point [2]float64
|
||||
@@ -0,0 +1,153 @@
|
||||
package zitie
|
||||
|
||||
import (
|
||||
"embed"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"toolbox/pkg/base"
|
||||
"toolbox/pkg/zitie/logic"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"golang.org/x/image/font/sfnt"
|
||||
)
|
||||
|
||||
//go:embed data/all.json
|
||||
var allDataContent []byte
|
||||
|
||||
//go:embed data/*.ttf
|
||||
var fontFS embed.FS
|
||||
|
||||
type zitieTool struct {
|
||||
allChars map[string]logic.HanziData
|
||||
}
|
||||
|
||||
func init() {
|
||||
base.Register(&zitieTool{})
|
||||
}
|
||||
|
||||
func (t *zitieTool) ID() string { return "zitie" }
|
||||
func (t *zitieTool) Name() string { return "汉字字帖生成" }
|
||||
func (t *zitieTool) Description() string { return "提供智能缺字处理和古风排版的专业字帖工具" }
|
||||
|
||||
func (t *zitieTool) Init() error {
|
||||
fmt.Println("Initializing Zitie tool with font check...")
|
||||
t.allChars = make(map[string]logic.HanziData)
|
||||
if err := json.Unmarshal(allDataContent, &t.allChars); err != nil {
|
||||
return fmt.Errorf("failed to unmarshal all.json: %v", err)
|
||||
}
|
||||
|
||||
fontBytes, _ := fontFS.ReadFile("data/font.ttf")
|
||||
logic.SetFontData(fontBytes)
|
||||
|
||||
fonts := map[string]string{
|
||||
"kaiti": "data/kaiti.ttf",
|
||||
"lishu": "data/lishu.ttf",
|
||||
"xingshu": "data/xingshu.ttf",
|
||||
"songti": "data/songti.ttf",
|
||||
}
|
||||
|
||||
for id, src := range fonts {
|
||||
bytes, err := fontFS.ReadFile(src)
|
||||
if err != nil { continue }
|
||||
|
||||
// 1. 同步到磁盘用于 gopdf
|
||||
tmpPath := filepath.Join(os.TempDir(), fmt.Sprintf("own_tools_%s.ttf", id))
|
||||
_ = os.WriteFile(tmpPath, bytes, 0644)
|
||||
logic.RegisterFontPath(id, tmpPath)
|
||||
|
||||
// 2. 解析 Cmap 用于缺字检查
|
||||
f, err := sfnt.Parse(bytes)
|
||||
if err == nil {
|
||||
logic.RegisterFontSFNT(id, f)
|
||||
fmt.Printf("Font [%s] analyzed and registered\n", id)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *zitieTool) RegisterRoutes(r *gin.RouterGroup) {
|
||||
r.POST("/teaching", t.handleTeaching)
|
||||
r.POST("/step", t.handleStep)
|
||||
r.POST("/manuscript", t.handleManuscript)
|
||||
}
|
||||
|
||||
type ZitieRequest struct {
|
||||
Chars string `json:"chars" binding:"required"`
|
||||
PaperSize string `json:"paper_size"`
|
||||
FontType string `json:"font_type"`
|
||||
}
|
||||
|
||||
func (t *zitieTool) handleTeaching(c *gin.Context) {
|
||||
var req ZitieRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
data := t.filterChars(req.Chars, false)
|
||||
t.generateAndResponse(c, data, "teaching", req.PaperSize, "kaiti")
|
||||
}
|
||||
|
||||
func (t *zitieTool) handleStep(c *gin.Context) {
|
||||
var req ZitieRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
data := t.filterChars(req.Chars, false)
|
||||
t.generateAndResponse(c, data, "step", req.PaperSize, "kaiti")
|
||||
}
|
||||
|
||||
func (t *zitieTool) handleManuscript(c *gin.Context) {
|
||||
var req ZitieRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
if req.FontType == "" { req.FontType = "kaiti" }
|
||||
data := t.filterChars(req.Chars, true)
|
||||
t.generateAndResponse(c, data, "manuscript", req.PaperSize, req.FontType)
|
||||
}
|
||||
|
||||
func (t *zitieTool) filterChars(input string, keepNewline bool) []logic.HanziData {
|
||||
var res []logic.HanziData
|
||||
// 扩充标点符号列表
|
||||
puncs := ",。!?;:、“”()《》〈〉…·.?!,:;\"'()<> 「」【】『』〔〕"
|
||||
|
||||
for _, r := range input {
|
||||
charStr := string(r)
|
||||
if r == '\n' {
|
||||
if keepNewline { res = append(res, logic.HanziData{Character: "\n"}) }
|
||||
continue
|
||||
}
|
||||
if r == ' ' || r == '\r' || r == '\t' { continue }
|
||||
|
||||
isPunc := false
|
||||
for _, p := range puncs { if r == p { isPunc = true; break } }
|
||||
if isPunc { continue }
|
||||
|
||||
if hd, ok := t.allChars[charStr]; ok {
|
||||
hd.Character = charStr
|
||||
res = append(res, hd)
|
||||
} else {
|
||||
res = append(res, logic.HanziData{Character: charStr})
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
func (t *zitieTool) generateAndResponse(c *gin.Context, data []logic.HanziData, mode, paper, font string) {
|
||||
if len(data) == 0 {
|
||||
c.JSON(http.StatusNotFound, gin.H{"error": "no valid characters found"})
|
||||
return
|
||||
}
|
||||
pdfBytes, err := logic.GeneratePDFExtended(data, mode, paper, font)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
|
||||
return
|
||||
}
|
||||
c.Data(http.StatusOK, "application/pdf", pdfBytes)
|
||||
}
|
||||
Reference in New Issue
Block a user