Compare commits

..

No commits in common. "main" and "v1.3.1" have entirely different histories.
main ... v1.3.1

11 changed files with 5142 additions and 79 deletions

24
.github/workflows/go.yml vendored Normal file
View file

@ -0,0 +1,24 @@
name: CI
on: [push, pull_request]
jobs:
build:
name: Build
runs-on: ubuntu-latest
steps:
- name: Set up Golang
uses: actions/setup-go@v2
with:
go-version: '^1.13.1'
- run: go version
- name: Check out code into the Go module directory
uses: actions/checkout@v2
- name: install dependencies
run: go mod download
- name: run test
run: go test -race -coverprofile=coverage.txt -covermode=atomic
- name: upload coverage
run: bash <(curl -s https://codecov.io/bash)

View file

@ -1,6 +1,6 @@
MIT License MIT License
Copyright (c) 2017 - 2024 Weilin Shi Copyright (c) 2017 - 2020 Weilin Shi
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View file

@ -2,17 +2,19 @@
<div> <div>
[![PkgGoDev](https://pkg.go.dev/badge/concord.hectabit.org/HectaBit/captcha)](https://pkg.go.dev/concord.hectabit.org/HectaBit/captcha) [![PkgGoDev](https://pkg.go.dev/badge/steambap/captcha)](https://pkg.go.dev/steambap/captcha)
[![Go Report Card](https://goreportcard.com/badge/concord.hectabit.org/HectaBit/captcha)](https://goreportcard.com/report/concord.hectabit.org/HectaBit/captcha) [![Build Status](https://github.com/steambap/captcha/workflows/CI/badge.svg)](https://github.com/steambap/captcha/actions?workflow=CI)
[![codecov](https://codecov.io/gh/steambap/captcha/branch/main/graph/badge.svg)](https://codecov.io/gh/steambap/captcha)
[![Go Report Card](https://goreportcard.com/badge/github.com/steambap/captcha)](https://goreportcard.com/report/github.com/steambap/captcha)
</div> </div>
## Why another captcha generator? ## Why another captcha generator?
Because I can. I want a simple and framework-independent way to generate captcha. It also should be flexible, at least allow me to pick my favorite font.
## install ## install
``` ```
go get concord.hectabit.org/HectaBit/captcha go get github.com/steambap/captcha
``` ```
## usage ## usage
@ -30,19 +32,14 @@ func handle(w http.ResponseWriter, r *http.Request) {
``` ```
[documentation](https://pkg.go.dev/concord.hectabit.org/HectaBit/captcha) | [documentation](https://godoc.org/github.com/steambap/captcha) |
[example](example/basic/main.go) | [example](example/basic/main.go)
[font example](example/load-font/main.go)
## sample image ## sample image
![image](example/captcha.png) ![image](example/captcha.png)
![image](example/captcha-math.png) ![image](example/captcha-math.png)
## Compatibility
This package uses embed package from Go 1.16. If for some reasons you have to use pre 1.16 version of Go, reference pre 1.4 version of this module in your go.mod.
## Contributing ## Contributing
If your found a bug, please contribute! If your found a bug, please contribute!
see [contributing.md](contributing.md) for more detail. see [contributing.md](contributing.md) for more detail.

View file

@ -2,7 +2,7 @@
package captcha package captcha
import ( import (
_ "embed" // embed font "bytes"
"image" "image"
"image/color" "image/color"
"image/draw" "image/draw"
@ -22,8 +22,6 @@ import (
const charPreset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789" const charPreset = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"
//go:embed fonts/Comismsh.ttf
var ttf []byte
var ttfFont *truetype.Font var ttfFont *truetype.Font
// Options manage captcha generation details. // Options manage captcha generation details.
@ -62,7 +60,7 @@ func newDefaultOption(width, height int) *Options {
return &Options{ return &Options{
BackgroundColor: color.Transparent, BackgroundColor: color.Transparent,
CharPreset: charPreset, CharPreset: charPreset,
TextLength: 6, TextLength: 4,
CurveNumber: 2, CurveNumber: 2,
FontDPI: 72.0, FontDPI: 72.0,
FontScale: 1.0, FontScale: 1.0,
@ -118,12 +116,12 @@ func LoadFont(fontData []byte) error {
// LoadFontFromReader load an external font from an io.Reader interface. // LoadFontFromReader load an external font from an io.Reader interface.
func LoadFontFromReader(reader io.Reader) error { func LoadFontFromReader(reader io.Reader) error {
b, err := io.ReadAll(reader) var buf bytes.Buffer
if err != nil { if _, err := io.Copy(&buf, reader); err != nil {
return err return err
} }
return LoadFont(b) return LoadFont(buf.Bytes())
} }
// New creates a new captcha. // New creates a new captcha.
@ -136,7 +134,11 @@ func New(width int, height int, option ...SetOption) (*Data, error) {
text := randomText(options) text := randomText(options)
img := image.NewNRGBA(image.Rect(0, 0, width, height)) img := image.NewNRGBA(image.Rect(0, 0, width, height))
if err := drawWithOption(text, img, options); err != nil { draw.Draw(img, img.Bounds(), &image.Uniform{options.BackgroundColor}, image.ZP, draw.Src)
drawNoise(img, options)
drawCurves(img, options)
err := drawText(text, img, options)
if err != nil {
return nil, err return nil, err
} }
@ -153,42 +155,21 @@ func NewMathExpr(width int, height int, option ...SetOption) (*Data, error) {
text, equation := randomEquation() text, equation := randomEquation()
img := image.NewNRGBA(image.Rect(0, 0, width, height)) img := image.NewNRGBA(image.Rect(0, 0, width, height))
if err := drawWithOption(equation, img, options); err != nil { draw.Draw(img, img.Bounds(), &image.Uniform{options.BackgroundColor}, image.ZP, draw.Src)
drawNoise(img, options)
drawCurves(img, options)
err := drawText(equation, img, options)
if err != nil {
return nil, err return nil, err
} }
return &Data{Text: text, img: img}, nil return &Data{Text: text, img: img}, nil
} }
// NewCustomGenerator creates a new captcha based on a custom text generator.
func NewCustomGenerator(
width int, height int, generator func() (anwser string, question string), option ...SetOption,
) (*Data, error) {
options := newDefaultOption(width, height)
for _, setOption := range option {
setOption(options)
}
answer, question := generator()
img := image.NewNRGBA(image.Rect(0, 0, width, height))
if err := drawWithOption(question, img, options); err != nil {
return nil, err
}
return &Data{Text: answer, img: img}, nil
}
func drawWithOption(text string, img *image.NRGBA, options *Options) error {
draw.Draw(img, img.Bounds(), &image.Uniform{options.BackgroundColor}, image.Point{}, draw.Src)
drawNoise(img, options)
drawCurves(img, options)
return drawText(text, img, options)
}
func randomText(opts *Options) (text string) { func randomText(opts *Options) (text string) {
n := len([]rune(opts.CharPreset)) n := len(opts.CharPreset)
for i := 0; i < opts.TextLength; i++ { for i := 0; i < opts.TextLength; i++ {
text += string([]rune(opts.CharPreset)[rand.Intn(n)]) text += string(opts.CharPreset[rand.Intn(n)])
} }
return text return text

View file

@ -69,12 +69,6 @@ func TestNewCaptchaOptions(t *testing.T) {
NewMathExpr(100, 34, func(options *Options) { NewMathExpr(100, 34, func(options *Options) {
options.BackgroundColor = color.Black options.BackgroundColor = color.Black
}) })
NewCustomGenerator(100, 34, func() (anwser string, question string) {
return "4", "2x2?"
}, func(o *Options) {
o.BackgroundColor = color.Black
})
} }
func TestNewMathExpr(t *testing.T) { func TestNewMathExpr(t *testing.T) {
@ -84,15 +78,6 @@ func TestNewMathExpr(t *testing.T) {
} }
} }
func TestNewCustomGenerator(t *testing.T) {
_, err := NewCustomGenerator(150, 50, func() (anwser string, question string) {
return "1", "2"
})
if err != nil {
t.Fatal(err)
}
}
func TestNilFontError(t *testing.T) { func TestNilFontError(t *testing.T) {
temp := ttfFont temp := ttfFont
ttfFont = nil ttfFont = nil
@ -107,13 +92,6 @@ func TestNilFontError(t *testing.T) {
t.Fatal("Expect to get nil font error") t.Fatal("Expect to get nil font error")
} }
_, err = NewCustomGenerator(150, 50, func() (anwser string, question string) {
return "1", "2"
})
if err == nil {
t.Fatal("Expect to get nil font error")
}
ttfFont = temp ttfFont = temp
} }

View file

@ -1 +1 @@
[https://www.contributor-covenant.org/version/2/1/code_of_conduct/](https://www.contributor-covenant.org/version/2/1/code_of_conduct/) [https://contributor-covenant.org/version/1/4/](https://contributor-covenant.org/version/1/4/)

View file

@ -1,7 +1,7 @@
module concord.hectabit.org/HectaBit/captcha/example/basic module github.com/steambap/captcha/example/basic
go 1.12 go 1.12
replace concord.hectabit.org/HectaBit/captcha => ../../ replace github.com/steambap/captcha => ../../
require concord.hectabit.org/HectaBit/captcha v0.0.0-00010101000000-000000000000 require github.com/steambap/captcha v0.0.0-00010101000000-000000000000

5016
font.go Normal file

File diff suppressed because it is too large Load diff

40
fonts/gen.go Normal file
View file

@ -0,0 +1,40 @@
package main
import (
"bytes"
"fmt"
"go/format"
"io/ioutil"
"log"
"path/filepath"
)
// This program generates a go file for Comismsh font
func main() {
src, err := ioutil.ReadFile("Comismsh.ttf")
if err != nil {
log.Fatal(err)
}
buf := new(bytes.Buffer)
fmt.Fprint(buf, "// DO NOT EDIT. This file is generated.\n\n")
fmt.Fprint(buf, "package captcha\n\n")
fmt.Fprint(buf, "// The following is Comismsh TrueType font data.\n")
fmt.Fprint(buf, "var ttf = []byte{")
for i, x := range src {
if i&15 == 0 {
buf.WriteByte('\n')
}
fmt.Fprintf(buf, "%#02x,", x)
}
fmt.Fprint(buf, "\n}\n")
dst, err := format.Source(buf.Bytes())
if err != nil {
log.Fatal(err)
}
if err := ioutil.WriteFile(filepath.Join("../", "font.go"), dst, 0666); err != nil {
log.Fatal(err)
}
}

13
go.mod
View file

@ -1,8 +1,15 @@
module concord.hectabit.org/HectaBit/captcha module github.com/steambap/captcha
go 1.20 go 1.12
require ( require (
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0
golang.org/x/image v0.15.0 golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f // indirect
golang.org/x/image v0.0.0-20200618115811-c13761719519
golang.org/x/net v0.0.0-20191119073136-fc4aabc6c914 // indirect
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e // indirect
golang.org/x/sys v0.0.0-20191119060738-e882bf8e40c2 // indirect
golang.org/x/text v0.3.2 // indirect
golang.org/x/tools v0.0.0-20191118222007-07fc4c7f2b98 // indirect
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898 // indirect
) )

24
go.sum
View file

@ -1,4 +1,24 @@
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g=
github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k=
golang.org/x/image v0.15.0 h1:kOELfmgrmJlw4Cdb7g/QGuB3CvDrXbqEIww/pNtNBm8= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/image v0.15.0/go.mod h1:HUYqC05R2ZcZ3ejNQsIHQDQiwWM4JBqmm6MKANTp4LE= golang.org/x/crypto v0.0.0-20191117063200-497ca9f6d64f/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/image v0.0.0-20190507092727-e4e5bf290fec h1:arXJwtMuk5vqI1NHX0UTnNw977rYk5Sl4jQqHj+hun4=
golang.org/x/image v0.0.0-20190507092727-e4e5bf290fec/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8 h1:hVwzHzIUGRjiF7EcUjqNxk3NCfkPxbDKRdnNE1Rpg0U=
golang.org/x/image v0.0.0-20191009234506-e7c1f5e7dbb8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/image v0.0.0-20200618115811-c13761719519 h1:1e2ufUJNM3lCHEY5jIgac/7UTjd6cgJNdatjPdFWf34=
golang.org/x/image v0.0.0-20200618115811-c13761719519/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20191119073136-fc4aabc6c914/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191119060738-e882bf8e40c2/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/tools v0.0.0-20191118222007-07fc4c7f2b98/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=