chore: optimize SEO and fix golangci-lint issues
- Implement dynamic SEO meta tags and sitemap.xml for toolbox.pengzhan.dev - Fix SPA title/meta refresh issue in layout.html - Address golangci-lint findings: - Refactor switch-case for tool path and SVG token parsing - Replace math.Pow with direct multiplication for performance - Fix unhandled error returns and unused imports - Omit redundant type declarations
This commit is contained in:
@@ -31,7 +31,7 @@ var IconCategories = map[string][]Icon{
|
||||
{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": {},
|
||||
"fruits": {},
|
||||
}
|
||||
|
||||
type SVG struct {
|
||||
|
||||
@@ -7,7 +7,6 @@ import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"toolbox/pkg/learnnumber/data"
|
||||
|
||||
"github.com/signintech/gopdf"
|
||||
@@ -33,15 +32,20 @@ type PathResult struct {
|
||||
var reSVGToken = regexp.MustCompile(`[a-zA-Z]|-?\d+\.?\d*`)
|
||||
|
||||
func GenerateCountingPDF(req CountingRequest) ([]byte, error) {
|
||||
rand.Seed(time.Now().UnixNano())
|
||||
pdf := &gopdf.GoPdf{}
|
||||
rect := gopdf.Rect{W: 595.28, H: 841.89}
|
||||
if req.PaperSize == "Letter" { rect = gopdf.Rect{W: 612, H: 792} }
|
||||
if req.PaperSize == "Letter" {
|
||||
rect = gopdf.Rect{W: 612, H: 792}
|
||||
}
|
||||
pdf.Start(gopdf.Config{PageSize: rect})
|
||||
|
||||
// 硬性限制:页数 1-10 页,防止资源耗尽
|
||||
if req.PageCount < 1 { req.PageCount = 1 }
|
||||
if req.PageCount > 10 { req.PageCount = 10 }
|
||||
if req.PageCount < 1 {
|
||||
req.PageCount = 1
|
||||
}
|
||||
if req.PageCount > 10 {
|
||||
req.PageCount = 10
|
||||
}
|
||||
|
||||
for i := 0; i < req.PageCount; i++ {
|
||||
pdf.AddPage()
|
||||
@@ -57,25 +61,40 @@ func GenerateCountingPDF(req CountingRequest) ([]byte, error) {
|
||||
func drawProblemV4(pdf *gopdf.GoPdf, req CountingRequest, startY, pW, pH float64) {
|
||||
margin := 40.0
|
||||
boxW, boxH := (pW-2*margin)*0.7, pH-60.0
|
||||
xBase, yBase := margin, startY + 30.0
|
||||
xBase, yBase := margin, startY+30.0
|
||||
|
||||
pdf.SetStrokeColor(0, 0, 0); pdf.SetLineWidth(2.5)
|
||||
pdf.SetStrokeColor(0, 0, 0)
|
||||
pdf.SetLineWidth(2.5)
|
||||
pdf.RectFromUpperLeft(xBase, yBase, boxW, boxH)
|
||||
|
||||
catIcons := data.IconCategories[req.Category]
|
||||
if len(catIcons) == 0 { catIcons = data.IconCategories["shapes"] }
|
||||
|
||||
if len(catIcons) == 0 {
|
||||
catIcons = data.IconCategories["shapes"]
|
||||
}
|
||||
|
||||
numTypes := req.IconTypes
|
||||
if numTypes < 1 { numTypes = 1 }; if numTypes > 6 { numTypes = 6 }
|
||||
if numTypes > len(catIcons) { numTypes = len(catIcons) }
|
||||
|
||||
if numTypes < 1 {
|
||||
numTypes = 1
|
||||
}
|
||||
if numTypes > 6 {
|
||||
numTypes = 6
|
||||
}
|
||||
if numTypes > len(catIcons) {
|
||||
numTypes = len(catIcons)
|
||||
}
|
||||
|
||||
total := req.TotalCount
|
||||
if total < numTypes { total = numTypes }; if total > 30 { total = 30 }
|
||||
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, catIcons[allIconsPerm[i]])
|
||||
@@ -87,49 +106,80 @@ func drawProblemV4(pdf *gopdf.GoPdf, req CountingRequest, startY, pW, pH float64
|
||||
}
|
||||
|
||||
avgRadius := math.Sqrt((boxW * boxH * 0.22) / (float64(total) * math.Pi))
|
||||
if avgRadius > 35.0 { avgRadius = 35.0 }
|
||||
if avgRadius > 35.0 {
|
||||
avgRadius = 35.0
|
||||
}
|
||||
|
||||
var placed []PlacedIcon
|
||||
iconTypeIdx, currentInType := 0, 0
|
||||
for i := 0; i < total; i++ {
|
||||
scaleVar := 0.9 + rand.Float64()*0.2; r := avgRadius * scaleVar
|
||||
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
|
||||
}
|
||||
}
|
||||
for _, p := range placed {
|
||||
dx := randX - p.X
|
||||
dy := randY - p.Y
|
||||
if math.Sqrt(dx*dx+dy*dy) < (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
|
||||
placed = append(placed, PlacedIcon{X: randX, Y: randY, Radius: r})
|
||||
break
|
||||
}
|
||||
}
|
||||
currentInType++; if currentInType >= counts[iconTypeIdx] { iconTypeIdx++; currentInType = 0 }
|
||||
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)
|
||||
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
|
||||
if len(rawResults) == 0 {
|
||||
return
|
||||
}
|
||||
var minX, minY, maxX, maxY = 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 }
|
||||
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 }
|
||||
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
|
||||
|
||||
@@ -141,10 +191,16 @@ func drawSimpleIcon(pdf *gopdf.GoPdf, icon data.Icon, cX, cY, size float64) {
|
||||
}
|
||||
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} }
|
||||
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) }
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -163,57 +219,122 @@ func parseMultiPath(icon data.Icon, scale, ox, oy float64) []PathResult {
|
||||
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 }
|
||||
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
|
||||
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++
|
||||
currentCmd = cmd
|
||||
isRel = (token[0] >= 'a' && token[0] <= 'z')
|
||||
i++
|
||||
continue
|
||||
}
|
||||
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 }
|
||||
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" }
|
||||
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++
|
||||
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++
|
||||
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})
|
||||
if i+5 >= len(tokens) {
|
||||
i = len(tokens)
|
||||
break
|
||||
}
|
||||
lx, ly = x, y; i += 6
|
||||
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 {
|
||||
invT := 1 - t
|
||||
tx := invT*invT*invT*lx + 3*invT*invT*t*x1 + 3*invT*t*t*x2 + t*t*t*x
|
||||
ty := invT*invT*invT*ly + 3*invT*invT*t*y1 + 3*invT*t*t*y2 + t*t*t*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})
|
||||
if i+3 >= len(tokens) {
|
||||
i = len(tokens)
|
||||
break
|
||||
}
|
||||
lx, ly = x, y; i += 4
|
||||
case "A": i += 7
|
||||
default: i++
|
||||
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 {
|
||||
invT := 1 - t
|
||||
tx := invT*invT*lx + 2*invT*t*x1 + t*t*x
|
||||
ty := invT*invT*ly + 2*invT*t*y1 + t*t*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++
|
||||
}
|
||||
}
|
||||
if len(pts) > 0 { results = append(results, PathResult{Points: pts, Closed: false}) }
|
||||
if len(pts) > 0 {
|
||||
results = append(results, PathResult{Points: pts, Closed: false})
|
||||
}
|
||||
}
|
||||
return results
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ type WritingRequest struct {
|
||||
}
|
||||
|
||||
type WritingNode struct {
|
||||
X, Y, R float64
|
||||
X, Y, R float64
|
||||
AngleA, AngleB float64
|
||||
BulgeR, Dist float64
|
||||
Number int
|
||||
@@ -26,11 +26,15 @@ type WritingNode struct {
|
||||
func GenerateWritingPDF(req WritingRequest) ([]byte, error) {
|
||||
pdf := &gopdf.GoPdf{}
|
||||
rect := gopdf.Rect{W: 595.28, H: 841.89}
|
||||
if req.PaperSize == "Letter" { rect = gopdf.Rect{W: 612, H: 792} }
|
||||
if req.PaperSize == "Letter" {
|
||||
rect = gopdf.Rect{W: 612, H: 792}
|
||||
}
|
||||
pdf.Start(gopdf.Config{PageSize: rect})
|
||||
|
||||
err := pdf.AddTTFFontData("basic", data.FontContent)
|
||||
if err != nil { return nil, err }
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
currentNum := req.StartNum
|
||||
// 只要还没达到 EndNum,就继续生成“整页”内容
|
||||
@@ -54,7 +58,7 @@ func drawOneFullPage(pdf *gopdf.GoPdf, start int, pW, pH float64) int {
|
||||
nodeR := 28.0
|
||||
bulgeR := nodeR * 0.32
|
||||
dist := nodeR + bulgeR + 18.0
|
||||
checkR := nodeR * 1.3
|
||||
checkR := nodeR * 1.3
|
||||
|
||||
var nodes []WritingNode
|
||||
num := start
|
||||
@@ -63,11 +67,13 @@ func drawOneFullPage(pdf *gopdf.GoPdf, start int, pW, pH float64) int {
|
||||
for {
|
||||
placed := false
|
||||
for retry := 0; retry < 1000; retry++ {
|
||||
rx := xBase + dist + 20 + rand.Float64()*(boxW - 2*dist - 40)
|
||||
ry := yBase + dist + 20 + rand.Float64()*(boxH - 2*dist - 40)
|
||||
|
||||
if isColliding(rx, ry, checkR, nodes) { continue }
|
||||
|
||||
rx := xBase + dist + 20 + rand.Float64()*(boxW-2*dist-40)
|
||||
ry := yBase + dist + 20 + rand.Float64()*(boxH-2*dist-40)
|
||||
|
||||
if isColliding(rx, ry, checkR, nodes) {
|
||||
continue
|
||||
}
|
||||
|
||||
angleA := rand.Float64() * 2 * math.Pi
|
||||
angleB := angleA + math.Pi + (rand.Float64()-0.5)*1.5
|
||||
nodes = append(nodes, WritingNode{X: rx, Y: ry, R: nodeR, AngleA: angleA, AngleB: angleB, BulgeR: bulgeR, Dist: dist, Number: num})
|
||||
@@ -98,7 +104,9 @@ func drawOneFullPage(pdf *gopdf.GoPdf, start int, pW, pH float64) int {
|
||||
}
|
||||
|
||||
func optimizeSpace(nodes []WritingNode, xBase, yBase, boxW, boxH, dist, checkR float64) {
|
||||
if len(nodes) < 2 { return }
|
||||
if len(nodes) < 2 {
|
||||
return
|
||||
}
|
||||
for iter := 0; iter < 120; iter++ {
|
||||
for i := range nodes {
|
||||
bestX, bestY := nodes[i].X, nodes[i].Y
|
||||
@@ -107,11 +115,16 @@ func optimizeSpace(nodes []WritingNode, xBase, yBase, boxW, boxH, dist, checkR f
|
||||
moveAngle := rand.Float64() * 2 * math.Pi
|
||||
nx := nodes[i].X + math.Cos(moveAngle)*5.0
|
||||
ny := nodes[i].Y + math.Sin(moveAngle)*5.0
|
||||
if nx-dist < xBase || nx+dist > xBase+boxW || ny-dist < yBase || ny+dist > yBase+boxH { continue }
|
||||
if isColliding(nx, ny, checkR, nodes[:i]) || isColliding(nx, ny, checkR, nodes[i+1:]) { continue }
|
||||
if nx-dist < xBase || nx+dist > xBase+boxW || ny-dist < yBase || ny+dist > yBase+boxH {
|
||||
continue
|
||||
}
|
||||
if isColliding(nx, ny, checkR, nodes[:i]) || isColliding(nx, ny, checkR, nodes[i+1:]) {
|
||||
continue
|
||||
}
|
||||
newMinDist := calcMinDist(nx, ny, i, nodes)
|
||||
if newMinDist > maxMinDist {
|
||||
maxMinDist = newMinDist; bestX, bestY = nx, ny
|
||||
maxMinDist = newMinDist
|
||||
bestX, bestY = nx, ny
|
||||
}
|
||||
}
|
||||
nodes[i].X, nodes[i].Y = bestX, bestY
|
||||
@@ -121,8 +134,12 @@ func optimizeSpace(nodes []WritingNode, xBase, yBase, boxW, boxH, dist, checkR f
|
||||
|
||||
func isColliding(x, y, r float64, existing []WritingNode) bool {
|
||||
for _, e := range existing {
|
||||
d := math.Sqrt(math.Pow(x-e.X, 2) + math.Pow(y-e.Y, 2))
|
||||
if d < (r + e.R*1.3 + 30.0) { return true }
|
||||
dx := x - e.X
|
||||
dy := y - e.Y
|
||||
d := math.Sqrt(dx*dx + dy*dy)
|
||||
if d < (r + e.R*1.3 + 30.0) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
@@ -130,50 +147,76 @@ func isColliding(x, y, r float64, existing []WritingNode) bool {
|
||||
func calcMinDist(x, y float64, idx int, nodes []WritingNode) float64 {
|
||||
minD := 10000.0
|
||||
for i, n := range nodes {
|
||||
if i == idx { continue }
|
||||
d := math.Sqrt(math.Pow(x-n.X, 2) + math.Pow(y-n.Y, 2))
|
||||
if d < minD { minD = d }
|
||||
if i == idx {
|
||||
continue
|
||||
}
|
||||
dx := x - n.X
|
||||
dy := y - n.Y
|
||||
d := math.Sqrt(dx*dx + dy*dy)
|
||||
if d < minD {
|
||||
minD = d
|
||||
}
|
||||
}
|
||||
return minD
|
||||
}
|
||||
|
||||
func getEdgePoint(x, y, r, angle float64, isCircle bool) (float64, float64) {
|
||||
gap := 2.0
|
||||
gap := 2.0
|
||||
er := r + gap
|
||||
if isCircle { return x + math.Cos(angle)*er, y + math.Sin(angle)*er }
|
||||
if isCircle {
|
||||
return x + math.Cos(angle)*er, y + math.Sin(angle)*er
|
||||
}
|
||||
absCos, absSin := math.Abs(math.Cos(angle)), math.Abs(math.Sin(angle))
|
||||
var d float64
|
||||
if absCos > absSin { d = er / absCos } else { d = er / absSin }
|
||||
if absCos > absSin {
|
||||
d = er / absCos
|
||||
} else {
|
||||
d = er / absSin
|
||||
}
|
||||
return x + math.Cos(angle)*d, y + math.Sin(angle)*d
|
||||
}
|
||||
|
||||
func drawCenteredText(pdf *gopdf.GoPdf, cx, cy float64, text string, fontSize float64) {
|
||||
pdf.SetFont("basic", "", fontSize)
|
||||
_ = pdf.SetFont("basic", "", fontSize)
|
||||
tw, _ := pdf.MeasureTextWidth(text)
|
||||
pdf.SetXY(cx - tw/2, cy + fontSize*0.38)
|
||||
pdf.Text(text)
|
||||
pdf.SetXY(cx-tw/2, cy+fontSize*0.38)
|
||||
_ = pdf.Text(text)
|
||||
}
|
||||
|
||||
func drawFullNode(pdf *gopdf.GoPdf, n WritingNode) {
|
||||
isOdd := n.Number%2 != 0
|
||||
pdf.SetStrokeColor(0, 0, 0); pdf.SetLineWidth(1.8)
|
||||
if isOdd { pdf.Oval(n.X-n.R, n.Y-n.R, n.X+n.R, n.Y+n.R) } else { pdf.RectFromUpperLeft(n.X-n.R, n.Y-n.R, n.R*2, n.R*2) }
|
||||
pdf.SetStrokeColor(0, 0, 0)
|
||||
pdf.SetLineWidth(1.8)
|
||||
if isOdd {
|
||||
pdf.Oval(n.X-n.R, n.Y-n.R, n.X+n.R, n.Y+n.R)
|
||||
} else {
|
||||
pdf.RectFromUpperLeft(n.X-n.R, n.Y-n.R, n.R*2, n.R*2)
|
||||
}
|
||||
pdf.SetTextColor(220, 220, 220)
|
||||
drawCenteredText(pdf, n.X, n.Y, strconv.Itoa(n.Number), n.R*1.35)
|
||||
|
||||
pdf.SetStrokeColor(180, 180, 180); pdf.SetLineWidth(1.0)
|
||||
ax, ay := n.X + math.Cos(n.AngleA)*n.Dist, n.Y + math.Sin(n.AngleA)*n.Dist
|
||||
pdf.SetStrokeColor(180, 180, 180)
|
||||
pdf.SetLineWidth(1.0)
|
||||
ax, ay := n.X+math.Cos(n.AngleA)*n.Dist, n.Y+math.Sin(n.AngleA)*n.Dist
|
||||
lx1, ly1 := getEdgePoint(n.X, n.Y, n.R, n.AngleA, isOdd)
|
||||
lx2, ly2 := getEdgePoint(ax, ay, n.BulgeR, n.AngleA+math.Pi, !isOdd)
|
||||
pdf.Line(lx1, ly1, lx2, ly2)
|
||||
if isOdd { pdf.RectFromUpperLeft(ax-n.BulgeR, ay-n.BulgeR, n.BulgeR*2, n.BulgeR*2) } else { pdf.Oval(ax-n.BulgeR, ay-n.BulgeR, ax+n.BulgeR, ay+n.BulgeR) }
|
||||
if isOdd {
|
||||
pdf.RectFromUpperLeft(ax-n.BulgeR, ay-n.BulgeR, n.BulgeR*2, n.BulgeR*2)
|
||||
} else {
|
||||
pdf.Oval(ax-n.BulgeR, ay-n.BulgeR, ax+n.BulgeR, ay+n.BulgeR)
|
||||
}
|
||||
|
||||
bx, by := n.X + math.Cos(n.AngleB)*n.Dist, n.Y + math.Sin(n.AngleB)*n.Dist
|
||||
bx, by := n.X+math.Cos(n.AngleB)*n.Dist, n.Y+math.Sin(n.AngleB)*n.Dist
|
||||
lx3, ly3 := getEdgePoint(n.X, n.Y, n.R, n.AngleB, isOdd)
|
||||
lx4, ly4 := getEdgePoint(bx, by, n.BulgeR, n.AngleB+math.Pi, !isOdd)
|
||||
pdf.Line(lx3, ly3, lx4, ly4)
|
||||
if isOdd { pdf.RectFromUpperLeft(bx-n.BulgeR, by-n.BulgeR, n.BulgeR*2, n.BulgeR*2) } else { pdf.Oval(bx-n.BulgeR, by-n.BulgeR, bx+n.BulgeR, by+n.BulgeR) }
|
||||
|
||||
if isOdd {
|
||||
pdf.RectFromUpperLeft(bx-n.BulgeR, by-n.BulgeR, n.BulgeR*2, n.BulgeR*2)
|
||||
} else {
|
||||
pdf.Oval(bx-n.BulgeR, by-n.BulgeR, bx+n.BulgeR, by+n.BulgeR)
|
||||
}
|
||||
|
||||
pdf.SetTextColor(120, 120, 120)
|
||||
drawCenteredText(pdf, bx, by, strconv.Itoa(n.Number+1), math.Max(7, n.BulgeR*1.2))
|
||||
}
|
||||
|
||||
@@ -16,10 +16,12 @@ func init() {
|
||||
base.Register(&learnNumberTool{})
|
||||
}
|
||||
|
||||
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) 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...")
|
||||
|
||||
Reference in New Issue
Block a user