+
+
+
-
-
-
-
-
-
-
-
+
+
+
+
+
+
-
+
-
+
+
{{end}}
diff --git a/pkg/base/tool.go b/pkg/base/tool.go
index e3296c0..8f177ed 100644
--- a/pkg/base/tool.go
+++ b/pkg/base/tool.go
@@ -1,22 +1,18 @@
package base
-import (
- "github.com/gin-gonic/gin"
-)
+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 路由
+ ID() string
+ Name() string
+ Description() string
+ Emoji() string
+ Init() error
+ RegisterRoutes(r *gin.RouterGroup)
}
-// Registry 存储所有已注册的工具
var Registry = make(map[string]Tool)
-// Register 用于工具在 init() 函数中注册自己
func Register(t Tool) {
Registry[t.ID()] = t
}
diff --git a/pkg/learnnumber/data/fruit.svg b/pkg/learnnumber/data/fruit.svg
new file mode 100644
index 0000000..202ffdf
--- /dev/null
+++ b/pkg/learnnumber/data/fruit.svg
@@ -0,0 +1,3830 @@
+
+
+
diff --git a/pkg/learnnumber/data/icons.go b/pkg/learnnumber/data/icons.go
index 3f174aa..4f4c39c 100644
--- a/pkg/learnnumber/data/icons.go
+++ b/pkg/learnnumber/data/icons.go
@@ -1,41 +1,77 @@
package data
+import (
+ "encoding/xml"
+ "fmt"
+ "os"
+ "strings"
+)
+
type Icon struct {
Name string
Paths []string
}
-// 升级版:更卡通、更饱满的简笔画
-var CountingIcons = []Icon{
- {Name: "Bear", Paths: []string{
- "M 512 800 C 300 800 200 700 200 500 C 200 300 350 200 512 200 C 674 200 824 300 824 500 C 824 700 724 800 512 800 Z", // Body
- "M 300 300 m -50 0 a 50 50 0 1 0 100 0 a 50 50 0 1 0 -100 0", // Ear L
- "M 724 300 m -50 0 a 50 50 0 1 0 100 0 a 50 50 0 1 0 -100 0", // Ear R
- "M 400 450 m -20 0 a 20 20 0 1 0 40 0 a 20 20 0 1 0 -40 0", // Eye L
- "M 624 450 m -20 0 a 20 20 0 1 0 40 0 a 20 20 0 1 0 -40 0", // Eye R
- "M 512 550 Q 512 650 400 650 M 512 550 Q 512 650 624 650", // Mouth
- }},
- {Name: "Cat", Paths: []string{
- "M 200 800 L 300 400 L 400 200 L 512 350 L 624 200 L 724 400 L 824 800 Z", // Head
- "M 400 550 m -15 0 a 15 15 0 1 0 30 0 a 15 15 0 1 0 -30 0", // Eye L
- "M 624 550 m -15 0 a 15 15 0 1 0 30 0 a 15 15 0 1 0 -30 0", // Eye R
- "M 512 650 L 450 700 M 512 650 L 574 700", // Nose
- }},
- {Name: "Car", Paths: []string{
- "M 100 700 L 100 500 Q 100 400 300 400 L 700 400 Q 900 400 900 500 L 900 700 Z", // Body
- "M 250 700 m -60 0 a 60 60 0 1 0 120 0 a 60 60 0 1 0 -120 0", // Wheel L
- "M 750 700 m -60 0 a 60 60 0 1 0 120 0 a 60 60 0 1 0 -120 0", // Wheel R
- "M 300 400 L 400 250 L 624 250 L 724 400", // Roof
- }},
- {Name: "Bird", Paths: []string{
- "M 512 512 m -300 0 a 300 300 0 1 0 600 0 a 300 300 0 1 0 -600 0", // Body
- "M 812 512 L 950 450 L 812 400 Z", // Beak
- "M 400 400 m -20 0 a 20 20 0 1 0 40 0 a 20 20 0 1 0 -40 0", // Eye
- "M 212 512 Q 100 400 212 300", // Wing
- }},
- {Name: "Rocket", Paths: []string{
- "M 512 100 Q 700 400 700 800 L 324 800 Q 324 400 512 100 Z", // Body
- "M 512 400 m -50 0 a 50 50 0 1 0 100 0 a 50 50 0 1 0 -100 0", // Window
- "M 324 800 L 200 950 L 324 900 M 700 800 L 824 950 L 700 900", // Fins
- }},
+var IconCategories = map[string][]Icon{
+ "shapes": {
+ {Name: "Circle", Paths: []string{"M 512 112 C 291 112 112 291 112 512 C 112 733 291 912 512 912 C 733 912 912 733 912 512 C 912 291 733 112 512 112 Z"}},
+ {Name: "Square", Paths: []string{"M 150 150 L 874 150 L 874 874 L 150 874 Z"}},
+ {Name: "Rectangle", Paths: []string{"M 100 300 L 924 300 L 924 724 L 100 724 Z"}},
+ {Name: "Triangle", Paths: []string{"M 512 150 L 900 850 L 124 850 Z"}},
+ {Name: "Star", Paths: []string{"M 512 100 L 612 400 L 924 400 L 674 600 L 774 900 L 512 700 L 250 900 L 350 600 L 100 400 L 412 400 Z"}},
+ {Name: "Heart", Paths: []string{"M 512 900 C 200 700 100 500 100 300 C 100 100 400 100 512 250 C 624 100 924 100 924 300 C 924 500 824 700 512 900 Z"}},
+ {Name: "Diamond", Paths: []string{"M 512 100 L 900 512 L 512 924 L 124 512 Z"}},
+ {Name: "Oval", Paths: []string{"M 512 350 C 200 350 100 420 100 512 C 100 604 200 674 512 674 C 824 674 924 604 924 512 C 924 420 824 350 512 350 Z"}},
+ {Name: "Trapezoid", Paths: []string{"M 300 200 L 724 200 L 924 800 L 100 800 Z"}},
+ {Name: "Hexagon", Paths: []string{"M 512 100 L 858 300 L 858 724 L 512 924 L 166 724 L 166 300 Z"}},
+ },
+ "fruits": {},
+}
+
+type SVG struct {
+ Groups []G `xml:"g"`
+}
+
+type G struct {
+ ID string `xml:"id,attr"`
+ Groups []G `xml:"g"`
+ Paths []Path `xml:"path"`
+}
+
+type Path struct {
+ D string `xml:"d,attr"`
+}
+
+func LoadIcons(filePath string) error {
+ data, err := os.ReadFile(filePath)
+ if err != nil { return err }
+ var svg SVG
+ if err := xml.Unmarshal(data, &svg); err != nil { return err }
+ var objectsG *G
+ for i := range svg.Groups {
+ if svg.Groups[i].ID == "objects" { objectsG = &svg.Groups[i]; break }
+ }
+ if objectsG == nil { return nil }
+ var fruitIcons []Icon
+ for i, g := range objectsG.Groups {
+ paths := collectPaths(g)
+ if len(paths) > 0 {
+ fruitIcons = append(fruitIcons, Icon{
+ Name: fmt.Sprintf("Item %d", i+1),
+ Paths: paths,
+ })
+ }
+ }
+ IconCategories["fruits"] = fruitIcons
+ return nil
+}
+
+func collectPaths(g G) []string {
+ var paths []string
+ for _, p := range g.Paths {
+ d := strings.TrimSpace(p.D)
+ if d != "" { paths = append(paths, d) }
+ }
+ for _, subG := range g.Groups { paths = append(paths, collectPaths(subG)...) }
+ return paths
}
diff --git a/pkg/learnnumber/logic/counting.go b/pkg/learnnumber/logic/counting.go
index 5a4b683..02bcc8e 100644
--- a/pkg/learnnumber/logic/counting.go
+++ b/pkg/learnnumber/logic/counting.go
@@ -16,10 +16,21 @@ import (
type CountingRequest struct {
TotalCount int `json:"total_count"`
IconTypes int `json:"icon_types"`
+ PageCount int `json:"page_count"`
+ Category string `json:"category"`
PaperSize string `json:"paper_size"`
}
-var reSVG = regexp.MustCompile(`([MLQCZmlqcz])|(-?\d+\.?\d*)`)
+type PlacedIcon struct {
+ X, Y, Radius float64
+}
+
+type PathResult struct {
+ Points []gopdf.Point
+ Closed bool
+}
+
+var reSVGToken = regexp.MustCompile(`[a-zA-Z]|-?\d+\.?\d*`)
func GenerateCountingPDF(req CountingRequest) ([]byte, error) {
rand.Seed(time.Now().UnixNano())
@@ -27,142 +38,182 @@ func GenerateCountingPDF(req CountingRequest) ([]byte, error) {
rect := gopdf.Rect{W: 595.28, H: 841.89}
if req.PaperSize == "Letter" { rect = gopdf.Rect{W: 612, H: 792} }
pdf.Start(gopdf.Config{PageSize: rect})
- pdf.AddPage()
- drawProblem(pdf, req, 0, rect.W, rect.H/2)
- drawProblem(pdf, req, rect.H/2, rect.W, rect.H/2)
+ // 硬性限制:页数 1-10 页,防止资源耗尽
+ if req.PageCount < 1 { req.PageCount = 1 }
+ if req.PageCount > 10 { req.PageCount = 10 }
+
+ for i := 0; i < req.PageCount; i++ {
+ pdf.AddPage()
+ drawProblemV4(pdf, req, 0, rect.W, rect.H/2)
+ drawProblemV4(pdf, req, rect.H/2, rect.W, rect.H/2)
+ }
var buf bytes.Buffer
_, err := pdf.WriteTo(&buf)
return buf.Bytes(), err
}
-func drawProblem(pdf *gopdf.GoPdf, req CountingRequest, startY, pW, pH float64) {
+func drawProblemV4(pdf *gopdf.GoPdf, req CountingRequest, startY, pW, pH float64) {
margin := 40.0
- boxW := (pW - 2*margin) * 0.7
- boxH := pH - 60.0
- x, y := margin, startY + 30.0
+ boxW, boxH := (pW-2*margin)*0.7, pH-60.0
+ xBase, yBase := margin, startY + 30.0
- // 1. 绘制方框
- pdf.SetStrokeColor(0, 0, 0); pdf.SetLineWidth(2.0)
- pdf.RectFromUpperLeft(x, y, boxW, boxH)
+ pdf.SetStrokeColor(0, 0, 0); pdf.SetLineWidth(2.5)
+ pdf.RectFromUpperLeft(xBase, yBase, boxW, boxH)
- // 2. 分配图标数量
- numTypes := req.IconTypes; if numTypes < 1 { numTypes = 1 }
- if numTypes > len(data.CountingIcons) { numTypes = len(data.CountingIcons) }
+ catIcons := data.IconCategories[req.Category]
+ if len(catIcons) == 0 { catIcons = data.IconCategories["shapes"] }
- total := req.TotalCount; if total < numTypes { total = numTypes }
- if total > 30 { total = 30 }
-
- // 随机选出图标种类
- allIcons := rand.Perm(len(data.CountingIcons))
- selectedIcons := []data.Icon{}
- counts := []int{}
+ numTypes := req.IconTypes
+ if numTypes < 1 { numTypes = 1 }; if numTypes > 6 { numTypes = 6 }
+ if numTypes > len(catIcons) { numTypes = len(catIcons) }
- // 先每种分配1个
- rem := total - numTypes
+ total := req.TotalCount
+ if total < numTypes { total = numTypes }; if total > 30 { total = 30 }
+
+ allIconsPerm := rand.Perm(len(catIcons))
+ var selectedIcons []data.Icon
+ counts := make([]int, numTypes)
+
+ // 随机分配逻辑
for i := 0; i < numTypes; i++ {
- selectedIcons = append(selectedIcons, data.CountingIcons[allIcons[i]])
- counts = append(counts, 1)
+ selectedIcons = append(selectedIcons, catIcons[allIconsPerm[i]])
+ counts[i] = 1
}
- // 随机分配剩下的
- for i := 0; i < rem; i++ {
+ remaining := total - numTypes
+ for i := 0; i < remaining; i++ {
counts[rand.Intn(numTypes)]++
}
- // 3. 布局计划 (使用网格,增加 Padding 避开边框)
- rows, cols := 5, 6
- cellW, cellH := boxW/float64(cols), boxH/float64(rows)
- indices := rand.Perm(rows * cols)
-
- // 严格图标大小:单元格的 70%,确保不溢出单元格边界
- iconSize := math.Min(cellW, cellH) * 0.7
- paddingX, paddingY := (cellW - iconSize)/2, (cellH - iconSize)/2
+ avgRadius := math.Sqrt((boxW * boxH * 0.22) / (float64(total) * math.Pi))
+ if avgRadius > 35.0 { avgRadius = 35.0 }
- iconTypeIdx := 0
- countInCurrentType := 0
-
+ var placed []PlacedIcon
+ iconTypeIdx, currentInType := 0, 0
for i := 0; i < total; i++ {
- cellIdx := indices[i]
- rIdx, cIdx := cellIdx/cols, cellIdx%cols
-
- // 基础位置
- baseX := x + float64(cIdx)*cellW + paddingX
- baseY := y + float64(rIdx)*cellH + paddingY
-
- // 渲染图标
- drawIcon(pdf, selectedIcons[iconTypeIdx], baseX + iconSize/2, baseY + iconSize/2, iconSize)
-
- countInCurrentType++
- if countInCurrentType >= counts[iconTypeIdx] {
- iconTypeIdx++
- countInCurrentType = 0
- }
- }
-
- // 4. 绘制右侧对照表
- legendX := x + boxW + 20.0
- legendStepY := boxH / float64(numTypes + 1)
- for i := 0; i < numTypes; i++ {
- lY := y + float64(i+1)*legendStepY
- drawIcon(pdf, selectedIcons[i], legendX + 20, lY, 30)
- pdf.SetStrokeColor(150, 150, 150); pdf.SetLineWidth(0.8)
- pdf.RectFromUpperLeft(legendX + 50, lY - 15, 30, 30)
- }
-}
-
-func drawIcon(pdf *gopdf.GoPdf, icon data.Icon, cX, cY, size float64) {
- scale := size / 1024.0
- pdf.SetStrokeColor(0, 0, 0); pdf.SetLineWidth(1.5) // 加粗线条
-
- ox, oy := cX - size/2, cY - size/2
- for _, pStr := range icon.Paths {
- pts := parseSmoothPath(pStr, scale, ox, oy)
- if len(pts) > 1 {
- // 如果是闭合路径
- if strings.Contains(strings.ToUpper(pStr), "Z") {
- pdf.Polygon(pts, "D")
- } else {
- for i := 0; i < len(pts)-1; i++ {
- pdf.Line(pts[i].X, pts[i].Y, pts[i+1].X, pts[i+1].Y)
+ scaleVar := 0.9 + rand.Float64()*0.2; r := avgRadius * scaleVar
+ for retry := 0; retry < 200; retry++ {
+ randX := xBase + r + 5 + rand.Float64()*(boxW-2*r-10)
+ randY := yBase + r + 5 + rand.Float64()*(boxH-2*r-10)
+ collision := false
+ for _, p := range placed {
+ if math.Sqrt(math.Pow(randX-p.X, 2)+math.Pow(randY-p.Y, 2)) < (r + p.Radius + 10.0) {
+ collision = true; break
}
}
+ if !collision {
+ drawSimpleIcon(pdf, selectedIcons[iconTypeIdx], randX, randY, r*2)
+ placed = append(placed, PlacedIcon{X: randX, Y: randY, Radius: r}); break
+ }
+ }
+ currentInType++; if currentInType >= counts[iconTypeIdx] { iconTypeIdx++; currentInType = 0 }
+ }
+
+ legendX, legendStepY := xBase+boxW+20.0, boxH/float64(numTypes+1)
+ for i := 0; i < numTypes; i++ {
+ lY := yBase + float64(i+1)*legendStepY
+ drawSimpleIcon(pdf, selectedIcons[i], legendX+25, lY, 35)
+ pdf.SetStrokeColor(150, 150, 150); pdf.SetLineWidth(1.0); pdf.RectFromUpperLeft(legendX+60, lY-15, 35, 35)
+ }
+}
+
+func drawSimpleIcon(pdf *gopdf.GoPdf, icon data.Icon, cX, cY, size float64) {
+ rawResults := parseMultiPath(icon, 1.0, 0, 0)
+ if len(rawResults) == 0 { return }
+ var minX, minY, maxX, maxY float64 = 1e9, 1e9, -1e9, -1e9
+ for _, res := range rawResults {
+ for _, p := range res.Points {
+ if p.X < minX { minX = p.X }; if p.X > maxX { maxX = p.X }
+ if p.Y < minY { minY = p.Y }; if p.Y > maxY { maxY = p.Y }
+ }
+ }
+ curW, curH := maxX-minX, maxY-minY
+ if curW <= 0 { curW = 1 }; if curH <= 0 { curH = 1 }
+ scale := size / math.Max(curW, curH)
+ ox, oy := cX-(curW*scale)/2-minX*scale, cY-(curH*scale)/2-minY*scale
+
+ pdf.SetStrokeColor(0, 0, 0)
+ if strings.Contains(strings.ToLower(icon.Name), "item") || len(icon.Paths) > 1 {
+ pdf.SetLineWidth(size / 60.0) // 复杂素材用细线 (30% less than 45 is approx 60)
+ } else {
+ pdf.SetLineWidth(size / 22.0) // 基础图形用粗线
+ }
+ for _, res := range rawResults {
+ scaledPts := make([]gopdf.Point, len(res.Points))
+ for i, p := range res.Points { scaledPts[i] = gopdf.Point{X: ox + p.X*scale, Y: oy + p.Y*scale} }
+ if len(scaledPts) > 1 {
+ if res.Closed { pdf.Polygon(scaledPts, "D") } else {
+ for j := 0; j < len(scaledPts)-1; j++ { pdf.Line(scaledPts[j].X, scaledPts[j].Y, scaledPts[j+1].X, scaledPts[j+1].Y) }
+ }
}
}
}
-// 支持贝塞尔曲线采样,确保图标圆润
-func parseSmoothPath(path string, scale, ox, oy float64) []gopdf.Point {
- var pts []gopdf.Point
- matches := reSVG.FindAllStringSubmatch(path, -1)
- var lx, ly float64
- for i := 0; i < len(matches); {
- cmd := matches[i][0]
- if cmd == "M" || cmd == "L" {
- x, _ := strconv.ParseFloat(matches[i+1][0], 64); y, _ := strconv.ParseFloat(matches[i+2][0], 64)
- lx, ly = x, y
- pts = append(pts, gopdf.Point{X: ox + x*scale, Y: oy + y*scale})
- i += 3
- } else if cmd == "C" {
- x1, _ := strconv.ParseFloat(matches[i+1][0], 64); y1, _ := strconv.ParseFloat(matches[i+2][0], 64)
- x2, _ := strconv.ParseFloat(matches[i+3][0], 64); y2, _ := strconv.ParseFloat(matches[i+4][0], 64)
- x, _ := strconv.ParseFloat(matches[i+5][0], 64); y, _ := strconv.ParseFloat(matches[i+6][0], 64)
- for t := 0.25; t <= 1.0; t += 0.25 {
- tx := math.Pow(1-t, 3)*lx + 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)*ly + 3*math.Pow(1-t, 2)*t*y1 + 3*(1-t)*math.Pow(t, 2)*y2 + math.Pow(t, 3)*y
- pts = append(pts, gopdf.Point{X: ox + tx*scale, Y: oy + ty*scale})
+func parseMultiPath(icon data.Icon, scale, ox, oy float64) []PathResult {
+ var results []PathResult
+ for _, path := range icon.Paths {
+ tokens := reSVGToken.FindAllString(path, -1)
+ var lx, ly, startX, startY float64
+ var pts []gopdf.Point
+ var currentCmd string
+ var isRel bool
+ for i := 0; i < len(tokens); {
+ token := tokens[i]
+ if (token[0] >= 'a' && token[0] <= 'z') || (token[0] >= 'A' && token[0] <= 'Z') {
+ cmd := strings.ToUpper(token)
+ if cmd == "M" {
+ if len(pts) > 0 { results = append(results, PathResult{Points: pts, Closed: false}); pts = nil }
+ } else if cmd == "Z" {
+ if len(pts) > 0 { results = append(results, PathResult{Points: pts, Closed: true}); pts = nil }
+ lx, ly = startX, startY; i++; continue
+ }
+ currentCmd = cmd; isRel = (token[0] >= 'a' && token[0] <= 'z'); i++
+ continue
}
- lx, ly = x, y; i += 7
- } else if cmd == "Q" {
- x1, _ := strconv.ParseFloat(matches[i+1][0], 64); y1, _ := strconv.ParseFloat(matches[i+2][0], 64)
- x, _ := strconv.ParseFloat(matches[i+3][0], 64); y, _ := strconv.ParseFloat(matches[i+4][0], 64)
- for t := 0.25; t <= 1.0; t += 0.25 {
- tx := math.Pow(1-t, 2)*lx + 2*(1-t)*t*x1 + math.Pow(t, 2)*x
- ty := math.Pow(1-t, 2)*ly + 2*(1-t)*t*y1 + math.Pow(t, 2)*y
- pts = append(pts, gopdf.Point{X: ox + tx*scale, Y: oy + ty*scale})
+ switch currentCmd {
+ case "M", "L":
+ if i+1 >= len(tokens) { i = len(tokens); break }
+ x, _ := strconv.ParseFloat(tokens[i], 64); y, _ := strconv.ParseFloat(tokens[i+1], 64)
+ if isRel { x += lx; y += ly }
+ if currentCmd == "M" { startX, startY = x, y }
+ lx, ly = x, y
+ pts = append(pts, gopdf.Point{X: ox + x*scale, Y: oy + y*scale}); i += 2
+ if currentCmd == "M" { currentCmd = "L" }
+ case "H":
+ x, _ := strconv.ParseFloat(tokens[i], 64); if isRel { x += lx }; lx = x
+ pts = append(pts, gopdf.Point{X: ox + x*scale, Y: oy + ly*scale}); i++
+ case "V":
+ y, _ := strconv.ParseFloat(tokens[i], 64); if isRel { y += ly }; ly = y
+ pts = append(pts, gopdf.Point{X: ox + lx*scale, Y: oy + y*scale}); i++
+ case "C":
+ if i+5 >= len(tokens) { i = len(tokens); break }
+ x1, _ := strconv.ParseFloat(tokens[i], 64); y1, _ := strconv.ParseFloat(tokens[i+1], 64)
+ x2, _ := strconv.ParseFloat(tokens[i+2], 64); y2, _ := strconv.ParseFloat(tokens[i+3], 64)
+ x, _ := strconv.ParseFloat(tokens[i+4], 64); y, _ := strconv.ParseFloat(tokens[i+5], 64)
+ if isRel { x1 += lx; y1 += ly; x2 += lx; y2 += ly; x += lx; y += ly }
+ for t := 0.2; t <= 1.0; t += 0.2 {
+ tx := math.Pow(1-t, 3)*lx + 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)*ly + 3*math.Pow(1-t, 2)*t*y1 + 3*(1-t)*math.Pow(t, 2)*y2 + math.Pow(t, 3)*y
+ pts = append(pts, gopdf.Point{X: ox + tx*scale, Y: oy + ty*scale})
+ }
+ lx, ly = x, y; i += 6
+ case "Q":
+ if i+3 >= len(tokens) { i = len(tokens); break }
+ x1, _ := strconv.ParseFloat(tokens[i], 64); y1, _ := strconv.ParseFloat(tokens[i+1], 64)
+ x, _ := strconv.ParseFloat(tokens[i+2], 64); y, _ := strconv.ParseFloat(tokens[i+3], 64)
+ if isRel { x1 += lx; y1 += ly; x += lx; y += ly }
+ for t := 0.25; t <= 1.0; t += 0.25 {
+ tx := math.Pow(1-t, 2)*lx + 2*(1-t)*t*x1 + math.Pow(t, 2)*x
+ ty := math.Pow(1-t, 2)*ly + 2*(1-t)*t*y1 + math.Pow(t, 2)*y
+ pts = append(pts, gopdf.Point{X: ox + tx*scale, Y: oy + ty*scale})
+ }
+ lx, ly = x, y; i += 4
+ case "A": i += 7
+ default: i++
}
- lx, ly = x, y; i += 5
- } else { i++ }
+ }
+ if len(pts) > 0 { results = append(results, PathResult{Points: pts, Closed: false}) }
}
- return pts
+ return results
}
diff --git a/pkg/learnnumber/tool.go b/pkg/learnnumber/tool.go
index 25b70ea..1223137 100644
--- a/pkg/learnnumber/tool.go
+++ b/pkg/learnnumber/tool.go
@@ -4,6 +4,7 @@ import (
"fmt"
"net/http"
"toolbox/pkg/base"
+ "toolbox/pkg/learnnumber/data"
"toolbox/pkg/learnnumber/logic"
"github.com/gin-gonic/gin"
@@ -18,14 +19,22 @@ func init() {
func (t *learnNumberTool) ID() string { return "learn-number" }
func (t *learnNumberTool) Name() string { return "幼儿数学助手" }
func (t *learnNumberTool) Description() string { return "包含数图形、基础加减法等趣味数学练习" }
+func (t *learnNumberTool) Emoji() string { return "🔢" }
func (t *learnNumberTool) Init() error {
fmt.Println("Initializing Learn-Number tool...")
- return nil
+ // 动态加载匿名对象
+ return data.LoadIcons("pkg/learnnumber/data/fruit.svg")
}
func (t *learnNumberTool) RegisterRoutes(r *gin.RouterGroup) {
r.POST("/counting", t.handleCounting)
+ r.GET("/categories", t.handleCategories)
+}
+
+func (t *learnNumberTool) handleCategories(c *gin.Context) {
+ // 返回分类信息供前端预览
+ c.JSON(http.StatusOK, data.IconCategories)
}
func (t *learnNumberTool) handleCounting(c *gin.Context) {
@@ -34,15 +43,10 @@ func (t *learnNumberTool) handleCounting(c *gin.Context) {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
-
- if req.TotalCount <= 0 { req.TotalCount = 20 }
- if req.TotalCount > 30 { req.TotalCount = 30 }
-
pdfBytes, err := logic.GenerateCountingPDF(req)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
return
}
-
c.Data(http.StatusOK, "application/pdf", pdfBytes)
}
diff --git a/pkg/zitie/tool.go b/pkg/zitie/tool.go
index b22d45e..aa535bc 100644
--- a/pkg/zitie/tool.go
+++ b/pkg/zitie/tool.go
@@ -31,6 +31,7 @@ func init() {
func (t *zitieTool) ID() string { return "zitie" }
func (t *zitieTool) Name() string { return "汉字字帖生成" }
func (t *zitieTool) Description() string { return "提供智能缺字处理和古风排版的专业字帖工具" }
+func (t *zitieTool) Emoji() string { return "🎨" }
func (t *zitieTool) Init() error {
fmt.Println("Initializing Zitie tool with font check...")