diff --git a/README.md b/README.md index cab9c09..fd1643e 100644 --- a/README.md +++ b/README.md @@ -70,8 +70,8 @@ const ( EncModeAlphanumeric // EncModeByte mode ... EncModeByte - // EncModeJP mode ... - EncModeJP + // EncModeKanji mode ... + EncModeKanji ) // WithEncodingMode sets the encoding mode. diff --git a/chardet.go b/chardet.go new file mode 100644 index 0000000..7a54710 --- /dev/null +++ b/chardet.go @@ -0,0 +1,253 @@ +package qrcode + +import ( + "errors" +) + +func init() { + restoreKanJi() +} + +var ( + ErrNotSupportCharacter = errors.New("character set not supported, please check your input data") +) + +// chardet.go refer to https://github.com/chardet/chardet to detect input string's +// character set, to see any unsupported character encountered in the input string. + +// analyzeEncFunc returns true is current byte matched in current mode, +// otherwise means you should use a bigger character set to check. +type analyzeEncFunc func(rune) bool + +// analyzeEncodeModeFromRaw try to detect letter set of input data, +// so that encoder can determine which mode should be use. +// reference: https://en.wikipedia.org/wiki/QR_code +// +// case1: only numbers, use EncModeNumeric. +// case2: could not use EncModeNumeric, but can find them all in character mapping, use EncModeAlphanumeric. +// case3: could not use EncModeAlphanumeric, but can find them all Shift JIS character set, use EncModeKanji. +// case4: could not use EncModeKanji, use EncModeByte. +func analyzeEncodeModeFromRaw(raw string) (encMode, error) { + var ( + analyzeFn analyzeEncFunc + mode = EncModeNone + ) + + getNextAnalyzeFn := func() analyzeEncFunc { + switch mode { + case EncModeNumeric: + return analyzeNum + case EncModeAlphanumeric: + return analyzeAlphaNum + case EncModeKanji: + return analyzeJP + case EncModeByte: + return analyzeByte + default: + } + + return nil + } + + next := func() bool { + // switch to next mode and get next analyze function. if no more analyze function, return true. + mode <<= 1 + analyzeFn = getNextAnalyzeFn() + return analyzeFn == nil + } + + next() + + // Loop to check each character in raw data, + // from low mode to higher while current mode could bear the input data. + for _, r := range raw { + reAnalyze: + // issue#28 @borislavone reports this bug. + // FIXED(@yeqown): next encMode analyzeVersionAuto func did not check the previous byte, + // add goto statement to reanalyze previous byte which can't be analyzed in last encMode. + if pass := analyzeFn(r); pass { + continue + } + + if nomore := next(); nomore { + break + } + + goto reAnalyze + } + + if mode > EncModeByte { + // If the mode overflow the EncModeKanji, means we can't encode the input data. + return EncModeNone, ErrNotSupportCharacter + } + + return mode, nil +} + +// analyzeNum is r in num encMode +func analyzeNum(r rune) bool { + return r >= '0' && r <= '9' +} + +// analyzeAlphaNum is r in alpha number +func analyzeAlphaNum(r rune) bool { + if (r >= '0' && r <= '9') || (r >= 'A' && r <= 'Z') { + return true + } + switch r { + case ' ', '$', '%', '*', '+', '-', '.', '/', ':': + return true + } + return false +} + +// analyzeByte always return true, since byte (utf8) mode can encode all characters. +func analyzeByte(r rune) bool { + return true +} + +// analyzeJP contains Kanji character set +// http://www.rikai.com/library/kanjitables/kanji_codes.sjis.shtml +func analyzeJP(r rune) bool { + _, ok := __UNICODE_TO_QR_KANJI[uint32(r)] + return ok +} + +var ( + __UNICODE_TO_QR_KANJI map[uint32]struct{} +) + +func restoreKanJi() { + // private static short[] UNICODE_TO_QR_KANJI = new short[1 << 16]; + + // Arrays.fill(UNICODE_TO_QR_KANJI, (short)-1); + // byte[] bytes = Base64.getDecoder().decode(PACKED_QR_KANJI_TO_UNICODE); + // for (int i = 0; i < bytes.length; i += 2) { + // char c = (char)(((bytes[i] & 0xFF) << 8) | (bytes[i + 1] & 0xFF)); + // if (c == 0xFFFF) + // continue; + // assert UNICODE_TO_QR_KANJI[c] == -1; + // UNICODE_TO_QR_KANJI[c] = (short)(i / 2); + // } + + __UNICODE_TO_QR_KANJI = make(map[uint32]struct{}, 1<<16) + // restore from __PACKED_QR_KANJI_TO_UNICODE + for i := 0; i < len(__PACKED_QR_KANJI_TO_UNICODE); i += 2 { + c := (uint32(__PACKED_QR_KANJI_TO_UNICODE[i]) << 8) | uint32(__PACKED_QR_KANJI_TO_UNICODE[i+1]) + if c == 0xFFFF { + continue + } + __UNICODE_TO_QR_KANJI[c] = struct{}{} + } +} + +var __PACKED_QR_KANJI_TO_UNICODE = "MAAwATAC/wz/DjD7/xr/G/8f/wEwmzCcALT/QACo/z7/4/8/MP0w/jCdMJ4wA07dMAUwBjAHMPwgFSAQ/w8AXDAcIBb/XCAmICUgGCAZIBwgHf8I/wkwFDAV/zv/Pf9b/10wCDAJMAowCzAMMA0wDjAPMBAwEf8LIhIAsQDX//8A9/8dImD/HP8eImYiZyIeIjQmQiZA" + + "ALAgMiAzIQP/5f8EAKIAo/8F/wP/Bv8K/yAApyYGJgUlyyXPJc4lxyXGJaEloCWzJbIlvSW8IDswEiGSIZAhkSGTMBP/////////////////////////////IggiCyKGIocigiKDIioiKf////////////////////8iJyIoAKwh0iHUIgAiA///////////////////" + + "//////////8iICKlIxIiAiIHImEiUiJqImsiGiI9Ih0iNSIrIiz//////////////////yErIDAmbyZtJmogICAhALb//////////yXv/////////////////////////////////////////////////xD/Ef8S/xP/FP8V/xb/F/8Y/xn///////////////////8h" + + "/yL/I/8k/yX/Jv8n/yj/Kf8q/yv/LP8t/y7/L/8w/zH/Mv8z/zT/Nf82/zf/OP85/zr///////////////////9B/0L/Q/9E/0X/Rv9H/0j/Sf9K/0v/TP9N/07/T/9Q/1H/Uv9T/1T/Vf9W/1f/WP9Z/1r//////////zBBMEIwQzBEMEUwRjBHMEgwSTBKMEswTDBN" + + "ME4wTzBQMFEwUjBTMFQwVTBWMFcwWDBZMFowWzBcMF0wXjBfMGAwYTBiMGMwZDBlMGYwZzBoMGkwajBrMGwwbTBuMG8wcDBxMHIwczB0MHUwdjB3MHgweTB6MHswfDB9MH4wfzCAMIEwgjCDMIQwhTCGMIcwiDCJMIowizCMMI0wjjCPMJAwkTCSMJP/////////////" + + "////////////////////////MKEwojCjMKQwpTCmMKcwqDCpMKowqzCsMK0wrjCvMLAwsTCyMLMwtDC1MLYwtzC4MLkwujC7MLwwvTC+ML8wwDDBMMIwwzDEMMUwxjDHMMgwyTDKMMswzDDNMM4wzzDQMNEw0jDTMNQw1TDWMNcw2DDZMNow2zDcMN0w3jDf//8w4DDh" + + "MOIw4zDkMOUw5jDnMOgw6TDqMOsw7DDtMO4w7zDwMPEw8jDzMPQw9TD2/////////////////////wORA5IDkwOUA5UDlgOXA5gDmQOaA5sDnAOdA54DnwOgA6EDowOkA6UDpgOnA6gDqf////////////////////8DsQOyA7MDtAO1A7YDtwO4A7kDugO7A7wDvQO+" + + "A78DwAPBA8MDxAPFA8YDxwPIA8n/////////////////////////////////////////////////////////////////////////////////////////////////////////////BBAEEQQSBBMEFAQVBAEEFgQXBBgEGQQaBBsEHAQdBB4EHwQgBCEEIgQjBCQEJQQm" + + "BCcEKAQpBCoEKwQsBC0ELgQv////////////////////////////////////////BDAEMQQyBDMENAQ1BFEENgQ3BDgEOQQ6BDsEPAQ9//8EPgQ/BEAEQQRCBEMERARFBEYERwRIBEkESgRLBEwETQROBE///////////////////////////////////yUAJQIlDCUQ" + + "JRglFCUcJSwlJCU0JTwlASUDJQ8lEyUbJRclIyUzJSslOyVLJSAlLyUoJTclPyUdJTAlJSU4JUL/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + + "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + + "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + + "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + + "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + + "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + + "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + + "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + + "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + + "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + + "/////////////////////////////////////06cVRZaA5Y/VMBhG2MoWfaQIoR1gxx6UGCqY+FuJWXthGaCppv1aJNXJ2WhYnFbm1nQhnuY9H1ifb6bjmIWfJ+It1uJXrVjCWaXaEiVx5eNZ09O5U8KT01PnVBJVvJZN1nUWgFcCWDfYQ9hcGYTaQVwunVPdXB5+32t" + + "fe+Aw4QOiGOLApBVkHpTO06VTqVX34CykMF4704AWPFuopA4ejKDKIKLnC9RQVNwVL1U4VbgWftfFZjybeuA5IUt////////lmKWcJagl/tUC1PzW4dwz3+9j8KW6FNvnVx6uk4ReJOB/G4mVhhVBGsdhRqcO1nlU6ltZnTclY9WQk6RkEuW8oNPmQxT4VW2WzBfcWYg" + + "ZvNoBGw4bPNtKXRbdsh6Tpg0gvGIW4pgku1tsnWrdsqZxWCmiwGNipWyaY5TrVGG//9XElgwWURbtF72YChjqWP0bL9vFHCOcRRxWXHVcz9+AYJ2gtGFl5BgkludG1hpZbxsWnUlUflZLlllX4Bf3GK8ZfpqKmsna7Rzi3/BiVadLJ0OnsRcoWyWg3tRBFxLYbaBxmh2" + + "cmFOWU/6U3hgaW4pek+X804LUxZO7k9VTz1PoU9zUqBT71YJWQ9awVu2W+F50WaHZ5xntmtMbLNwa3PCeY15vno8e4eCsYLbgwSDd4Pvg9OHZoqyVimMqI/mkE6XHoaKT8Rc6GIRcll1O4Hlgr2G/ozAlsWZE5nVTstPGonjVt5YSljKXvtf62AqYJRgYmHQYhJi0GU5" + + "////////m0FmZmiwbXdwcHVMdoZ9dYKlh/mVi5aOjJ1R8VK+WRZUs1uzXRZhaGmCba94jYTLiFeKcpOnmrhtbJmohtlXo2f/hs6SDlKDVodUBF7TYuFkuWg8aDhru3NyeLp6a4maidKNa48DkO2Vo5aUl2lbZlyzaX2YTZhOY5t7IGor//9qf2i2nA1vX1JyVZ1gcGLs" + + "bTtuB27RhFuJEI9EThScOVP2aRtqOpeEaCpRXHrDhLKR3JOMVludKGgigwWEMXylUgiCxXTmTn5Pg1GgW9JSClLYUudd+1WaWCpZ5luMW5hb215yXnlgo2EfYWNhvmPbZWJn0WhTaPprPmtTbFdvIm+Xb0V0sHUYduN3C3r/e6F8IX3pfzZ/8ICdgmaDnomzisyMq5CE" + + "lFGVk5WRlaKWZZfTmSiCGE44VCtcuF3Mc6l2THc8XKl/640LlsGYEZhUmFhPAU8OU3FVnFZoV/pZR1sJW8RckF4MXn5fzGPuZzpl12XiZx9oy2jE////////al9eMGvFbBdsfXV/eUhbY3oAfQBfvYmPihiMtI13jsyPHZjimg6bPE6AUH1RAFmTW5xiL2KAZOxrOnKg" + + "dZF5R3+ph/uKvItwY6yDypegVAlUA1WraFRqWIpweCdndZ7NU3RbooEahlCQBk4YTkVOx08RU8pUOFuuXxNgJWVR//9nPWxCbHJs43B4dAN6dnquewh9Gnz+fWZl53JbU7tcRV3oYtJi4GMZbiCGWooxjd2S+G8BeaabWk6oTqtOrE+bT6BQ0VFHevZRcVH2U1RTIVN/" + + "U+tVrFiDXOFfN19KYC9gUGBtYx9lWWpLbMFywnLtd++A+IEFggiFTpD3k+GX/5lXmlpO8FHdXC1mgWltXEBm8ml1c4loUHyBUMVS5FdHXf6TJmWkayNrPXQ0eYF5vXtLfcqCuYPMiH+JX4s5j9GR0VQfkoBOXVA2U+VTOnLXc5Z36YLmjq+ZxpnImdJRd2Eahl5VsHp6" + + "UHZb05BHloVOMmrbkedcUVxI////////Y5h6n2yTl3SPYXqqcYqWiHyCaBd+cGhRk2xS8lQbhauKE3+kjs2Q4VNmiIh5QU/CUL5SEVFEVVNXLXPqV4tZUV9iX4RgdWF2YWdhqWOyZDplbGZvaEJuE3Vmej18+31MfZl+S39rgw6DSobNigiKY4tmjv2YGp2PgriPzpvo" + + "//9Sh2IfZINvwJaZaEFQkWsgbHpvVHp0fVCIQIojZwhO9lA5UCZQZVF8UjhSY1WnVw9YBVrMXvphsmH4YvNjcmkcailyfXKscy54FHhvfXl3DICpiYuLGYzijtKQY5N1lnqYVZoTnnhRQ1OfU7Nee18mbhtukHOEc/59Q4I3igCK+pZQTk5QC1PkVHxW+lnRW2Rd8V6r" + + "XydiOGVFZ69uVnLQfMqItIChgOGD8IZOioeN6JI3lseYZ58TTpROkk8NU0hUSVQ+Wi9fjF+hYJ9op2qOdFp4gYqeiqSLd5GQTl6byU6kT3xPr1AZUBZRSVFsUp9SuVL+U5pT41QR////////VA5ViVdRV6JZfVtUW11bj13lXedd9154XoNeml63XxhgUmFMYpdi2GOn" + + "ZTtmAmZDZvRnbWghaJdpy2xfbSptaW4vbp11MnaHeGx6P3zgfQV9GH1efbGAFYADgK+AsYFUgY+CKoNSiEyIYYsbjKKM/JDKkXWScXg/kvyVpJZN//+YBZmZmtidO1JbUqtT91QIWNVi92/gjGqPX565UUtSO1RKVv16QJF3nWCe0nNEbwmBcHURX/1g2pqoctuPvGtk" + + "mANOylbwV2RYvlpaYGhhx2YPZgZoOWixbfd11X06gm6bQk6bT1BTyVUGXW9d5l3uZ/tsmXRzeAKKUJOWiN9XUF6nYytQtVCsUY1nAFTJWF5Zu1uwX2liTWOhaD1rc24IcH2Rx3KAeBV4JnltZY59MIPciMGPCZabUmRXKGdQf2qMoVG0V0KWKlg6aYqAtFSyXQ5X/HiV" + + "nfpPXFJKVItkPmYoZxRn9XqEe1Z9IpMvaFybrXs5UxlRilI3////////W99i9mSuZOZnLWu6hamW0XaQm9ZjTJMGm6t2v2ZSTglQmFPCXHFg6GSSZWNoX3Hmc8p1I3uXfoKGlYuDjNuReJkQZaxmq2uLTtVO1E86T39SOlP4U/JV41bbWOtZy1nJWf9bUFxNXgJeK1/X" + + "YB1jB2UvW1xlr2W9ZehnnWti//9re2wPc0V5SXnBfPh9GX0rgKKBAoHziZaKXoppimaKjIrujMeM3JbMmPxrb06LTzxPjVFQW1db+mFIYwFmQmshbstsu3I+dL111HjBeTqADIAzgeqElI+ebFCef18Pi1idK3r6jvhbjZbrTgNT8Vf3WTFayVukYIluf28Gdb6M6luf" + + "hQB74FByZ/SCnVxhhUp+HoIOUZlcBGNojWZlnHFueT59F4AFix2OypBuhseQqlAfUvpcOmdTcHxyNZFMkciTK4LlW8JfMWD5TjtT1luIYktnMWuKculz4HougWuNo5FSmZZRElPXVGpb/2OIajl9rJcAVtpTzlRo////////W5dcMV3eT+5hAWL+bTJ5wHnLfUJ+TX/S" + + "ge2CH4SQiEaJcouQjnSPL5AxkUuRbJbGkZxOwE9PUUVTQV+TYg5n1GxBbgtzY34mkc2Sg1PUWRlbv23ReV1+LnybWH5xn1H6iFOP8E/KXPtmJXeseuOCHJn/UcZfqmXsaW9riW3z//9ulm9kdv59FF3hkHWRh5gGUeZSHWJAZpFm2W4aXrZ90n9yZviFr4X3ivhSqVPZ" + + "WXNej1+QYFWS5JZkULdRH1LdUyBTR1PsVOhVRlUxVhdZaFm+WjxbtVwGXA9cEVwaXoReil7gX3Bif2KEYttjjGN3ZgdmDGYtZnZnfmiiah9qNWy8bYhuCW5YcTxxJnFndcd3AXhdeQF5ZXnweuB7EXynfTmAloPWhIuFSYhdiPOKH4o8ilSKc4xhjN6RpJJmk36UGJac" + + "l5hOCk4ITh5OV1GXUnBXzlg0WMxbIl44YMVk/mdhZ1ZtRHK2dXN6Y4S4i3KRuJMgVjFX9Jj+////////Yu1pDWuWce1+VIB3gnKJ5pjfh1WPsVw7TzhP4U+1VQdaIFvdW+lfw2FOYy9lsGZLaO5pm214bfF1M3W5dx95XnnmfTOB44KvhaqJqoo6jquPm5Aykd2XB066" + + "TsFSA1h1WOxcC3UaXD2BTooKj8WWY5dteyWKz5gIkWJW81Oo//+QF1Q5V4JeJWOobDRwindhfIt/4IhwkEKRVJMQkxiWj3RemsRdB11pZXBnoo2olttjbmdJaRmDxZgXlsCI/m+EZHpb+E4WcCx1XWYvUcRSNlLiWdNfgWAnYhBlP2V0Zh9mdGjyaBZrY24FcnJ1H3bb" + + "fL6AVljwiP2Jf4qgipOKy5AdkZKXUpdZZYl6DoEGlrteLWDcYhplpWYUZ5B383pNfE1+PoEKjKyNZI3hjl94qVIHYtljpWRCYpiKLXqDe8CKrJbqfXaCDIdJTtlRSFNDU2Bbo1wCXBZd3WImYkdksGgTaDRsyW1FbRdn029ccU5xfWXLen97rX3a////////fkp/qIF6" + + "ghuCOYWmim6Mzo31kHiQd5KtkpGVg5uuUk1VhG84cTZRaHmFflWBs3zOVkxYUVyoY6pm/mb9aVpy2XWPdY55DnlWed98l30gfUSGB4o0ljuQYZ8gUOdSdVPMU+JQCVWqWO5ZT3I9W4tcZFMdYONg82NcY4NjP2O7//9kzWXpZvld42nNaf1vFXHlTol16Xb4epN8333P" + + "fZyAYYNJg1iEbIS8hfuIxY1wkAGQbZOXlxyaElDPWJdhjoHThTWNCJAgT8NQdFJHU3Ngb2NJZ19uLI2zkB9P11xejMplz32aU1KIllF2Y8NbWFtrXApkDWdRkFxO1lkaWSpscIpRVT5YFVmlYPBiU2fBgjVpVZZAmcSaKE9TWAZb/oAQXLFeL1+FYCBhS2I0Zv9s8G7e" + + "gM6Bf4LUiIuMuJAAkC6Wip7bm9tO41PwWSd7LJGNmEyd+W7dcCdTU1VEW4ViWGKeYtNsom/vdCKKF5Q4b8GK/oM4UeeG+FPq////////U+lPRpBUj7BZaoExXf166o+/aNqMN3L4nEhqPYqwTjlTWFYGV2ZixWOiZeZrTm3hbltwrXfteu97qn27gD2AxobLipWTW1bj" + + "WMdfPmWtZpZqgGu1dTeKx1Akd+VXMF8bYGVmemxgdfR6Gn9ugfSHGJBFmbN7yXVcevl7UYTE//+QEHnpepKDNlrhd0BOLU7yW5lf4GK9Zjxn8WzohmuId4o7kU6S85nQahdwJnMqgueEV4yvTgFRRlHLVYtb9V4WXjNegV8UXzVfa1+0YfJjEWaiZx1vbnJSdTp3OoB0" + + "gTmBeId2ir+K3I2FjfOSmpV3mAKc5VLFY1d29GcVbIhzzYzDk66Wc20lWJxpDmnMj/2TmnXbkBpYWmgCY7Rp+09Dbyxn2I+7hSZ9tJNUaT9vcFdqWPdbLH0scipUCpHjnbROrU9OUFxQdVJDjJ5USFgkW5peHV6VXq1e918fYIxitWM6Y9Bor2xAeId5jnoLfeCCR4oC" + + "iuaORJAT////////kLiRLZHYnw5s5WRYZOJldW70doR7G5Bpk9FuulTyX7lkpI9Nj+2SRFF4WGtZKVxVXpdt+36PdRyMvI7imFtwuU8da79vsXUwlvtRTlQQWDVYV1msXGBfkmWXZ1xuIXZ7g9+M7ZAUkP2TTXgleDpSql6mVx9ZdGASUBJRWlGs//9RzVIAVRBYVFhY" + + "WVdblVz2XYtgvGKVZC1ncWhDaLxo33bXbdhub22bcG9xyF9Tddh5d3tJe1R7UnzWfXFSMIRjhWmF5IoOiwSMRo4PkAOQD5QZlnaYLZowldhQzVLVVAxYAlwOYadknm0ed7N65YD0hASQU5KFXOCdB1M/X5dfs22ccnl3Y3m/e+Rr0nLsiq1oA2phUfh6gWk0XEqc9oLr" + + "W8WRSXAeVnhcb2DHZWZsjIxakEGYE1RRZseSDVlIkKNRhU5NUeqFmYsOcFhjepNLaWKZtH4EdXdTV2lgjt+W42xdToxcPF8Qj+lTAozRgImGeV7/ZeVOc1Fl////////WYJcP5fuTvtZil/Nio1v4XmweWJb54RxcytxsV50X/Vje2SaccN8mE5DXvxOS1fcVqJgqW/D" + + "fQ2A/YEzgb+PsomXhqRd9GKKZK2Jh2d3bOJtPnQ2eDRaRn91gq2ZrE/zXsNi3WOSZVdnb3bDckyAzIC6jymRTVANV/lakmiF//9pc3Fkcv2Mt1jyjOCWapAZh3955HfnhClPL1JlU1pizWfPbMp2fXuUfJWCNoWEj+tm3W8gcgZ+G4OrmcGeplH9e7F4cnu4gId7SGro" + + "XmGAjHVRdWBRa5Jibox2epGXmupPEH9wYpx7T5WlnOlWelhZhuSWvE80UiRTSlPNU9teBmQsZZFnf2w+bE5ySHKvc+11VH5BgiyF6Yype8SRxnFpmBKY72M9Zml1anbkeNCFQ4buUypTUVQmWYNeh198YLJiSWJ5YqtlkGvUbMx1snaueJF52H3Lf3eApYirirmMu5B/" + + "l16Y22oLfDhQmVw+X65nh2vYdDV3CX+O////////nztnynoXUzl1i5rtX2aBnYPxgJhfPF/FdWJ7RpA8aGdZ61qbfRB2fossT/VfamoZbDdvAnTieWiIaIpVjHle32PPdcV50oLXkyiS8oSchu2cLVTBX2xljG1ccBWMp4zTmDtlT3T2Tg1O2FfgWStaZlvMUaheA16c" + + "YBZidmV3//9lp2ZubW5yNnsmgVCBmoKZi1yMoIzmjXSWHJZET65kq2tmgh6EYYVqkOhcAWlTmKiEeoVXTw9Sb1+pXkVnDXmPgXmJB4mGbfVfF2JVbLhOz3Jpm5JSBlQ7VnRYs2GkYm5xGllufIl83n0blvBlh4BeThlPdVF1WEBeY15zXwpnxE4mhT2ViZZbfHOYAVD7" + + "WMF2VninUiV3pYURe4ZQT1kJckd7x33oj7qP1JBNT79SyVopXwGXrU/dgheS6lcDY1VraXUriNyPFHpCUt9Yk2FVYgpmrmvNfD+D6VAjT/hTBVRGWDFZSVudXPBc710pXpZisWNnZT5luWcL////////bNVs4XD5eDJ+K4DegrOEDITshwKJEooqjEqQppLSmP2c851s" + + "Tk9OoVCNUlZXSlmoXj1f2F/ZYj9mtGcbZ9Bo0lGSfSGAqoGoiwCMjIy/kn6WMlQgmCxTF1DVU1xYqGSyZzRyZ3dmekaR5lLDbKFrhlgAXkxZVGcsf/tR4XbG//9kaXjom1Seu1fLWblmJ2eaa85U6WnZXlWBnGeVm6pn/pxSaF1Opk/jU8hiuWcrbKuPxE+tfm2ev04H" + + "YWJugG8rhRNUc2cqm0Vd83uVXKxbxoccbkqE0XoUgQhZmXyNbBF3IFLZWSJxIXJfd9uXJ51haQtaf1oYUaVUDVR9Zg5234/3kpic9Fnqcl1uxVFNaMl9v33sl2KeumR4aiGDAlmEW19r23MbdvJ9soAXhJlRMmcontl27mdiUv+ZBVwkYjt8foywVU9gtn0LlYBTAU5f" + + "UbZZHHI6gDaRzl8ld+JThF95fQSFrIozjo2XVmfzha6UU2EJYQhsuXZS////////iu2POFUvT1FRKlLHU8tbpV59YKBhgmPWZwln2m5nbYxzNnM3dTF5UIjVipiQSpCRkPWWxIeNWRVOiE9ZTg6KiY8/mBBQrV58WZZbuV64Y9pj+mTBZtxpSmnYbQtutnGUdSh6r3+K" + + "gACESYTJiYGLIY4KkGWWfZkKYX5ikWsy//9sg210f8x//G3Af4WHuoj4Z2WDsZg8lvdtG31hhD2Rak5xU3VdUGsEb+uFzYYtiadSKVQPXGVnTmiodAZ0g3XiiM+I4ZHMluKWeF+Lc4d6y4ROY6B1ZVKJbUFunHQJdVl4a3ySloZ63J+NT7ZhbmXFhlxOhk6uUNpOIVHM" + + "W+5lmWiBbbxzH3ZCd616HHzngm+K0pB8kc+WdZgYUpt90VArU5hnl23LcdB0M4HojyqWo5xXnp90YFhBbZl9L5heTuRPNk+LUbdSsV26YBxzsnk8gtOSNJa3lvaXCp6Xn2Jmpmt0UhdSo3DIiMJeyWBLYZBvI3FJfD599IBv////////hO6QI5MsVEKbb2rTcImMwo3v" + + "lzJStFpBXspfBGcXaXxplG1qbw9yYnL8e+2AAYB+h0uQzlFtnpN5hICLkzKK1lAtVIyKcWtqjMSBB2DRZ6Cd8k6ZTpicEIprhcGFaGkAbn54l4FV////////////////////////////////////////////////////////////////////////////////////////" + + "/////////////////////////////18MThBOFU4qTjFONk48Tj9OQk5WTlhOgk6FjGtOioISXw1Ojk6eTp9OoE6iTrBOs062Ts5OzU7ETsZOwk7XTt5O7U7fTvdPCU9aTzBPW09dT1dPR092T4hPj0+YT3tPaU9wT5FPb0+GT5ZRGE/UT99Pzk/YT9tP0U/aT9BP5E/l" + + "UBpQKFAUUCpQJVAFTxxP9lAhUClQLE/+T+9QEVAGUENQR2cDUFVQUFBIUFpQVlBsUHhQgFCaUIVQtFCy////////UMlQylCzUMJQ1lDeUOVQ7VDjUO5Q+VD1UQlRAVECURZRFVEUURpRIVE6UTdRPFE7UT9RQFFSUUxRVFFievhRaVFqUW5RgFGCVthRjFGJUY9RkVGT" + + "UZVRllGkUaZRolGpUapRq1GzUbFRslGwUbVRvVHFUclR21HghlVR6VHt//9R8FH1Uf5SBFILUhRSDlInUipSLlIzUjlST1JEUktSTFJeUlRSalJ0UmlSc1J/Un1SjVKUUpJScVKIUpGPqI+nUqxSrVK8UrVSwVLNUtdS3lLjUuaY7VLgUvNS9VL4UvlTBlMIdThTDVMQ" + + "Uw9TFVMaUyNTL1MxUzNTOFNAU0ZTRU4XU0lTTVHWU15TaVNuWRhTe1N3U4JTllOgU6ZTpVOuU7BTtlPDfBKW2VPfZvxx7lPuU+hT7VP6VAFUPVRAVCxULVQ8VC5UNlQpVB1UTlSPVHVUjlRfVHFUd1RwVJJUe1SAVHZUhFSQVIZUx1SiVLhUpVSsVMRUyFSo////////" + + "VKtUwlSkVL5UvFTYVOVU5lUPVRRU/VTuVO1U+lTiVTlVQFVjVUxVLlVcVUVVVlVXVThVM1VdVZlVgFSvVYpVn1V7VX5VmFWeVa5VfFWDValVh1WoVdpVxVXfVcRV3FXkVdRWFFX3VhZV/lX9VhtV+VZOVlBx31Y0VjZWMlY4//9Wa1ZkVi9WbFZqVoZWgFaKVqBWlFaP" + + "VqVWrla2VrRWwla8VsFWw1bAVshWzlbRVtNW11buVvlXAFb/VwRXCVcIVwtXDVcTVxhXFlXHVxxXJlc3VzhXTlc7V0BXT1dpV8BXiFdhV39XiVeTV6BXs1ekV6pXsFfDV8ZX1FfSV9NYClfWV+NYC1gZWB1YclghWGJYS1hwa8BYUlg9WHlYhVi5WJ9Yq1i6WN5Yu1i4" + + "WK5YxVjTWNFY11jZWNhY5VjcWORY31jvWPpY+Vj7WPxY/VkCWQpZEFkbaKZZJVksWS1ZMlk4WT560llVWVBZTllaWVhZYllgWWdZbFlp////////WXhZgVmdT15Pq1mjWbJZxlnoWdxZjVnZWdpaJVofWhFaHFoJWhpaQFpsWklaNVo2WmJaalqaWrxavlrLWsJavVrj" + + "Wtda5lrpWtZa+lr7WwxbC1sWWzJa0FsqWzZbPltDW0VbQFtRW1VbWltbW2VbaVtwW3NbdVt4ZYhbeluA//9bg1umW7hbw1vHW8lb1FvQW+Rb5lviW95b5VvrW/Bb9lvzXAVcB1wIXA1cE1wgXCJcKFw4XDlcQVxGXE5cU1xQXE9bcVxsXG5OYlx2XHlcjFyRXJRZm1yr" + + "XLtctly8XLdcxVy+XMdc2VzpXP1c+lztXYxc6l0LXRVdF11cXR9dG10RXRRdIl0aXRldGF1MXVJdTl1LXWxdc112XYddhF2CXaJdnV2sXa5dvV2QXbddvF3JXc1d013SXdZd213rXfJd9V4LXhpeGV4RXhteNl43XkReQ15AXk5eV15UXl9eYl5kXkdedV52XnqevF5/" + + "XqBewV7CXshe0F7P////////XtZe417dXtpe217iXuFe6F7pXuxe8V7zXvBe9F74Xv5fA18JX11fXF8LXxFfFl8pXy1fOF9BX0hfTF9OXy9fUV9WX1dfWV9hX21fc193X4Nfgl9/X4pfiF+RX4dfnl+ZX5hfoF+oX61fvF/WX/tf5F/4X/Ff3WCzX/9gIWBg//9gGWAQ" + + "YClgDmAxYBtgFWArYCZgD2A6YFpgQWBqYHdgX2BKYEZgTWBjYENgZGBCYGxga2BZYIFgjWDnYINgmmCEYJtglmCXYJJgp2CLYOFguGDgYNNgtF/wYL1gxmC1YNhhTWEVYQZg9mD3YQBg9GD6YQNhIWD7YPFhDWEOYUdhPmEoYSdhSmE/YTxhLGE0YT1hQmFEYXNhd2FY" + + "YVlhWmFrYXRhb2FlYXFhX2FdYVNhdWGZYZZhh2GsYZRhmmGKYZFhq2GuYcxhymHJYfdhyGHDYcZhumHLf3lhzWHmYeNh9mH6YfRh/2H9Yfxh/mIAYghiCWINYgxiFGIb////////Yh5iIWIqYi5iMGIyYjNiQWJOYl5iY2JbYmBiaGJ8YoJiiWJ+YpJik2KWYtRig2KU" + + "Ytdi0WK7Ys9i/2LGZNRiyGLcYsxiymLCYsdim2LJYwxi7mLxYydjAmMIYu9i9WNQYz5jTWQcY09jlmOOY4Bjq2N2Y6Njj2OJY59jtWNr//9jaWO+Y+ljwGPGY+NjyWPSY/ZjxGQWZDRkBmQTZCZkNmUdZBdkKGQPZGdkb2R2ZE5lKmSVZJNkpWSpZIhkvGTaZNJkxWTH" + + "ZLtk2GTCZPFk54IJZOBk4WKsZONk72UsZPZk9GTyZPplAGT9ZRhlHGUFZSRlI2UrZTRlNWU3ZTZlOHVLZUhlVmVVZU1lWGVeZV1lcmV4ZYJlg4uKZZtln2WrZbdlw2XGZcFlxGXMZdJl22XZZeBl4WXxZ3JmCmYDZftnc2Y1ZjZmNGYcZk9mRGZJZkFmXmZdZmRmZ2Zo" + + "Zl9mYmZwZoNmiGaOZolmhGaYZp1mwWa5Zslmvma8////////ZsRmuGbWZtpm4GY/ZuZm6WbwZvVm92cPZxZnHmcmZyeXOGcuZz9nNmdBZzhnN2dGZ15nYGdZZ2NnZGeJZ3BnqWd8Z2pnjGeLZ6ZnoWeFZ7dn72e0Z+xns2fpZ7hn5GfeZ91n4mfuZ7lnzmfGZ+dqnGge" + + "aEZoKWhAaE1oMmhO//9os2graFloY2h3aH9on2iPaK1olGidaJtog2quaLlodGi1aKBoumkPaI1ofmkBaMppCGjYaSJpJmjhaQxozWjUaOdo1Wk2aRJpBGjXaONpJWj5aOBo72koaSppGmkjaSFoxml5aXdpXGl4aWtpVGl+aW5pOWl0aT1pWWkwaWFpXmldaYFpammy" + + "aa5p0Gm/acFp02m+ac5b6GnKad1pu2nDaadqLmmRaaBpnGmVabRp3mnoagJqG2n/awpp+WnyaedqBWmxah5p7WoUaetqCmoSasFqI2oTakRqDGpyajZqeGpHamJqWWpmakhqOGoiapBqjWqgaoRqomqj////////apeGF2q7asNqwmq4arNqrGreatFq32qqatpq6mr7" + + "awWGFmr6axJrFpsxax9rOGs3dtxrOZjua0drQ2tJa1BrWWtUa1trX2tha3hreWt/a4BrhGuDa41rmGuVa55rpGuqa6trr2uya7Frs2u3a7xrxmvLa9Nr32vsa+tr82vv//+evmwIbBNsFGwbbCRsI2xebFVsYmxqbIJsjWyabIFsm2x+bGhsc2ySbJBsxGzxbNNsvWzX" + + "bMVs3WyubLFsvmy6bNts72zZbOptH4hNbTZtK209bThtGW01bTNtEm0MbWNtk21kbVpteW1ZbY5tlW/kbYVt+W4VbgpttW3HbeZtuG3Gbext3m3Mbeht0m3Fbfpt2W3kbdVt6m3ubi1ubm4ubhlucm5fbj5uI25rbitudm5Nbh9uQ246bk5uJG7/bh1uOG6CbqpumG7J" + + "brdu0269bq9uxG6ybtRu1W6PbqVuwm6fb0FvEXBMbuxu+G7+bz9u8m8xbu9vMm7M////////bz5vE273b4Zvem94b4FvgG9vb1tv829tb4JvfG9Yb45vkW/Cb2Zvs2+jb6FvpG+5b8Zvqm/fb9Vv7G/Ub9hv8W/ub9twCXALb/pwEXABcA9v/nAbcBpvdHAdcBhwH3Aw" + + "cD5wMnBRcGNwmXCScK9w8XCscLhws3CucN9wy3Dd//9w2XEJcP1xHHEZcWVxVXGIcWZxYnFMcVZxbHGPcftxhHGVcahxrHHXcblxvnHScclx1HHOceBx7HHncfVx/HH5cf9yDXIQchtyKHItcixyMHIycjtyPHI/ckByRnJLclhydHJ+coJygXKHcpJylnKicqdyuXKy" + + "csNyxnLEcs5y0nLicuBy4XL5cvdQD3MXcwpzHHMWcx1zNHMvcylzJXM+c05zT57Yc1dzanNoc3BzeHN1c3tzenPIc7NzznO7c8Bz5XPuc950onQFdG90JXP4dDJ0OnRVdD90X3RZdEF0XHRpdHB0Y3RqdHZ0fnSLdJ50p3TKdM901HPx////////dOB043TndOl07nTy" + + "dPB08XT4dPd1BHUDdQV1DHUOdQ11FXUTdR51JnUsdTx1RHVNdUp1SXVbdUZ1WnVpdWR1Z3VrdW11eHV2dYZ1h3V0dYp1iXWCdZR1mnWddaV1o3XCdbN1w3W1db11uHW8dbF1zXXKddJ12XXjdd51/nX///91/HYBdfB1+nXydfN2C3YNdgl2H3YndiB2IXYidiR2NHYw" + + "djt2R3ZIdkZ2XHZYdmF2YnZodml2anZndmx2cHZydnZ2eHZ8doB2g3aIdot2jnaWdpN2mXaadrB2tHa4drl2unbCds121nbSdt524Xbldud26oYvdvt3CHcHdwR3KXckdx53JXcmdxt3N3c4d0d3Wndod2t3W3dld393fnd5d453i3eRd6B3nnewd7Z3uXe/d7x3vXe7" + + "d8d3zXfXd9p33Hfjd+53/HgMeBJ5JnggeSp4RXiOeHR4hnh8eJp4jHijeLV4qniveNF4xnjLeNR4vni8eMV4ynjs////////eOd42nj9ePR5B3kSeRF5GXkseSt5QHlgeVd5X3laeVV5U3l6eX95inmdeaefS3mqea55s3m5ebp5yXnVeed57HnheeN6CHoNehh6GXog" + + "eh95gHoxejt6Pno3ekN6V3pJemF6Ynppn516cHp5en16iHqXepV6mHqWeql6yHqw//96tnrFesR6v5CDesd6ynrNes961XrTetl62nrdeuF64nrmeu168HsCew97CnsGezN7GHsZex57NXsoezZ7UHt6ewR7TXsLe0x7RXt1e2V7dHtne3B7cXtse257nXuYe597jXuc" + + "e5p7i3uSe497XXuZe8t7wXvMe897tHvGe9176XwRfBR75nvlfGB8AHwHfBN783v3fBd8DXv2fCN8J3wqfB98N3wrfD18THxDfFR8T3xAfFB8WHxffGR8VnxlfGx8dXyDfJB8pHytfKJ8q3yhfKh8s3yyfLF8rny5fL18wHzFfMJ82HzSfNx84ps7fO988nz0fPZ8+n0G" + + "////////fQJ9HH0VfQp9RX1LfS59Mn0/fTV9Rn1zfVZ9Tn1yfWh9bn1PfWN9k32JfVt9j319fZt9un2ufaN9tX3Hfb19q349faJ9r33cfbh9n32wfdh93X3kfd59+33yfeF+BX4KfiN+IX4SfjF+H34Jfgt+In5GfmZ+O341fjl+Q343//9+Mn46fmd+XX5Wfl5+WX5a" + + "fnl+an5pfnx+e36DfdV+fY+ufn9+iH6Jfox+kn6QfpN+lH6Wfo5+m36cfzh/On9Ff0x/TX9Of1B/UX9Vf1R/WH9ff2B/aH9pf2d/eH+Cf4Z/g3+If4d/jH+Uf55/nX+af6N/r3+yf7l/rn+2f7iLcX/Ff8Z/yn/Vf9R/4X/mf+l/83/5mNyABoAEgAuAEoAYgBmAHIAh" + + "gCiAP4A7gEqARoBSgFiAWoBfgGKAaIBzgHKAcIB2gHmAfYB/gISAhoCFgJuAk4CagK1RkICsgNuA5YDZgN2AxIDagNaBCYDvgPGBG4EpgSOBL4FL////////louBRoE+gVOBUYD8gXGBboFlgWaBdIGDgYiBioGAgYKBoIGVgaSBo4FfgZOBqYGwgbWBvoG4gb2BwIHC" + + "gbqByYHNgdGB2YHYgciB2oHfgeCB54H6gfuB/oIBggKCBYIHggqCDYIQghaCKYIrgjiCM4JAglmCWIJdglqCX4Jk//+CYoJogmqCa4IugnGCd4J4gn6CjYKSgquCn4K7gqyC4YLjgt+C0oL0gvOC+oOTgwOC+4L5gt6DBoLcgwmC2YM1gzSDFoMygzGDQIM5g1CDRYMv" + + "gyuDF4MYg4WDmoOqg5+DooOWgyODjoOHg4qDfIO1g3ODdYOgg4mDqIP0hBOD64POg/2EA4PYhAuDwYP3hAeD4IPyhA2EIoQgg72EOIUGg/uEbYQqhDyFWoSEhHeEa4SthG6EgoRphEaELIRvhHmENYTKhGKEuYS/hJ+E2YTNhLuE2oTQhMGExoTWhKGFIYT/hPSFF4UY" + + "hSyFH4UVhRSE/IVAhWOFWIVI////////hUGGAoVLhVWFgIWkhYiFkYWKhaiFbYWUhZuF6oWHhZyFd4V+hZCFyYW6hc+FuYXQhdWF3YXlhdyF+YYKhhOGC4X+hfqGBoYihhqGMIY/hk1OVYZUhl+GZ4ZxhpOGo4aphqqGi4aMhraGr4bEhsaGsIbJiCOGq4bUht6G6Ybs" + + "//+G34bbhu+HEocGhwiHAIcDhvuHEYcJhw2G+YcKhzSHP4c3hzuHJYcphxqHYIdfh3iHTIdOh3SHV4doh26HWYdTh2OHaogFh6KHn4eCh6+Hy4e9h8CH0JbWh6uHxIezh8eHxoe7h++H8ofgiA+IDYf+h/aH94gOh9KIEYgWiBWIIoghiDGINog5iCeIO4hEiEKIUohZ" + + "iF6IYohriIGIfoieiHWIfYi1iHKIgoiXiJKIroiZiKKIjYikiLCIv4ixiMOIxIjUiNiI2YjdiPmJAoj8iPSI6IjyiQSJDIkKiROJQ4keiSWJKokriUGJRIk7iTaJOIlMiR2JYIle////////iWaJZIltiWqJb4l0iXeJfomDiYiJiomTiZiJoYmpiaaJrImvibKJuom9" + + "ib+JwInaidyJ3YnnifSJ+IoDihaKEIoMihuKHYolijaKQYpbilKKRopIinyKbYpsimKKhYqCioSKqIqhipGKpYqmipqKo4rEis2KworaiuuK84rn//+K5IrxixSK4IriiveK3orbiwyLB4saiuGLFosQixeLIIszl6uLJosriz6LKItBi0yLT4tOi0mLVotbi1qLa4tf" + + "i2yLb4t0i32LgIuMi46LkouTi5aLmYuajDqMQYw/jEiMTIxOjFCMVYxijGyMeIx6jIKMiYyFjIqMjYyOjJSMfIyYYh2MrYyqjL2MsoyzjK6MtozIjMGM5IzjjNqM/Yz6jPuNBI0FjQqNB40PjQ2NEJ9OjROMzY0UjRaNZ41tjXGNc42BjZmNwo2+jbqNz43ajdaNzI3b" + + "jcuN6o3rjd+N4438jgiOCY3/jh2OHo4Qjh+OQo41jjCONI5K////////jkeOSY5MjlCOSI5ZjmSOYI4qjmOOVY52jnKOfI6BjoeOhY6EjouOio6TjpGOlI6ZjqqOoY6sjrCOxo6xjr6OxY7IjsuO247jjvyO+47rjv6PCo8FjxWPEo8ZjxOPHI8fjxuPDI8mjzOPO485" + + "j0WPQo8+j0yPSY9Gj06PV49c//+PYo9jj2SPnI+fj6OPrY+vj7eP2o/lj+KP6o/vkIeP9JAFj/mP+pARkBWQIZANkB6QFpALkCeQNpA1kDmP+JBPkFCQUZBSkA6QSZA+kFaQWJBekGiQb5B2lqiQcpCCkH2QgZCAkIqQiZCPkKiQr5CxkLWQ4pDkYkiQ25ECkRKRGZEy" + + "kTCRSpFWkViRY5FlkWmRc5FykYuRiZGCkaKRq5GvkaqRtZG0kbqRwJHBkcmRy5HQkdaR35HhkduR/JH1kfaSHpH/khSSLJIVkhGSXpJXkkWSSZJkkkiSlZI/kkuSUJKckpaSk5KbklqSz5K5kreS6ZMPkvqTRJMu////////kxmTIpMakyOTOpM1kzuTXJNgk3yTbpNW" + + "k7CTrJOtk5STuZPWk9eT6JPlk9iTw5Pdk9CTyJPklBqUFJQTlAOUB5QQlDaUK5Q1lCGUOpRBlFKURJRblGCUYpRelGqSKZRwlHWUd5R9lFqUfJR+lIGUf5WClYeVipWUlZaVmJWZ//+VoJWolaeVrZW8lbuVuZW+lcpv9pXDlc2VzJXVldSV1pXcleGV5ZXiliGWKJYu" + + "li+WQpZMlk+WS5Z3llyWXpZdll+WZpZylmyWjZaYlpWWl5aqlqeWsZaylrCWtJa2lriWuZbOlsuWyZbNiU2W3JcNltWW+ZcElwaXCJcTlw6XEZcPlxaXGZcklyqXMJc5lz2XPpdEl0aXSJdCl0mXXJdgl2SXZpdoUtKXa5dxl3mXhZd8l4GXepeGl4uXj5eQl5yXqJem" + + "l6OXs5e0l8OXxpfIl8uX3Jftn0+X8nrfl/aX9ZgPmAyYOJgkmCGYN5g9mEaYT5hLmGuYb5hw////////mHGYdJhzmKqYr5ixmLaYxJjDmMaY6ZjrmQOZCZkSmRSZGJkhmR2ZHpkkmSCZLJkumT2ZPplCmUmZRZlQmUuZUZlSmUyZVZmXmZiZpZmtma6ZvJnfmduZ3ZnY" + + "mdGZ7ZnumfGZ8pn7mfiaAZoPmgWZ4poZmiuaN5pFmkKaQJpD//+aPppVmk2aW5pXml+aYpplmmSaaZprmmqarZqwmryawJrPmtGa05rUmt6a35rimuOa5prvmuua7pr0mvGa95r7mwabGJsamx+bIpsjmyWbJ5somymbKpsumy+bMptEm0ObT5tNm06bUZtYm3Sbk5uD" + + "m5GblpuXm5+boJuom7SbwJvKm7mbxpvPm9Gb0pvjm+Kb5JvUm+GcOpvym/Gb8JwVnBScCZwTnAycBpwInBKcCpwEnC6cG5wlnCScIZwwnEecMpxGnD6cWpxgnGecdpx4nOec7JzwnQmdCJzrnQOdBp0qnSadr50jnR+dRJ0VnRKdQZ0/nT6dRp1I////////nV2dXp1k" + + "nVGdUJ1ZnXKdiZ2Hnaudb516nZqdpJ2pnbKdxJ3BnbuduJ26ncadz53Cndmd0534nead7Z3vnf2eGp4bnh6edZ55nn2egZ6InouejJ6SnpWekZ6dnqWeqZ64nqqerZdhnsyezp7PntCe1J7cnt6e3Z7gnuWe6J7v//+e9J72nvee+Z77nvye/Z8Hnwh2t58VnyGfLJ8+" + + "n0qfUp9Un2OfX59gn2GfZp9nn2yfap93n3Kfdp+Vn5yfoFgvaceQWXRkUdxxmf//////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + + "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + + "////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////" + + "/////////////////////////////////////////////w==" diff --git a/chardet_test.go b/chardet_test.go new file mode 100644 index 0000000..9d71710 --- /dev/null +++ b/chardet_test.go @@ -0,0 +1,339 @@ +package qrcode + +import ( + "testing" +) + +func Test_analyzeNum(t *testing.T) { + type args struct { + byt rune + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "case 0", + args: args{byt: '0'}, + want: true, + }, + { + name: "case 1", + args: args{byt: 'a'}, + want: false, + }, + { + name: "case 2", + args: args{byt: 'A'}, + want: false, + }, + { + name: "case 3", + args: args{byt: '9'}, + want: true, + }, + { + name: "case 4", + args: args{byt: '*'}, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := analyzeNum(tt.args.byt); got != tt.want { + t.Errorf("analyzeNum() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_analyzeAlphanum(t *testing.T) { + type args struct { + byt rune + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "case 0", + args: args{byt: '0'}, + want: true, + }, + { + name: "case 1", + args: args{byt: 'a'}, + want: false, + }, + { + name: "case 2", + args: args{byt: 'A'}, + want: true, + }, + { + name: "case 3", + args: args{byt: '9'}, + want: true, + }, + { + name: "case 4", + args: args{byt: '*'}, + want: true, + }, + { + name: "case 5", + args: args{byt: '?'}, + want: false, + }, + { + name: "case 6", + args: args{byt: '&'}, + want: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := analyzeAlphaNum(tt.args.byt); got != tt.want { + t.Errorf("analyzeAlphaNum() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_analyzeByte(t *testing.T) { + type args struct { + byt rune + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "case 0", + args: args{byt: '0'}, + want: true, + }, + { + name: "case 1", + args: args{byt: 'a'}, + want: true, + }, + { + name: "case 2", + args: args{byt: 'A'}, + want: true, + }, + { + name: "case 3", + args: args{byt: '9'}, + want: true, + }, + { + name: "case 4", + args: args{byt: '*'}, + want: true, + }, + { + name: "case 5", + args: args{byt: '?'}, + want: true, + }, + { + name: "case 6", + args: args{byt: '&'}, + want: true, + }, + { + name: "case 7", + args: args{byt: 'Ö'}, + want: true, + }, + { + name: "case 8", + args: args{byt: 'に'}, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := analyzeByte(tt.args.byt); got != tt.want { + t.Errorf("analyzeByte() = %v, want %v", got, tt.want) + } + }) + } +} + +func Test_analyzeJP(t *testing.T) { + type args struct { + r rune + } + tests := []struct { + name string + args args + want bool + }{ + { + name: "case 0", + args: args{r: '0'}, + want: false, + }, + { + name: "case 1", + args: args{r: 'a'}, + want: false, + }, + { + name: "case 2", + args: args{r: 'A'}, + want: false, + }, + { + name: "case 3", + args: args{r: '9'}, + want: false, + }, + { + name: "case 4", + args: args{r: '*'}, + want: false, + }, + { + name: "case 5", + args: args{r: '?'}, + want: false, + }, + { + name: "case 6", + args: args{r: '&'}, + want: false, + }, + { + name: "case 7", + args: args{r: 'Ö'}, + want: false, + }, + { + name: "case 8", + args: args{r: 'に'}, + want: true, + }, + { + name: "case 9", + args: args{r: '茗'}, + want: true, + }, + { + name: "case 10", + args: args{r: '杆'}, + want: true, + }, + { + name: "case 11", + args: args{r: '荷'}, + want: true, + }, + { + name: "case 12", + args: args{r: '杠'}, + want: true, + }, + { + name: "case 13", + args: args{r: '杙'}, + want: true, + }, + { + name: "case 14", + args: args{r: '杣'}, + want: true, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := analyzeJP(tt.args.r); got != tt.want { + t.Errorf("analyzeJP(%c=0x%x) = %v, want %v", tt.args.r, tt.args.r, got, tt.want) + } + }) + } +} + +func Test_analyzeMode(t *testing.T) { + type args struct { + raw string + } + tests := []struct { + name string + args args + want encMode + wantErr bool + }{ + { + name: "case 0", + args: args{raw: "123120899231"}, + want: EncModeNumeric, + wantErr: false, + }, + { + name: "case 1", + args: args{raw: ":/1231H208*99231FBJO"}, + want: EncModeAlphanumeric, + wantErr: false, + }, + { + name: "case 2", + args: args{raw: "hahah1298312hG&^FBJO@jhgG*"}, + want: EncModeByte, + }, + { + name: "case 3", + args: args{raw: "JKAHDOIANKQOIHCMJKASJ"}, + want: EncModeAlphanumeric, + wantErr: false, + }, + { + name: "case 4", + args: args{raw: "https://baidu.com?keyword=_JSO==GA"}, + want: EncModeByte, + wantErr: false, + }, + { + name: "case 5", + args: args{raw: "茗荷"}, + want: EncModeKanji, + wantErr: false, + }, + { + name: "case 6 (swedish letter)", + args: args{raw: "Övrigt aksldjlk Övrigt should JP encMode?"}, + want: EncModeByte, + }, + { + name: "case 7 (japanese letter)", + args: args{raw: "嵋嶄"}, + want: EncModeKanji, + wantErr: false, + }, + { + name: "issue#28 alphanum mode does not support lower case letter", + args: args{raw: "a"}, + want: EncModeByte, + wantErr: false, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := analyzeEncodeModeFromRaw(tt.args.raw) + if (err != nil) != tt.wantErr { + t.Errorf("analyzeMode() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if got != tt.want { + t.Errorf("analyzeMode() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/cmd/wasm/types.go b/cmd/wasm/types.go index f81aae3..7c9783a 100644 --- a/cmd/wasm/types.go +++ b/cmd/wasm/types.go @@ -89,8 +89,8 @@ func (o *genOption) encodeOptions() []qrcode.EncodeOption { out = append(out, qrcode.WithEncodingMode(qrcode.EncModeNumeric)) case uint8(qrcode.EncModeByte): out = append(out, qrcode.WithEncodingMode(qrcode.EncModeByte)) - case uint8(qrcode.EncModeJP): - out = append(out, qrcode.WithEncodingMode(qrcode.EncModeJP)) + case uint8(qrcode.EncModeKanji): + out = append(out, qrcode.WithEncodingMode(qrcode.EncModeKanji)) } switch o.encodeOption.ecLevel { diff --git a/encoder.go b/encoder.go index d9c2c8b..acd8219 100644 --- a/encoder.go +++ b/encoder.go @@ -5,26 +5,52 @@ package qrcode import ( "fmt" "log" + "strconv" "github.com/yeqown/reedsolomon/binary" + "golang.org/x/text/encoding/japanese" + "golang.org/x/text/transform" ) -// encMode ... +// encMode indicates the encoding mode of the data to be encoded. +// The encoding mode is used to determine how the data should be encoded +// into bits for the QR code. This repository supports the following encoding +// modes: +// - EncModeNone: no encoding +// - EncModeNumeric: numeric encoding +// - EncModeAlphanumeric: alphanumeric encoding +// - EncModeKanji: japanese kanji encoding +// - EncModeByte: byte encoding +// +// The encoding mode is determined by the data to be encoded. For example, if +// the data to be encoded is all numeric, the encoding mode will be EncModeNumeric. +// If the data to be encoded is alphanumeric, the encoding mode will be EncModeAlphanumeric. +// You can also specify the encoding mode automatically by using EncModeAuto, which +// will automatically determine the encoding mode based on the data to be encoded. type encMode uint const ( - // a qrbool of EncModeAuto will trigger a detection of the letter set from the input data, + // EncModeAuto will trigger a detection of the letter set from the input data. EncModeAuto = 0 - // EncModeNone mode ... - EncModeNone encMode = 1 << iota - // EncModeNumeric mode ... - EncModeNumeric - // EncModeAlphanumeric mode ... - EncModeAlphanumeric - // EncModeByte mode ... - EncModeByte + + // EncModeNone mode represents no encoding, usually used as initial value of encMode + EncModeNone encMode = 2 + + // EncModeNumeric mode support only numeric character set (0-9) + EncModeNumeric encMode = 4 + + // EncModeAlphanumeric mode support only alphanumeric character set (0-9, A-Z, SP, $%*+-./ or :) + EncModeAlphanumeric encMode = 8 + // EncModeJP mode ... - EncModeJP + // @Deprecated use EncModeKanji instead + EncModeJP encMode = 16 + // EncModeKanji mode support only Shift JIS encoding character set. + // From 0x8140 to 0x9FFC and 0xE040 to 0xEBBF. + EncModeKanji = EncModeJP + + // EncModeByte mode support ISO-8859-1 character set by default, but also support UTF-8. + EncModeByte encMode = 32 ) var ( @@ -41,12 +67,12 @@ func getEncModeName(mode encMode) string { return "numeric" case EncModeAlphanumeric: return "alphanumeric" + case EncModeKanji: + return "kanji" case EncModeByte: return "byte" - case EncModeJP: - return "japan" default: - return "unknown" + return "unknown(" + strconv.Itoa(int(mode)) + ")" } } @@ -59,7 +85,7 @@ func getEncodeModeIndicator(mode encMode) *binary.Binary { return binary.New(false, false, true, false) case EncModeByte: return binary.New(false, true, false, false) - case EncModeJP: + case EncModeKanji: return binary.New(true, false, false, false) default: panic("no indicator") @@ -69,8 +95,7 @@ func getEncodeModeIndicator(mode encMode) *binary.Binary { // encoder ... data to bit stream ... type encoder struct { // self init - dst *binary.Binary - data []byte // raw input data + dst *binary.Binary // initial params mode encMode // encode mode @@ -81,9 +106,14 @@ type encoder struct { } func newEncoder(m encMode, ec ecLevel, v version) *encoder { + switch m { + case EncModeNumeric, EncModeAlphanumeric, EncModeByte, EncModeKanji: + default: + panic("unsupported data encoding mode in newEncoder()") + } + return &encoder{ dst: nil, - data: nil, mode: m, ecLv: ec, version: v, @@ -93,27 +123,37 @@ func newEncoder(m encMode, ec ecLevel, v version) *encoder { // Encode ... // 1. encode raw data into bitset // 2. append _defaultPadding data -// -func (e *encoder) Encode(byts []byte) (*binary.Binary, error) { +func (e *encoder) Encode(raw string) (*binary.Binary, error) { e.dst = binary.New() - e.data = byts + + var data []byte + switch e.mode { + case EncModeNumeric, EncModeAlphanumeric, EncModeByte: + data = []byte(raw) + case EncModeKanji: + data = toShiftJIS(raw) + default: + log.Printf("unsupported encoding mode: %s", getEncModeName(e.mode)) + } // append mode indicator symbol indicator := getEncodeModeIndicator(e.mode) e.dst.Append(indicator) // append chars length counter bits symbol - e.dst.AppendUint32(uint32(len(byts)), e.charCountBits()) + e.dst.AppendUint32(uint32(len(data)), e.charCountBits()) // encode data with specified mode switch e.mode { case EncModeNumeric: - e.encodeNumeric() + e.encodeNumeric(data) case EncModeAlphanumeric: - e.encodeAlphanumeric() + e.encodeAlphanumeric(data) + case EncModeKanji: + e.encodeKanji(data) case EncModeByte: - e.encodeByte() - case EncModeJP: - panic("this has not been finished") + e.encodeByte(data) + default: + log.Printf("unsupported encoding mode: %s", getEncModeName(e.mode)) } // fill and _defaultPadding bits @@ -123,20 +163,20 @@ func (e *encoder) Encode(byts []byte) (*binary.Binary, error) { } // 0001b mode indicator -func (e *encoder) encodeNumeric() { +func (e *encoder) encodeNumeric(data []byte) { if e.dst == nil { log.Println("e.dst is nil") return } - for i := 0; i < len(e.data); i += 3 { - charsRemaining := len(e.data) - i + for i := 0; i < len(data); i += 3 { + charsRemaining := len(data) - i var value uint32 bitsUsed := 1 for j := 0; j < charsRemaining && j < 3; j++ { value *= 10 - value += uint32(e.data[i+j] - 0x30) + value += uint32(data[i+j] - 0x30) bitsUsed += 3 } e.dst.AppendUint32(value, bitsUsed) @@ -144,18 +184,18 @@ func (e *encoder) encodeNumeric() { } // 0010b mode indicator -func (e *encoder) encodeAlphanumeric() { +func (e *encoder) encodeAlphanumeric(data []byte) { if e.dst == nil { log.Println("e.dst is nil") return } - for i := 0; i < len(e.data); i += 2 { - charsRemaining := len(e.data) - i + for i := 0; i < len(data); i += 2 { + charsRemaining := len(data) - i var value uint32 for j := 0; j < charsRemaining && j < 2; j++ { value *= 45 - value += encodeAlphanumericCharacter(e.data[i+j]) + value += encodeAlphanumericCharacter(data[i+j]) } bitsUsed := 6 @@ -168,16 +208,81 @@ func (e *encoder) encodeAlphanumeric() { } // 0100b mode indicator -func (e *encoder) encodeByte() { +func (e *encoder) encodeByte(data []byte) { if e.dst == nil { log.Println("e.dst is nil") return } - for _, b := range e.data { + for _, b := range data { _ = e.dst.AppendByte(b, 8) } } +// toShiftJIS +// https://www.thonky.com/qr-code-tutorial/kanji-mode-encoding +func toShiftJIS(raw string) []byte { + // FIXME: some character encoded into Shift JIS but not in the range of 0x8140-0x9FFC and 0xE040-0xEBBF. + enc := japanese.ShiftJIS.NewEncoder() + s2, _, err := transform.String(enc, raw) + if err != nil { + log.Printf("could not encode string to Shift JIS: %v", err) + return []byte{} + } + + data := []byte(s2) + if len(data)%2 != 0 { + // BUG: encode bytes with Shift JIS must be times of 2, cause panic here + log.Panicf("shift JIS encoded []byte must be times of 2, but got %d", len(data)) + } + + for i := 0; i < len(data); i += 2 { + data[i], data[i+1] = encodeShiftJIS(data[i], data[i+1]) + } + + return data +} + +func encodeShiftJIS(hi byte, lo byte) (byte, byte) { + r := uint16(hi)<<8 | uint16(lo) + + // fmt.Printf("before: r=%x\n", r) + if r > 0x8140 && r < 0x9FFC { + r -= 0x8140 + } else if r > 0xE040 && r < 0xEBBF { + r -= 0xC140 + } else { + // Not a Shift JIS character out of range 0x8140-0x9FFC and 0xE040-0xEBBF + log.Printf("'%c'(0x%x) not a Shift JIS character out of range 0x8140-0x9FFC and 0xE040-0xEBBF", r, r) + return 0, 0 + } + + fmt.Printf("middle: r=%x\n", r) + hi = uint8(r >> 8) + lo = uint8(r & 0xFF) + + // fmt.Printf("middle: high=%x, low=%x\n", hi, lo) + + r = uint16(hi)*uint16(0xC0) + uint16(lo) + // fmt.Printf("after: r=%x\n", r) + + return byte(r >> 8), byte(r & 0xFF) +} + +// encodeKanji +func (e *encoder) encodeKanji(data []byte) { + // data must be times of 2, since toShiftJIS encode 1 char to 2 bytes + if len(data)%2 != 0 { + log.Println("data must be times of 2") + } + + for i := 0; i < len(data); i += 2 { + // 2 bytes to 1 kanji + // 2 bytes to 13 bits + _ = e.dst.AppendByte(data[i]<<3, 5) + _ = e.dst.AppendByte(data[i+1], 8) + } +} + // Break Up into 8-bit Codewords and Add Pad Bytes if Necessary func (e *encoder) breakUpInto8bit() { // fill ending code (max 4bit) @@ -280,71 +385,3 @@ 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 - -// analyzeEncodeModeFromRaw try to detect letter set of input data, -// so that encoder can determine which mode should be use. -// reference: https://en.wikipedia.org/wiki/QR_code -// -// 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. -// -func analyzeEncodeModeFromRaw(raw []byte) encMode { - analyzeFnMapping := map[encMode]analyzeEncFunc{ - EncModeNumeric: analyzeNum, - EncModeAlphanumeric: analyzeAlphaNum, - EncModeByte: nil, - EncModeJP: nil, - } - - var ( - f analyzeEncFunc - mode = EncModeNumeric - ) - - // 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 { - reAnalyze: - if f = analyzeFnMapping[mode]; f == nil { - break - } - - // issue#28 @borislavone reports this bug. - // FIXED(@yeqown): next encMode analyzeVersionAuto func did not check the previous byte, - // add goto statement to reanalyze previous byte which can't be analyzed in last encMode. - if !f(byt) { - mode <<= 1 - goto reAnalyze - } - } - - return mode -} - -// analyzeNum is byt in num encMode -func analyzeNum(byt byte) bool { - return byt >= '0' && byt <= '9' -} - -// analyzeAlphaNum is byt in alpha number -func analyzeAlphaNum(byt byte) bool { - if (byt >= '0' && byt <= '9') || (byt >= 'A' && byt <= 'Z') { - return true - } - switch byt { - case ' ', '$', '%', '*', '+', '-', '.', '/', ':': - return true - } - return false -} - -//// analyzeByte is byt in bytes. -//func analyzeByte(byt byte) qrbool { -// return false -//} diff --git a/encoder_test.go b/encoder_test.go index c89a459..da5d267 100644 --- a/encoder_test.go +++ b/encoder_test.go @@ -1,13 +1,10 @@ package qrcode import ( + "bytes" "testing" ) -// func init() { -// load(defaultVersionCfg) -// } - func TestEncodeNum(t *testing.T) { enc := encoder{ ecLv: ErrorCorrectionLow, @@ -15,7 +12,7 @@ func TestEncodeNum(t *testing.T) { version: loadVersion(1, ErrorCorrectionLow), } - b, err := enc.Encode([]byte("12312312")) + b, err := enc.Encode("12312312") if err != nil { t.Errorf("could not encode: %v", err) t.Fail() @@ -30,7 +27,7 @@ func TestEncodeAlphanum(t *testing.T) { version: loadVersion(1, ErrorCorrectionLow), } - b, err := enc.Encode([]byte("AKJA*:/")) + b, err := enc.Encode("AKJA*:/") if err != nil { t.Errorf("could not encode: %v", err) t.Fail() @@ -45,7 +42,7 @@ func TestEncodeByte(t *testing.T) { version: loadVersion(5, ErrorCorrectionQuart), } - b, err := enc.Encode([]byte("http://baidu.com?keyword=123123")) + b, err := enc.Encode("http://baidu.com?keyword=123123") if err != nil { t.Errorf("could not encode: %v", err) t.Fail() @@ -53,158 +50,26 @@ func TestEncodeByte(t *testing.T) { t.Log(b, b.Len()) } -func Test_analyzeNum(t *testing.T) { +func Test_toShiftJIS(t *testing.T) { type args struct { - byt byte + s string } tests := []struct { name string args args - want bool + want []byte }{ { - name: "case 0", - args: args{byt: '0'}, - want: true, - }, - { - name: "case 1", - args: args{byt: 'a'}, - want: false, - }, - { - name: "case 2", - args: args{byt: 'A'}, - want: false, - }, - { - name: "case 3", - args: args{byt: '9'}, - want: true, - }, - { - name: "case 4", - args: args{byt: '*'}, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := analyzeNum(tt.args.byt); got != tt.want { - t.Errorf("analyzeNum() = %v, want %v", got, tt.want) - } - }) - } -} - -func Test_analyzeAlphanum(t *testing.T) { - type args struct { - byt byte - } - tests := []struct { - name string - args args - want bool - }{ - { - name: "case 0", - args: args{byt: '0'}, - want: true, - }, - { - name: "case 1", - args: args{byt: 'a'}, - want: false, - }, - { - name: "case 2", - args: args{byt: 'A'}, - want: true, - }, - { - name: "case 3", - args: args{byt: '9'}, - want: true, - }, - { - name: "case 4", - args: args{byt: '*'}, - want: true, + name: "test 1", + args: args{"茗荷"}, + want: []byte{0x1A, 0xAA, 0x06, 0x97}, }, - { - name: "case 5", - args: args{byt: '?'}, - want: false, - }, - { - name: "case 6", - args: args{byt: '&'}, - want: false, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - if got := analyzeAlphaNum(tt.args.byt); got != tt.want { - t.Errorf("analyzeAlphaNum() = %v, want %v", got, tt.want) - } - }) } -} -func Test_anlayzeMode(t *testing.T) { - type args struct { - raw []byte - } - tests := []struct { - name string - args args - want encMode - }{ - { - name: "case 0", - args: args{raw: []byte("123120899231")}, - want: EncModeNumeric, - }, - { - name: "case 1", - args: args{raw: []byte(":/1231H208*99231FBJO")}, - want: EncModeAlphanumeric, - }, - { - name: "case 2", - args: args{raw: []byte("hahah1298312hG&^FBJO@jhgG*")}, - want: EncModeByte, - }, - { - name: "case 3", - args: args{raw: []byte("JKAHDOIANKQOIHCMJKASJ")}, - want: EncModeAlphanumeric, - }, - { - name: "case 4", - args: args{raw: []byte("https://baidu.com?keyword=_JSO==GA")}, - want: EncModeByte, - }, - { - name: "case 5", - args: args{raw: []byte("这是汉字也应该是EncModeByte")}, - want: EncModeByte, - }, - { - name: "case 6 (swedish letter)", - args: args{raw: []byte("Övrigt aksldjlk Övrigt should JP encMode?")}, - want: EncModeByte, - }, - { - name: "issue#28", - args: args{raw: []byte("a")}, - want: EncModeByte, - }, - } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if got := analyzeEncodeModeFromRaw(tt.args.raw); got != tt.want { - t.Errorf("analyzeEncodeModeFromRaw() = %v, want %v", got, tt.want) + if got := toShiftJIS(tt.args.s); !bytes.Equal(got, tt.want) { + t.Errorf("toShiftJIS() = %v, want %v", got, tt.want) } }) } diff --git a/example/main.go b/example/main.go index e17f24c..5b59ccc 100644 --- a/example/main.go +++ b/example/main.go @@ -10,7 +10,7 @@ import ( func main() { repo() - //issue17() + // issue17() } func repo() { @@ -34,9 +34,9 @@ func repo() { 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") + // 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", + // 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) diff --git a/qrcode.go b/qrcode.go index 7cc7ad4..9dd4b8f 100644 --- a/qrcode.go +++ b/qrcode.go @@ -30,7 +30,6 @@ func NewWith(text string, opts ...EncodeOption) (*QRCode, error) { func build(text string, option *encodingOption) (*QRCode, error) { qrc := &QRCode{ sourceText: text, - sourceRawBytes: []byte(text), dataBSet: nil, mat: nil, ecBSet: nil, @@ -51,8 +50,8 @@ func build(text string, option *encodingOption) (*QRCode, error) { // QRCode contains fields to generate QRCode matrix, outputImageOptions to Draw image, // etc. type QRCode struct { - sourceText string // sourceText input text - sourceRawBytes []byte // raw Data to transfer + sourceText string // sourceText input text + // sourceRawBytes []byte // raw Data to transfer dataBSet *binary.Binary // final data bit stream of encode data mat *Matrix // matrix grid to store final bitmap @@ -89,7 +88,10 @@ func (q *QRCode) Dimension() int { func (q *QRCode) init() (err error) { // choose encode mode (num, alpha num, byte, Japanese) if q.encodingOption.EncMode == EncModeAuto { - q.encodingOption.EncMode = analyzeEncodeModeFromRaw(q.sourceRawBytes) + q.encodingOption.EncMode, err = analyzeEncodeModeFromRaw(q.sourceText) + if err != nil { + return fmt.Errorf("init: analyze encode mode failed: %v", err) + } } // choose version @@ -140,7 +142,7 @@ func (q *QRCode) calcVersion() (ver *version, err error) { // automatically parse version if needAnalyze { // analyzeVersion the input data to choose to adapt version - analyzed, err2 := analyzeVersion(q.sourceRawBytes, opt.EcLevel, opt.EncMode) + analyzed, err2 := analyzeVersion(q.sourceText, opt.EcLevel, opt.EncMode) if err2 != nil { err = fmt.Errorf("calcVersion: analyzeVersionAuto failed: %v", err2) return nil, err @@ -166,7 +168,7 @@ func (q *QRCode) dataEncoding() (blocks []dataBlock, err error) { var ( bset *binary.Binary ) - bset, err = q.encoder.Encode(q.sourceRawBytes) + bset, err = q.encoder.Encode(q.sourceText) if err != nil { err = fmt.Errorf("could not encode data: %v", err) return diff --git a/version.go b/version.go index 641c65b..760c621 100644 --- a/version.go +++ b/version.go @@ -7,6 +7,8 @@ import ( "sync" // "github.com/skip2/go-qrcode/bitset" + "unicode/utf8" + "github.com/yeqown/reedsolomon/binary" ) @@ -273,7 +275,7 @@ func loadVersion(lv int, ec ecLevel) version { // // check out http://muyuchengfeng.xyz/%E4%BA%8C%E7%BB%B4%E7%A0%81-%E5%AD%97%E7%AC%A6%E5%AE%B9%E9%87%8F%E8%A1%A8/ // for more details. -func analyzeVersion(raw []byte, ec ecLevel, mode encMode) (*version, error) { +func analyzeVersion(raw string, ec ecLevel, mode encMode) (*version, error) { step := 0 switch ec { case ErrorCorrectionLow: @@ -288,7 +290,7 @@ func analyzeVersion(raw []byte, ec ecLevel, mode encMode) (*version, error) { return nil, errInvalidErrorCorrectionLevel } - want, mark := len(raw), 0 + want, mark := utf8.RuneCountInString(raw), 0 for ; step < 160; step += 4 { switch mode { @@ -298,7 +300,7 @@ func analyzeVersion(raw []byte, ec ecLevel, mode encMode) (*version, error) { mark = versions[step].Cap.AlphaNumeric case EncModeByte: mark = versions[step].Cap.Byte - case EncModeJP: + case EncModeKanji: mark = versions[step].Cap.JP default: return nil, errMissMatchedEncodeType diff --git a/version_test.go b/version_test.go index 3051f58..9af578f 100644 --- a/version_test.go +++ b/version_test.go @@ -7,8 +7,8 @@ import ( "sync" "testing" - "github.com/stretchr/testify/require" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) func TestVersion_Dimension(t *testing.T) { @@ -70,7 +70,7 @@ func Test_analyzeVersion(t *testing.T) { v3 := loadVersion(23, ErrorCorrectionMedium) type args struct { - raw []byte + raw string ecLv ecLevel eMode encMode } @@ -83,7 +83,7 @@ func Test_analyzeVersion(t *testing.T) { { name: "case 0", args: args{ - raw: []byte("TEXT"), + raw: "TEXT", ecLv: ErrorCorrectionMedium, eMode: EncModeAlphanumeric, }, @@ -93,7 +93,7 @@ func Test_analyzeVersion(t *testing.T) { { name: "case 1", args: args{ - raw: []byte(strings.Repeat("TEXT", 30)), + raw: strings.Repeat("TEXT", 30), ecLv: ErrorCorrectionMedium, eMode: EncModeAlphanumeric, }, @@ -103,7 +103,7 @@ func Test_analyzeVersion(t *testing.T) { { name: "case 2", args: args{ - raw: []byte(strings.Repeat("TEXT", 300)), + raw: strings.Repeat("TEXT", 300), ecLv: ErrorCorrectionMedium, eMode: EncModeAlphanumeric, }, @@ -282,7 +282,7 @@ func Benchmark_loadVersion_bottom(b *testing.B) { } func Benchmark_analyzeVersion_short(b *testing.B) { - source := []byte("text") + source := "text" for i := 0; i < b.N; i++ { _, _ = analyzeVersion(source, ErrorCorrectionMedium, EncModeByte) @@ -290,7 +290,7 @@ func Benchmark_analyzeVersion_short(b *testing.B) { } func Benchmark_analyzeVersion_middle(b *testing.B) { - source := []byte(strings.Repeat("text", 30)) + source := strings.Repeat("text", 30) for i := 0; i < b.N; i++ { _, _ = analyzeVersion(source, ErrorCorrectionMedium, EncModeByte) @@ -298,7 +298,7 @@ func Benchmark_analyzeVersion_middle(b *testing.B) { } func Benchmark_analyzeVersion_long(b *testing.B) { - source := []byte(strings.Repeat("text", 300)) + source := strings.Repeat("text", 300) for i := 0; i < b.N; i++ { _, _ = analyzeVersion(source, ErrorCorrectionMedium, EncModeByte)