diff --git a/encoder.go b/encoder.go index 7ec38a5..1409bf7 100644 --- a/encoder.go +++ b/encoder.go @@ -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 @@ -314,3 +329,8 @@ func analyzeAlphaNum(byt byte) bool { } return false } + +//// analyzeByte is byt in bytes. +//func analyzeByte(byt byte) bool { +// return false +//} diff --git a/encoder_test.go b/encoder_test.go index 2a522c4..aad267f 100644 --- a/encoder_test.go +++ b/encoder_test.go @@ -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) } }) } diff --git a/example/main.go b/example/main.go index b3957f4..99b4fc1 100644 --- a/example/main.go +++ b/example/main.go @@ -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) } } diff --git a/image_option_api_test.go b/image_option_api_test.go index 61a745d..ea4d482 100644 --- a/image_option_api_test.go +++ b/image_option_api_test.go @@ -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) { diff --git a/matrix/matrix.go b/matrix/matrix.go index 520b2cf..504db71 100644 --- a/matrix/matrix.go +++ b/matrix/matrix.go @@ -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) diff --git a/qrcode.go b/qrcode.go index 564ae78..b5cfd9e 100644 --- a/qrcode.go +++ b/qrcode.go @@ -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 @@ -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 @@ -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) @@ -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) @@ -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++ } } } diff --git a/version.go b/version.go index d47787d..cc55d01 100644 --- a/version.go +++ b/version.go @@ -3,6 +3,7 @@ package qrcode import ( "errors" "log" + "strconv" // "github.com/skip2/go-qrcode/bitset" "github.com/yeqown/reedsolomon/binary" @@ -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` @@ -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 } } @@ -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{} @@ -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 {