Skip to content

Commit

Permalink
chore: fix bugs and add features
Browse files Browse the repository at this point in the history
  • Loading branch information
reu98 committed Mar 25, 2024
1 parent 146b60c commit 8cd2683
Show file tree
Hide file tree
Showing 18 changed files with 440 additions and 404 deletions.
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,14 @@
### Add:

- Document package

## [1.1.0] - 2024-03-26

### Fix:

- The random color error is not working.

### Feature:

- Add generate noise.
- Add the option to choose a font.
58 changes: 15 additions & 43 deletions captcha.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
// Package captcha provides an easy to use
package captcha

import "fmt"
import (
"fmt"
"image/color"
)

type Result struct {
// A random string or the result of an operation.
Expand All @@ -16,7 +19,7 @@ func CreateByText(option OptionText) (*Result, error) {
opt := getOptionByText(option)
text := opt.randomText()

data, err := createCaptcha(text, opt)
data, err := opt.createCaptcha(text)
if err != nil {
return nil, err
}
Expand All @@ -31,25 +34,9 @@ func CreateByText(option OptionText) (*Result, error) {
// It will return a captcha with an operation like 1 + 1.
func CreateByMath(option OptionMath) (*Result, error) {
opt := getOptionByMath(option)
min := mathMinDefault
if opt.MathMin != nil {
min = *opt.MathMin
}

max := mathMaxDefault
if opt.MathMax != nil {
max = *opt.MathMax
}

var operator matchOperator
if opt.MathOperator != nil {
operator = *opt.MathOperator
} else {
operator = randomOperation()
}

resultMath := generateMathOperation(&min, &max, &operator)
data, err := createCaptcha((*resultMath).Equation, opt)
resultMath := opt.generateMathOperation()
data, err := opt.createCaptcha(resultMath.Equation)
if err != nil {
return nil, err
}
Expand All @@ -60,35 +47,20 @@ func CreateByMath(option OptionMath) (*Result, error) {
}, nil
}

func createCaptcha(text string, opt *option) (string, error) {
width := widthDefault
if opt.Width != nil {
width = *opt.Width
}

height := heightDefault
if opt.Height != nil {
height = *opt.Height
}

bg := ""
if opt.BackgroundColor != nil {
isColor := true
opt.IsColor = &isColor
bg = *opt.BackgroundColor
}

result := fmt.Sprintf("<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"%v\" height=\"%v\" viewBox=\"0,0,%v,%v\" style=\"transform: rotateX(180deg)\">", width, height, width, height)
if bg != "" {
result += fmt.Sprintf("<rect fill=\"%v\" width=\"100%%\" height=\"100%%\"/>", bg)
func (opt *option) createCaptcha(text string) (string, error) {
result := fmt.Sprintf("<svg xmlns=\"http://www.w3.org/2000/svg\" width=\"%v\" height=\"%v\" viewBox=\"0,0,%v,%v\" style=\"transform: rotateX(180deg)\">", opt.width, opt.height, opt.width, opt.height)
if opt.backgroundColor != color.Transparent {
r, g, b, a := opt.backgroundColor.RGBA()
result += fmt.Sprintf("<rect fill=\"rgba(%v, %v, %v, %v)\" width=\"100%%\" height=\"100%%\"/>", r>>8, g>>8, b>>8, a>>8)
}

lineNoise := opt.drawLineNoise()
lineNoise := opt.drawCurve()
noise := opt.drawNoise()
pathText, err := opt.drawText(text)
if err != nil {
return "", err
}
result += fmt.Sprintf("%v%v</svg>", lineNoise, pathText)
result += fmt.Sprintf("%v%v%v</svg>", lineNoise, pathText, noise)

return result, nil
}
4 changes: 2 additions & 2 deletions captcha_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,8 @@ func TestCreateByMathByOption(t *testing.T) {
minMath := uint8(rand.Uint32())
maxMath := uint16(minMath) + uint16(rand.Uint32())
option := OptionMath{
MathMin: &minMath,
MathMax: &maxMath,
MathMin: minMath,
MathMax: maxMath,
}
data, err := CreateByMath(option)

Expand Down
46 changes: 27 additions & 19 deletions constants.go
Original file line number Diff line number Diff line change
@@ -1,23 +1,31 @@
package captcha

const (
characters string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
mathMinDefault uint8 = 1
mathMaxDefault uint16 = 9
widthDefault uint16 = 150
heightDefault uint16 = 50
noiseDefault uint8 = 1
sizeDefault uint8 = 4
fontSizeDefault uint8 = 12
ratioFontSize uint8 = 4
noiseGreyColorMinDefault uint8 = 1
noiseGreyColorMaxDefault uint8 = 9
noiseGreyColorMinInverse uint8 = 7
noiseGreyColorMaxInverse uint8 = 15
paddingHorizontalDefault uint8 = 5
fillColorMinDefault uint8 = 0
fillColorMaxDefault uint8 = 4
fillColorMinInverse uint8 = 10
fillColorMaxInverse uint8 = 14
regexColor string = "^#(?:[0-9a-fA-F]{3}){1,2}$"
characters string = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"

fontSizeDefault uint8 = 16
ratioFontSize uint8 = 8

mathMinDefault uint8 = 1
mathMaxDefault uint16 = 9

noiseRadius uint8 = 1

widthDefault uint16 = 150
heightDefault uint16 = 50

sizeDefault uint8 = 4

curveDefault uint8 = 1
curveGreyColorMinDefault uint8 = 1
curveGreyColorMaxDefault uint8 = 9
curveGreyColorMinInverse uint8 = 7
curveGreyColorMaxInverse uint8 = 15

paddingHorizontalDefault uint8 = 5

fillColorMinDefault uint8 = 0
fillColorMaxDefault uint8 = 4
fillColorMinInverse uint8 = 10
fillColorMaxInverse uint8 = 14
)
127 changes: 51 additions & 76 deletions draw.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,58 +2,43 @@ package captcha

import (
"fmt"
"log"
"math/rand"

"github.com/reu98/go-svg-captcha/fonts"
"github.com/tdewolff/canvas"
)

func loadFont() (*canvas.Font, error) {
fontFamily, err := canvas.LoadFont(fonts.Comismsh, 0, canvas.FontRegular)
if err != nil {
return nil, err
}

return fontFamily, nil
}

func (opt *option) drawText(text string) (string, error) {
width := widthDefault
if opt.Width != nil {
width = *opt.Width
}

height := heightDefault
if opt.Height != nil {
height = *opt.Height
}

isColor := false
if opt.IsColor != nil && *opt.IsColor {
isColor = true
}
defer func() {
if err := recover(); err != nil {
log.Fatalln(err)
}
}()

fillColorMin := fillColorMinDefault
fillColorMax := fillColorMaxDefault
if opt.IsInverse != nil && *opt.IsInverse {
if opt.isInverse {
fillColorMin = fillColorMinInverse
fillColorMax = fillColorMaxInverse
}

letterWidth := float32((width - uint16(paddingHorizontalDefault)*2)) / float32(len(text))
letterWidth := float32((opt.width - uint16(paddingHorizontalDefault)*2)) / float32(len(text))
result := ""
for index, char := range text {
var fill string
if isColor {
fill = randomColor(opt.BackgroundColor)
if opt.isColor {
fill = opt.randomColor()
} else {
min := uint8(fillColorMin)
max := uint8(fillColorMax)
fill = randomGreyColor(&min, &max)
}

minY := float32(opt.height) / 4
maxY := float32(opt.height) / 2
x := letterWidth*float32(index) + letterWidth/2
y := float32(height / 2)
y := minY + rand.Float32()*(maxY-minY)
d, err := opt.drawChar(char, x, y)
if err != nil {
return "", err
Expand All @@ -64,66 +49,64 @@ func (opt *option) drawText(text string) (string, error) {
return result, nil
}

func (opt *option) drawNoise() string {
var result string
totalNoise := (opt.width * opt.height) / 28
for i := 0; i < int(totalNoise); i++ {
x := rand.Intn(int(opt.width))
y := rand.Intn(int(opt.height))
color := randomColor()

result += fmt.Sprintf("<circle cx=\"%d\" cy=\"%d\" r=\"%d\" fill=\"%s\" />", x, y, noiseRadius, color)
}

return result
}

func (opt *option) drawChar(char rune, x float32, y float32) (string, error) {
fontFamily, err := loadFont()
fontFamily, err := loadFont(opt.fontPath)
if err != nil {
return "", err
}

fontSize := fontSizeDefault * ratioFontSize
if opt.FontSize != nil && *opt.FontSize > 0 {
fontSize = *opt.FontSize
}

face := fontFamily.Face(float64(fontSize), nil)

path, _, err := face.ToPath(string(char))
fontFace := fontFamily.Face(float64(opt.fontSize), nil)
path, _, err := fontFace.ToPath(string(char))
if err != nil {
return "", err
}

return randomTranslatePath(path, x, y), nil
}

func (opt *option) drawLineNoise() string {
index := 0
noise := noiseDefault
if opt.Noise != nil && *opt.Noise > noiseDefault {
noise = *opt.Noise
}
func loadFont(fontPath string) (*canvas.Font, error) {
if fontPath != "" {
fontFamily, err := canvas.LoadFontFile(fontPath, canvas.FontRegular)
if err != nil {
return nil, err
}

min := noiseGreyColorMinDefault
max := noiseGreyColorMaxDefault
if opt.IsInverse != nil && *opt.IsInverse {
min = noiseGreyColorMinInverse
max = noiseGreyColorMaxInverse
return fontFamily, nil
}

width := widthDefault
if opt.Width != nil {
width = *opt.Width
fontFamily, err := canvas.LoadFont(fonts.Comismsh, 0, canvas.FontRegular)
if err != nil {
return nil, err
}

height := heightDefault
if opt.Height != nil {
height = *opt.Height
}
return fontFamily, nil
}

result := ""
for uint8(index) < noise {
var stroke string
if opt.IsColor != nil && *opt.IsColor {
stroke = randomColor(opt.BackgroundColor)
} else {
stroke = randomGreyColor(&min, &max)
}
func (opt *option) drawCurve() string {
count := opt.curve
var result string
for i := 0; i < int(count); i++ {
stroke := opt.randomColor()
moveLine := fmt.Sprintf("%v %v", randomInt(1, 21), randomInt(1, uint16(opt.height)-1))
cubicStart := fmt.Sprintf("%v %v", randomInt(uint8(opt.width/2-21), uint16(opt.width/2+21)), randomInt(1, uint16(opt.height-1)))
cubicMid := fmt.Sprintf("%v %v", randomInt(uint8(opt.width/2-21), uint16(opt.width/2+21)), randomInt(1, uint16(opt.height-1)))
cubicEnd := fmt.Sprintf("%v %v", randomInt(uint8(opt.width-21), uint16(opt.width-1)), randomInt(1, uint16(opt.height-1)))

moveLine := fmt.Sprintf("%v %v", randomInt(1, 21), randomInt(1, uint16(height)-1))
cubicStart := fmt.Sprintf("%v %v", randomInt(uint8(width/2-21), uint16(width/2+21)), randomInt(1, uint16(height-1)))
cubicMid := fmt.Sprintf("%v %v", randomInt(uint8(width/2-21), uint16(width/2+21)), randomInt(1, uint16(height-1)))
cubicEnd := fmt.Sprintf("%v %v", randomInt(uint8(width-21), uint16(width-1)), randomInt(1, uint16(height-1)))
result += fmt.Sprintf("<path d=\"M%v C%v,%v,%v\" stroke=\"%v\" fill=\"none\"/> ", moveLine, cubicStart, cubicMid, cubicEnd, stroke)
index++
}

return result
Expand All @@ -145,14 +128,6 @@ func calculateRandomOffset(offset float32) float32 {
return offset - randomOffset()
}

func randomOperation() matchOperator {
if rand.Float32() < 0.5 {
return MathOperatorMinus
}

return MathOperatorPlus
}

func randomOffset() float32 {
return (rand.Float32() * 0.2) - 0.1
}
Loading

0 comments on commit 8cd2683

Please sign in to comment.