Skip to content

Commit

Permalink
Merge pull request #18 from yeqown/fix/swedish-letter-17
Browse files Browse the repository at this point in the history
fix/swedish letter 17 #17
  • Loading branch information
yeqown authored Apr 6, 2021
2 parents 6efc0b3 + 7f8ccf5 commit e9ee406
Show file tree
Hide file tree
Showing 7 changed files with 168 additions and 71 deletions.
54 changes: 37 additions & 17 deletions encoder.go
Original file line number Diff line number Diff line change
Expand Up @@ -269,41 +269,56 @@ func encodeAlphanumericCharacter(v byte) uint32 {
return 0
}

// analyzeEncFunc returns true is current byte matched in current mode, otherwise means you should
// use a bigger character set to check.
type analyzeEncFunc func(byte) bool

// 如果输入字符串只包含数字(0-9),请使用数字编码模式。
// 在数字编码模式不适用的情况下,如果可以在字符索引表的左列中找到输入字符串中的所有字符,请使用字符编码模式。注意:小写字母不能使用字符编码模式。
// 在字符编码模式不适用的情况下,如果字符可以在ISO-8859-1字符集中找到,则使用字节编码模式。
func anlayzeMode(raw []byte) encMode {
// analyzeMode try to detect letter set of input data, so that encoder can determine which mode should be use.
// case1: only numbers, use encModeNumeric.
// case2: could not use encModeNumeric, but you can find all of them in character mapping, use encModeAlphanumeric.
// case3: could not use encModeAlphanumeric, but you can find all of them in ISO-8859-1 character set, use encModeByte.
// case4: could not use encModeByte, use encModeJP, no more choice.
//
// Links: https://en.wikipedia.org/wiki/QR_code Storage section.
func analyzeMode(raw []byte) encMode {
var (
analyFunc analyzeEncFunc = analyzeNum
encMode = encModeNumeric
analyzeFn analyzeEncFunc = analyzeNum
mode = encModeNumeric
)
// check

// loop to check each character in raw data,
// from low mode to higher while current mode could bearing the input data.
for _, byt := range raw {
switch encMode {
switch mode {
case encModeNumeric:
if !analyFunc(byt) {
encMode = encModeAlphanumeric
analyFunc = analyzeAlphaNum
if !analyzeFn(byt) {
mode = encModeAlphanumeric
analyzeFn = analyzeAlphaNum
}
case encModeAlphanumeric:
if !analyFunc(byt) {
encMode = encModeByte
if !analyzeFn(byt) {
mode = encModeByte
//analyzeFn = analyzeByte
}
case encModeByte:
return encModeByte
return mode
//if !analyzeFn(byt) {
// mode = encModeJP
//}
//case encModeJP:
// return mode
}
}
return encMode

return mode
}

// analyzeNum ... is byt in num encMode
// analyzeNum is byt in num encMode
func analyzeNum(byt byte) bool {
return byt >= '0' && byt <= '9'
}

// analyzeAlphaNum ... is byt in alpha number
// analyzeAlphaNum is byt in alpha number
func analyzeAlphaNum(byt byte) bool {
if (byt >= '0' && byt <= '9') || (byt >= 'A' && byt <= 'Z') {
return true
Expand All @@ -314,3 +329,8 @@ func analyzeAlphaNum(byt byte) bool {
}
return false
}

//// analyzeByte is byt in bytes.
//func analyzeByte(byt byte) bool {
// return false
//}
9 changes: 7 additions & 2 deletions encoder_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,11 +190,16 @@ func Test_anlayzeMode(t *testing.T) {
args: args{raw: []byte("这是汉字也应该是EncModeByte")},
want: encModeByte,
},
{
name: "case 6 (swedish letter)",
args: args{raw: []byte("Övrigt aksldjlk Övrigt should JP encMode?")},
want: encModeByte,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := anlayzeMode(tt.args.raw); got != tt.want {
t.Errorf("anlayzeMode() = %v, want %v", got, tt.want)
if got := analyzeMode(tt.args.raw); got != tt.want {
t.Errorf("analyzeMode() = %v, want %v", got, tt.want)
}
})
}
Expand Down
24 changes: 23 additions & 1 deletion example/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,35 @@ import (
)

func main() {
repo()

//issue17()
}

func repo() {
qrc, err := qrcode.New("https://github.com/yeqown/go-qrcode")
if err != nil {
fmt.Printf("could not generate QRCode: %v", err)
}

// save file
if err := qrc.Save("../testdata/repo-qrcode.jpeg"); err != nil {
if err = qrc.Save("../testdata/repo-qrcode.jpeg"); err != nil {
fmt.Printf("could not save image: %v", err)
}
}

func issue17() {
qrc, err := qrcode.New("Övrigt asdasd asdas djaskl djaslk djaslkj dlaiodqjwiodjaskldj aksldjlk Övrigt")
//qrc, err := qrcode.New("text content this is custom text content this is custom text content70123")
// content over than 74 length would trigger this
//qrc, err := qrcode.New("text content this is custom text content this is custom text content701234",
// qrcode.WithCircleShape())
if err != nil {
fmt.Printf("could not generate QRCode: %v", err)
}

// save file
if err = qrc.Save("./testdata/issue-17.jpeg"); err != nil {
fmt.Printf("could not save image: %v", err)
}
}
24 changes: 13 additions & 11 deletions image_option_api_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,21 +8,23 @@ import (
)

func Test_WithBuiltinImageEncoder(t *testing.T) {
oo := _defaultOutputOption

assert.IsType(t, oo.imageEncoder, jpegEncoder{})
WithBuiltinImageEncoder(JPEG_FORMAT).apply(oo)
assert.IsType(t, oo.imageEncoder, jpegEncoder{})
WithBuiltinImageEncoder(PNG_FORMAT).apply(oo)
assert.IsType(t, oo.imageEncoder, pngEncoder{})
oo := *_defaultOutputOption
oo2 := &oo

assert.IsType(t, jpegEncoder{}, oo2.imageEncoder)
WithBuiltinImageEncoder(JPEG_FORMAT).apply(oo2)
assert.IsType(t, jpegEncoder{}, oo2.imageEncoder)
WithBuiltinImageEncoder(PNG_FORMAT).apply(oo2)
assert.IsType(t, pngEncoder{}, oo2.imageEncoder)
}

func Test_WithCustomImageEncoder(t *testing.T) {
oo := _defaultOutputOption
oo := *_defaultOutputOption
oo2 := &oo

assert.IsType(t, oo.imageEncoder, jpegEncoder{})
WithCustomImageEncoder(nil).apply(oo)
assert.IsType(t, oo.imageEncoder, jpegEncoder{})
assert.IsType(t, jpegEncoder{}, oo2.imageEncoder)
WithCustomImageEncoder(nil).apply(oo2)
assert.IsType(t, jpegEncoder{}, oo2.imageEncoder)
}

func Test_BgColor_FgColor(t *testing.T) {
Expand Down
6 changes: 5 additions & 1 deletion matrix/matrix.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,13 +101,17 @@ func (m *Matrix) init() {
// Print to stdout
func (m *Matrix) print() {
m.Iterate(ROW, func(x, y int, s State) {
fmt.Printf("(%2d,%2d)%s ", x, y, s)
fmt.Printf("%6d ", s)
if (x + 1) == m.width {
fmt.Println()
}
})
}

func (m *Matrix) Print() {
m.print()
}

// Copy matrix into a new Matrix
func (m *Matrix) Copy() *Matrix {
newMat := make([][]State, m.width)
Expand Down
45 changes: 24 additions & 21 deletions qrcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ var (
_debug = false

// once to load versions config file
once sync.Once
//once sync.Once
)

// New generate a QRCode struct to create
Expand Down Expand Up @@ -90,12 +90,12 @@ type QRCode struct {
}

func (q *QRCode) init() error {
once.Do(func() {
// once load versions config file into memory
// if err := load(defaultVersionCfg); err != nil {
// panic(err)
// }
})
//once.Do(func() {
// once load versions config file into memory
// if err := load(defaultVersionCfg); err != nil {
// panic(err)
// }
//})
q.rawData = []byte(q.content)
if q.needAnalyze {
// analyze the input data to choose adapt version
Expand Down Expand Up @@ -151,7 +151,7 @@ func (q *QRCode) analyze() error {
q.ecLv = Quart

// choose encode mode (num, alpha num, byte, Japanese)
q.mode = anlayzeMode(q.rawData)
q.mode = analyzeMode(q.rawData)

// analyze content to decide version etc.
analyzedV, err := analyzeVersion(q.rawData, q.ecLv, q.mode)
Expand Down Expand Up @@ -479,7 +479,7 @@ func addDarkBlock(m *matrix.Matrix, x, y int) {

// reserveFormatBlock maintain the position in matrix for format info
func reserveFormatBlock(m *matrix.Matrix, dimension int) {
for pos := 0; pos < 9; pos++ {
for pos := 1; pos < 9; pos++ {
// skip timing line
if pos == 6 {
_ = m.Set(8, dimension-pos, matrix.StateFormat)
Expand Down Expand Up @@ -718,18 +718,21 @@ func (q *QRCode) xorMask(m *matrix.Matrix, mask *mask) {
// fillVersionInfo ref to:
// https://www.thonky.com/qr-code-tutorial/format-version-tables
func (q *QRCode) fillVersionInfo(m *matrix.Matrix, dimension int) {
verBSet := q.v.verInfo()
var mod3, mod6 int
for pos := 0; pos < 18; pos++ {
mod3 = pos % 3
mod6 = pos % 6

if verBSet.At(pos) {
_ = m.Set(mod6, dimension-12+mod3, matrix.StateTrue)
_ = m.Set(dimension-12+mod3, mod6, matrix.StateTrue)
} else {
_ = m.Set(mod6, dimension-12+mod3, matrix.StateFalse)
_ = m.Set(dimension-12+mod3, mod6, matrix.StateTrue)
bin := q.v.verInfo()

// from high bit to lowest
pos := 0
for j := 5; j >= 0; j-- {
for i := 1; i <= 3; i++ {
if bin.At(pos) {
_ = m.Set(dimension-8-i, j, matrix.StateTrue)
_ = m.Set(j, dimension-8-i, matrix.StateTrue)
} else {
_ = m.Set(dimension-8-i, j, matrix.StateFalse)
_ = m.Set(j, dimension-8-i, matrix.StateFalse)
}

pos++
}
}
}
Expand Down
77 changes: 59 additions & 18 deletions version.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package qrcode
import (
"errors"
"log"
"strconv"

// "github.com/skip2/go-qrcode/bitset"
"github.com/yeqown/reedsolomon/binary"
Expand Down Expand Up @@ -87,9 +88,9 @@ func init() {
// }
// }

for ver := 1; ver <= 40; ver++ {
loadAlignmentPatternLoc(ver)
}
//for ver := 1; ver <= 40; ver++ {
// loadAlignmentPatternLoc(ver)
//}
}

// load versionCfg.json (versions config file) into `[]versions`
Expand Down Expand Up @@ -235,27 +236,29 @@ func analyzeVersion(raw []byte, ecLv ecLevel, eMode encMode) (*version, error) {
if len(versions) == 0 {
panic("did not loaded the versions config success")
}

var (
// target version
lengthCnt = len(raw)
cap int
length = len(raw)
c int
)

for _, v := range versions {
if v.ECLevel == ecLv {
switch eMode {
case encModeNumeric:
cap = v.Cap.Byte
c = v.Cap.Numeric
case encModeAlphanumeric:
cap = v.Cap.Byte
c = v.Cap.AlphaNumeric
case encModeByte:
cap = v.Cap.Byte
c = v.Cap.Byte
case encModeJP:
cap = v.Cap.JP
c = v.Cap.JP
default:
return nil, errMissMatchedEncodeType
}
// cap bigger than data length
if cap > lengthCnt {
// c bigger than data length
if c > length {
return &v, nil
}
}
Expand All @@ -271,13 +274,48 @@ func analyzeVersion(raw []byte, ecLv ecLevel, eMode encMode) (*version, error) {
// }

var (
// TODO: append more version
// https://www.thonky.com/qr-code-tutorial/alignment-pattern-locations
// DONE(@yeqown): add more version
alignPatternLocation = map[int][]int{
2: {6, 18},
3: {6, 22},
4: {6, 26},
5: {6, 30},
6: {6, 34},
2: {6, 18},
3: {6, 22},
4: {6, 26},
5: {6, 30},
6: {6, 34},
7: {6, 22, 38},
8: {6, 24, 42},
9: {6, 26, 46},
10: {6, 28, 50},
11: {6, 30, 54},
12: {6, 32, 58},
13: {6, 34, 62},
14: {6, 26, 46, 66},
15: {6, 26, 48, 70},
16: {6, 26, 50, 74},
17: {6, 30, 54, 78},
18: {6, 30, 56, 82},
19: {6, 30, 58, 86},
20: {6, 34, 62, 90},
21: {6, 28, 50, 72, 94},
22: {6, 26, 50, 74, 98},
23: {6, 30, 54, 78, 102},
24: {6, 28, 54, 80, 106},
25: {6, 32, 58, 84, 110},
26: {6, 30, 58, 86, 114},
27: {6, 34, 62, 90, 118},
28: {6, 26, 50, 74, 98, 122},
29: {6, 30, 54, 78, 102, 126},
30: {6, 26, 52, 78, 104, 130},
31: {6, 30, 56, 82, 108, 134},
32: {6, 34, 60, 86, 112, 138},
33: {6, 30, 58, 86, 114, 142},
34: {6, 34, 62, 90, 118, 146},
35: {6, 30, 54, 78, 102, 126, 150},
36: {6, 24, 50, 76, 102, 128, 154},
37: {6, 28, 54, 80, 106, 132, 158},
38: {6, 32, 58, 84, 110, 136, 162},
39: {6, 26, 54, 82, 110, 138, 166},
40: {6, 30, 58, 86, 114, 142, 170},
}

alignPatternCache = map[int][]loc{}
Expand All @@ -300,7 +338,10 @@ func loadAlignmentPatternLoc(ver int) (locs []loc) {
}

dimension := ver*4 + 17
positions := alignPatternLocation[ver]
positions, ok := alignPatternLocation[ver]
if !ok {
panic("could not found align at version: " + strconv.Itoa(ver))
}

for _, pos1 := range positions {
for _, pos2 := range positions {
Expand Down

0 comments on commit e9ee406

Please sign in to comment.