diff --git a/go.mod b/go.mod index 689eb88..f33a202 100644 --- a/go.mod +++ b/go.mod @@ -58,6 +58,7 @@ require ( github.com/mattn/go-sqlite3 v1.14.22 // indirect github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d // indirect github.com/ncruces/go-strftime v0.1.9 // indirect + github.com/piglig/go-qr v0.2.4 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/spf13/cast v1.6.0 // indirect github.com/spf13/cobra v1.8.0 // indirect diff --git a/go.sum b/go.sum index 39665fe..c6b2eec 100644 --- a/go.sum +++ b/go.sum @@ -271,6 +271,8 @@ github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d h1:5PJl274Y63IEHC+7izoQ github.com/mgutz/ansi v0.0.0-20200706080929-d51e80ef957d/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4= github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls= +github.com/piglig/go-qr v0.2.4 h1:G/fY3/Oq0NI1oc0lEhBv75QXUtIW/FmcL9l8D1jIo1M= +github.com/piglig/go-qr v0.2.4/go.mod h1:funyXL4IdgMPcbICoVm1XweMtZy7Px3kyITTENkmA5w= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= diff --git a/goqr/bit_buffer.go b/goqr/bit_buffer.go deleted file mode 100644 index 0601c6b..0000000 --- a/goqr/bit_buffer.go +++ /dev/null @@ -1,81 +0,0 @@ -package goqr - -import ( - "fmt" - "math" -) - -// BitSet defines an interface that allows manipulation of a bitset. -type BitSet interface { - getBit(i int) bool - set(i int, value bool) - len() int -} - -type BitBuffer []bool - -// len returns the length of the BitBuffer. -func (b *BitBuffer) len() int { - return len(*b) -} - -// set sets the bit at position i in the BitBuffer to value. -func (b *BitBuffer) set(i int, value bool) { - if i >= len(*b) { - b.grow(1 + i) // If index is beyond current length, grow buffer. - } - (*b)[i] = value -} - -// getBit returns the bit at position i in the BitBuffer. -func (b *BitBuffer) getBit(i int) bool { - if i >= len(*b) { - return false - } - return (*b)[i] -} - -// grow increases the size of the BitBuffer. -func (b *BitBuffer) grow(size int) { - res := make(BitBuffer, size) - copy(res, *b) - *b = res -} - -// appendBits appends val as a binary number of length bits to the end of the BitBuffer. -func (b *BitBuffer) appendBits(val, length int) error { - if length < 0 || length > 31 || (val>>uint(length)) != 0 { - return fmt.Errorf("value out of range") - } - if math.MaxInt32-b.len() < length { - return fmt.Errorf("maximum length reached") - } - for i := length - 1; i >= 0; i-- { - b.set(b.len(), getBit(val, i)) - } - return nil -} - -// appendData appends another BitBuffer to this BitBuffer. -func (b *BitBuffer) appendData(other *BitBuffer) error { - if other == nil { - return fmt.Errorf("BitBuffer is nil") - } - - if math.MaxInt32-b.len() < other.len() { - return fmt.Errorf("maximum length reached") - } - - for i := 0; i < other.len(); i++ { - bit := other.getBit(i) - b.set(b.len(), bit) - } - return nil -} - -// clone creates a copy of the BitBuffer. -func (b *BitBuffer) clone() *BitBuffer { - clone := make(BitBuffer, len(*b)) - copy(clone, *b) - return &clone -} diff --git a/goqr/error.go b/goqr/error.go deleted file mode 100644 index c45f0d9..0000000 --- a/goqr/error.go +++ /dev/null @@ -1,9 +0,0 @@ -package goqr - -type DataTooLongException struct { - Msg string -} - -func (d *DataTooLongException) Error() string { - return d.Msg -} diff --git a/goqr/qr_code.go b/goqr/qr_code.go deleted file mode 100644 index 8f70599..0000000 --- a/goqr/qr_code.go +++ /dev/null @@ -1,1019 +0,0 @@ -package goqr - -import ( - "errors" - "fmt" - "image" - "image/color" - "image/png" - "io" - "math" - "os" - "path/filepath" - "strings" -) - -// Ecc is the representation of an error correction level in a QR Code symbol. -type Ecc int - -const ( - Low Ecc = iota // 7% of codewords can be restored - Medium // 15% of codewords can be restored - Quartile // 25% of codewords can be restored - High // 30% of codewords can be restored -) - -// eccFormats maps the ECC to its respective format bits. -var eccFormats = [...]int{1, 0, 3, 2} - -// FormatBits method gets the format bits associated with the error correction level. -func (e Ecc) FormatBits() int { - return eccFormats[e] -} - -// Minimum(1) and Maximum(40) version numbers based on the QR Code Model 2 standard -const ( - MinVersion = 1 - MaxVersion = 40 -) - -// penaltyN1 - N4 are constants used in QR Code masking penalty rules. -const ( - penaltyN1 = 3 - penaltyN2 = 3 - penaltyN3 = 40 - penaltyN4 = 10 -) - -// getEccCodeWordsPerBlock function provides a lookup table for the number of error correction code words per block -// for different versions and error correction levels of the QR Code. -func getEccCodeWordsPerBlock() [][]int8 { - return [][]int8{ - // Version: (note that index 0 is for padding, and is set to an illegal value) - //0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level - {-1, 7, 10, 15, 20, 26, 18, 20, 24, 30, 18, 20, 24, 26, 30, 22, 24, 28, 30, 28, 28, 28, 28, 30, 30, 26, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Low - {-1, 10, 16, 26, 18, 24, 16, 18, 22, 22, 26, 30, 22, 22, 24, 24, 28, 28, 26, 26, 26, 26, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28, 28}, // Medium - {-1, 13, 22, 18, 26, 18, 24, 18, 22, 20, 24, 28, 26, 24, 20, 30, 24, 28, 28, 26, 30, 28, 30, 30, 30, 30, 28, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // Quartile - {-1, 17, 28, 22, 16, 22, 28, 26, 26, 24, 28, 24, 28, 22, 24, 24, 30, 28, 28, 26, 28, 30, 24, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30, 30}, // High - } -} - -// getNumErrorCorrectionBlocks function provides a lookup table for the number of error correction blocks required -// for different versions and error correction levels of the QR Code. -func getNumErrorCorrectionBlocks() [][]int8 { - return [][]int8{ - // Version: (note that index 0 is for padding, and is set to an illegal value) - //0, 1, 2, 3, 4, 5, 6, 7, 8, 9,10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40 Error correction level - {-1, 1, 1, 1, 1, 1, 2, 2, 2, 2, 4, 4, 4, 4, 4, 6, 6, 6, 6, 7, 8, 8, 9, 9, 10, 12, 12, 12, 13, 14, 15, 16, 17, 18, 19, 19, 20, 21, 22, 24, 25}, // Low - {-1, 1, 1, 1, 2, 2, 4, 4, 4, 5, 5, 5, 8, 9, 9, 10, 10, 11, 13, 14, 16, 17, 17, 18, 20, 21, 23, 25, 26, 28, 29, 31, 33, 35, 37, 38, 40, 43, 45, 47, 49}, // Medium - {-1, 1, 1, 2, 2, 4, 4, 6, 6, 8, 8, 8, 10, 12, 16, 12, 17, 16, 18, 21, 20, 23, 23, 25, 27, 29, 34, 34, 35, 38, 40, 43, 45, 48, 51, 53, 56, 59, 62, 65, 68}, // Quartile - {-1, 1, 1, 2, 4, 4, 4, 5, 6, 8, 8, 11, 11, 16, 16, 18, 16, 19, 21, 25, 25, 25, 34, 30, 32, 35, 37, 40, 42, 45, 48, 51, 54, 57, 60, 63, 66, 70, 74, 77, 81}, // High - } -} - -// QrCodeImgConfig is the representation of the QR Code generation configuration -type QrCodeImgConfig struct { - scale, border int - light, dark color.Color -} - -// NewQrCodeImgConfig is used to create a QR code generation config with the provided scale of image(scale), border of image(border), -// and the default light and dark color are white and black. -func NewQrCodeImgConfig(scale int, border int) *QrCodeImgConfig { - return &QrCodeImgConfig{scale: scale, border: border, light: color.White, dark: color.Black} -} - -func (q *QrCodeImgConfig) Valid() error { - if q.scale <= 0 { - return errors.New("scale must be positive") - } - - if q.border < 0 { - return errors.New("border must be non-negative") - } - - return nil -} - -// Light gets light color from QrCodeImgConfig -func (q *QrCodeImgConfig) Light() color.Color { - return q.light -} - -// SetLight sets light color in the QrCodeImgConfig -func (q *QrCodeImgConfig) SetLight(light color.Color) { - q.light = light -} - -// Dark gets dark color from QrCodeImgConfig -func (q *QrCodeImgConfig) Dark() color.Color { - return q.dark -} - -// SetDark sets dark color in the QrCodeImgConfig -func (q *QrCodeImgConfig) SetDark(dark color.Color) { - q.dark = dark -} - -// QrCode is the representation of a QR code -type QrCode struct { - version int // Version of the QR Code. - size int // Size of the QR Code. - errorCorrectionLevel Ecc // Error correction level (ECC) of the QR Code. - mask int // Mask pattern of the QR Code. - - modules [][]bool // 2D boolean matrix representing dark modules in the QR Code. - isFunction [][]bool // 2D boolean matrix distinguishing function from data modules. -} - -// newQrCode is used to create a new QR code with the provided version(ver), error correction level(ecl), -// data codewords (dataCodewords) and mask value (msk). -func newQrCode(ver int, ecl Ecc, dataCodewords []byte, msk int) (*QrCode, error) { - if msk < -1 || msk > 7 { - return nil, errors.New("mask value out of range") - } - - qrCode := &QrCode{ - version: ver, - size: ver*4 + 17, // Calculate size based on version - errorCorrectionLevel: ecl, - } - - modules := make([][]bool, qrCode.size) - isFunction := make([][]bool, qrCode.size) - for i := 0; i < qrCode.size; i++ { - modules[i] = make([]bool, qrCode.size) - isFunction[i] = make([]bool, qrCode.size) - } - qrCode.modules = modules - qrCode.isFunction = isFunction - - // Draw function patterns on the QR Code - qrCode.drawFunctionPatterns() - - // Add error correction and interleave the data codewords - allCodewords, err := qrCode.addEccAndInterLeave(dataCodewords) - if err != nil { - return nil, err - } - - err = qrCode.drawCodewords(allCodewords) - if err != nil { - return nil, err - } - - // If mask is -1, choose the best mask based on minimizing penalty score - if msk == -1 { - minPenalty := math.MaxInt32 - for i := 0; i < 8; i++ { - err = qrCode.applyMask(i) - if err != nil { - return nil, err - } - qrCode.drawFormatBits(i) - penalty := qrCode.getPenaltyScore() - if penalty < minPenalty { - msk = i - minPenalty = penalty - } - err = qrCode.applyMask(i) - if err != nil { - return nil, err - } - } - } - - // Apply the selected mask - qrCode.mask = msk - err = qrCode.applyMask(msk) - if err != nil { - return nil, err - } - - // Draw format bits for the mask - qrCode.drawFormatBits(msk) - qrCode.isFunction = nil - - return qrCode, nil -} - -// GetSize returns the size of the QR code -func (q *QrCode) GetSize() int { - return q.size -} - -// GetModule checks if a module is dark at given coordinates. -func (q *QrCode) GetModule(x, y int) bool { - return 0 <= x && x < q.size && 0 <= y && y < q.size && q.modules[y][x] -} - -// setFunctionModule sets a given module's status and function status in QrCode. -// The method takes coordinates x, y and isDark - a flag indicating whether the module should be dark or not. -func (q *QrCode) setFunctionModule(x, y int, isDark bool) { - // Assigning darkness state to the respective module in the QR Code. - q.modules[y][x] = isDark - // Marking this module as a function module. - q.isFunction[y][x] = true -} - -// addEccAndInterLeave adds Error Correction Code (ECC) and interleaves to the data received. -// This method takes an array of bytes representing the data that needs to be encoded into a QR code. -// It returns the data with added ECC and after interleaving, or an error if something goes wrong. -func (q *QrCode) addEccAndInterLeave(data []byte) ([]byte, error) { - // Getting the number of data codewords for the current version and error correction level - numDataCodewords := getNumDataCodewords(q.version, q.errorCorrectionLevel) - - // Checking if the input data length is equal to the number of data codewords - if len(data) != numDataCodewords { - return nil, errors.New("invalid argument") - } - - // Get the number of blocks and block ECC length based on the errorCorrectionLevel and QR code version - numBlocks := getNumErrorCorrectionBlocks()[q.errorCorrectionLevel][q.version] - blockEccLen := getEccCodeWordsPerBlock()[q.errorCorrectionLevel][q.version] - rawCodewords := getNumRawDataModules(q.version) / 8 - - // Calculate the number of short blocks - numShortBlocks := int(numBlocks) - rawCodewords%int(numBlocks) - // Calculate the length of short blocks - shortBlockLen := rawCodewords / int(numBlocks) - - blocks := make([][]byte, numBlocks) - // Compute reed solomon divisor - rsDiv, err := reedSolomonComputeDivisor(int(blockEccLen)) - if err != nil { - return nil, err - } - for i, k := 0, 0; i < int(numBlocks); i++ { - index := 1 - if i < numShortBlocks { - index = 0 - } - - // Prepare the data to be encoded - dat := make([]byte, shortBlockLen-int(blockEccLen)+index) - copy(dat, data[k:k+shortBlockLen-int(blockEccLen)+index]) - k += len(dat) - - // Prepare the block to store the encoded data and the ECC - block := make([]byte, shortBlockLen+1) - copy(block, dat) - - // Calculate the ECC for the data - ecc := reedSolomonComputeRemainder(dat, rsDiv) - - // Append the ECC to the end of the data - copy(block[len(block)-int(blockEccLen):], ecc) - blocks[i] = block - } - - res := make([]byte, rawCodewords) - for i, k := 0, 0; i < len(blocks[0]); i++ { - for j := 0; j < len(blocks); j++ { - if i != shortBlockLen-int(blockEccLen) || j >= numShortBlocks { - res[k] = blocks[j][i] - k++ - } - } - } - return res, nil -} - -// drawCodewords fills up the QR code's modules based on the input data. -func (q *QrCode) drawCodewords(data []byte) error { - // getNumRawDataModules estimates the number of data bits that can be stored for a given version of QR Code - // The result is divided by 8 to find the number of bytes available. - numRawDataModules := getNumRawDataModules(q.version) / 8 - if len(data) != numRawDataModules { - return errors.New("illegal argument") - } - - i := 0 - // Iterate over QR Code grid from right-to-left. - for right := q.size - 1; right >= 1; right -= 2 { - // Skip column at index 6 as it's reserved for timing patterns in QR Code. - if right == 6 { - right = 5 - } - // Iterate over each row. - for vert := 0; vert < q.size; vert++ { - // Check two adjacent pixels. - for j := 0; j < 2; j++ { - x := right - j - // This checks if we're going upwards in the current two-column section of the QR Code. - upward := ((right + 1) & 2) == 0 - y := vert - if upward { - // If we're going upwards, calculate the corresponding y-coordinate. - y = q.size - 1 - vert - } - // Check if current module is not a function pattern and there's data left to encode. - if !q.isFunction[y][x] && i < len(data)*8 { - // Write bits into QR Code. Use bitwise operations to extract individual bits from each byte of data. - q.modules[y][x] = getBit(int(data[i>>3]), 7-(i&7)) - i++ - } - } - } - } - return nil -} - -// applyMask applies the chosen mask pattern to the QR code. -func (q *QrCode) applyMask(msk int) error { - if msk < 0 || msk > 7 { - return errors.New("mask value out of range") - } - - for y := 0; y < q.size; y++ { - for x := 0; x < q.size; x++ { - // A boolean variable which will decide if the current cell needs to be inverted or not. - var invert bool - // Each case corresponds to a different mask pattern defined by the QR code specification. - switch msk { - case 0: - invert = (x+y)%2 == 0 - case 1: - invert = y%2 == 0 - case 2: - invert = x%3 == 0 - case 3: - invert = (x+y)%3 == 0 - case 4: - invert = (x/3+y/2)%2 == 0 - case 5: - invert = x*y%2+x*y%3 == 0 - case 6: - invert = (x*y%2+x*y%3)%2 == 0 - case 7: - invert = ((x+y)%2+x*y%3)%2 == 0 - } - // Invert the cell's color if it is not a function pattern cell and the 'invert' variable is true. - q.modules[y][x] = q.modules[y][x] != (invert && !q.isFunction[y][x]) - } - } - return nil -} - -// getPenaltyScore is a method of the QrCode struct that -// calculates and returns a penalty score based on several criteria. -func (q *QrCode) getPenaltyScore() int { - res := 0 - // Calculate penalties in the horizontal direction - for y := 0; y < q.size; y++ { - runColor, runX := false, 0 - runHistory := make([]int, 7) - for x := 0; x < q.size; x++ { - if q.modules[y][x] == runColor { - runX++ - if runX == 5 { - res += penaltyN1 - } else if runX > 5 { - res++ - } - } else { - q.finderPenaltyAddHistory(runX, runHistory) - // If the color run was for white pixels, calculate additional penalties - if !runColor { - res += q.finderPenaltyCountPatterns(runHistory) * penaltyN3 - } - runColor = q.modules[y][x] - runX = 1 - } - } - // After evaluating all pixels in the row, check for finder pattern violation - res += q.finderPenaltyTerminateAndCount(runColor, runX, runHistory) * penaltyN3 - } - - // Repeat similar process for vertical direction - for x := 0; x < q.size; x++ { - runColor, runY := false, 0 - runHistory := make([]int, 7) - for y := 0; y < q.size; y++ { - if q.modules[y][x] == runColor { - runY++ - if runY == 5 { - res += penaltyN1 - } else if runY > 5 { - res++ - } - } else { - q.finderPenaltyAddHistory(runY, runHistory) - if !runColor { - res += q.finderPenaltyCountPatterns(runHistory) * penaltyN3 - } - runColor = q.modules[y][x] - runY = 1 - } - } - res += q.finderPenaltyTerminateAndCount(runColor, runY, runHistory) * penaltyN3 - } - - for y := 0; y < q.size-1; y++ { - for x := 0; x < q.size-1; x++ { - color := q.modules[y][x] - // If 2x2 block has the same color, increase penalty - if color == q.modules[y][x+1] && - color == q.modules[y+1][x] && - color == q.modules[y+1][x+1] { - res += penaltyN2 - } - } - } - - // Count the total number of dark modules - dark := 0 - for _, row := range q.modules { - for _, color := range row { - if color { - dark++ - } - } - } - - // Compute the ratio of dark modules to total modules, compare to ideal ratio and apply penalty - total := q.size * q.size - k := (abs(dark*20-total*10)+total-1)/total - 1 - res += k * penaltyN4 - return res -} - -// drawAlignmentPattern draws an alignment pattern centered at the given coordinates (x, y). -// Alignment patterns are part of the QR code that enable readers to accurately read the data, -// even if the image is distorted (e.g., skewed or twisted). -func (q *QrCode) drawAlignmentPattern(x, y int) { - // We scan a 5x5 region around the center module (specified by x, y). - for dy := -2; dy <= 2; dy++ { - for dx := -2; dx <= 2; dx++ { - // For each scanned module, we use setFunctionModule to either color it or not, - // depending on its distance from the central module. - // Modules farther away from the center are made dark (isDark = true), except for those directly adjacent to the center. - q.setFunctionModule(x+dx, y+dy, max(abs(dx), abs(dy)) != 1) - } - } -} - -// drawFinderPattern draws a finder pattern centered at the given coordinates (x, y). -// Finder patterns are located at three corners in QR Codes and allow the QR Code to be read from any direction. -func (q *QrCode) drawFinderPattern(x, y int) { - for dy := -4; dy <= 4; dy++ { - for dx := -4; dx <= 4; dx++ { - dist := max(abs(dx), abs(dy)) - xx, yy := x+dx, y+dy - if 0 <= xx && xx < q.size && 0 <= yy && yy < q.size { - q.setFunctionModule(xx, yy, dist != 2 && dist != 4) - } - } - } -} - -// drawVersion encodes version information into the QR Code. Version information is only included -// for QR Codes with a version number of 7 or higher. -func (q *QrCode) drawVersion() { - if q.version < 7 { - return - } - - rem := q.version - for i := 0; i < 12; i++ { - rem = (rem << 1) ^ ((rem >> 11) * 0x1F25) // Perform calculation to derive final remainder - } - bits := q.version<<12 | rem - - // Draw two copies - for i := 0; i < 18; i++ { - bit := getBit(bits, i) - a := q.size - 11 + i%3 - b := i / 3 - q.setFunctionModule(a, b, bit) - q.setFunctionModule(b, a, bit) - } -} - -// drawFunctionPatterns adds all the function patterns (including format/version info, timing patterns, -// alignment patterns, and finder patterns) to the QR Code matrix. -func (q *QrCode) drawFunctionPatterns() { - // Draw horizontal and vertical timing patterns - for i := 0; i < q.size; i++ { - q.setFunctionModule(6, i, i%2 == 0) - q.setFunctionModule(i, 6, i%2 == 0) - } - - // Draw 3 finder patterns - q.drawFinderPattern(3, 3) - q.drawFinderPattern(q.size-4, 3) - q.drawFinderPattern(3, q.size-4) - - // Get the positions of the alignment patterns, then draw them - alignPatPos := q.getAlignmentPatternPositions() - numAlign := len(alignPatPos) - for i := 0; i < numAlign; i++ { - for j := 0; j < numAlign; j++ { - if !(i == 0 && j == 0 || i == 0 && j == numAlign-1 || i == numAlign-1 && j == 0) { - q.drawAlignmentPattern(alignPatPos[i], alignPatPos[j]) - } - } - } - - q.drawFormatBits(0) - q.drawVersion() -} - -// getAlignmentPatternPositions returns the positions of the alignment patterns in the QR code based on its version. -// If the version is 1, it returns an empty int slice because there's no alignment pattern in this case. -func (q *QrCode) getAlignmentPatternPositions() []int { - if q.version == 1 { - return []int{} - } else { - numAlign := q.version/7 + 2 // Calculation of number of alignment patterns based on version of QR Code. - step := 0 - if q.version == 32 { - step = 26 - } else { - step = (q.version*4 + numAlign*2 + 1) / (numAlign*2 - 2) * 2 // Calculation of step size for placing the alignment patterns. - } - - res := make([]int, numAlign) - res[0] = 6 - for i, pos := len(res)-1, q.size-7; i >= 1; { - res[i] = pos - i-- - pos -= step - } - - return res - } -} - -// drawFormatBits encodes format information (error correction level and mask number) into the QR Code's format bits. -func (q *QrCode) drawFormatBits(msk int) { - data := q.errorCorrectionLevel.FormatBits()<<3 | msk - rem := data - for i := 0; i < 10; i++ { - rem = (rem << 1) ^ ((rem >> 9) * 0x537) // Computes the remainder of the polynomial division - } - - bits := (data<<10 | rem) ^ 0x5412 // Combines the data, remainder and additional bit string - - for i := 0; i <= 5; i++ { - q.setFunctionModule(8, i, getBit(bits, i)) - } - q.setFunctionModule(8, 7, getBit(bits, 6)) - q.setFunctionModule(8, 8, getBit(bits, 7)) - q.setFunctionModule(7, 8, getBit(bits, 8)) - - for i := 9; i < 15; i++ { - q.setFunctionModule(14-i, 8, getBit(bits, i)) - } - - for i := 0; i < 8; i++ { - q.setFunctionModule(q.size-1-i, 8, getBit(bits, i)) - } - - for i := 8; i < 15; i++ { - q.setFunctionModule(8, q.size-15+i, getBit(bits, i)) - } - q.setFunctionModule(8, q.size-8, true) -} - -// PNG generates a PNG image file for the QR code with QrCodeImgConfig and saves it to given file path -func (q *QrCode) PNG(config *QrCodeImgConfig, filePath string) error { - err := q.validateWritePNGConfig(config) - if err != nil { - return err - } - - pngFile, err := os.Create(filePath) - if err != nil { - return fmt.Errorf("error creating PNG file: %w", err) - } - defer pngFile.Close() - - return q.doWriteAsPNG(config, pngFile) -} - -// WriteAsPNG writes the QR code as PNG with QrCodeImgConfig to the provided io.Writer. -func (q *QrCode) WriteAsPNG(config *QrCodeImgConfig, writer io.Writer) error { - err := q.validateWritePNGConfig(config) - if err != nil { - return err - } - - return q.doWriteAsPNG(config, writer) -} - -// validateWritePNGConfig validates the parameters to write the QR code as PNG -func (q *QrCode) validateWritePNGConfig(config *QrCodeImgConfig) error { - err := config.Valid() - if err != nil { - return err - } - - // Ensure that the border size combined with QR code size does not exceed the maximum allowed integer value after scaling. - if config.border > (math.MaxInt32/2) || int64(q.GetSize())+int64(config.border)*2 > math.MaxInt32/int64(config.scale) { - return errors.New("scale or border too large") - } - - return nil -} - -// doWriteAsPNG writes the QR code as PNG with QrCodeImgConfig to the provided io.Writer. -func (q *QrCode) doWriteAsPNG(config *QrCodeImgConfig, writer io.Writer) error { - rgba := q.toImage(config) - - if err := png.Encode(writer, rgba); err != nil { - return fmt.Errorf("failed to encode PNG: %w", err) - } - - return nil -} - -// toImage generates an RGBA image based on QrCodeImgConfig -func (q *QrCode) toImage(config *QrCodeImgConfig) *image.RGBA { - size := q.GetSize() + config.border*2 - imageWidth := size * config.scale - imageHeight := size * config.scale - result := image.NewRGBA(image.Rect(0, 0, imageWidth, imageHeight)) - for y := 0; y < imageHeight; y++ { - for x := 0; x < imageWidth; x++ { - moduleX := x/config.scale - config.border - moduleY := y/config.scale - config.border - isDark := q.GetModule(moduleX, moduleY) - if isDark { - result.Set(x, y, config.Dark()) - } else { - result.Set(x, y, config.Light()) - } - } - } - return result -} - -// SVG generates a SVG file for the QR code with QrCodeImgConfig, light, dark color and saves it to given file path -func (q *QrCode) SVG(config *QrCodeImgConfig, filePath, light, dark string) error { - err := config.Valid() - if err != nil { - return err - } - - if ext := filepath.Ext(filePath); ext != ".svg" { - return fmt.Errorf("file type:%v invalid", ext) - } - - svgFile, err := os.Create(filePath) - if err != nil { - return fmt.Errorf("error creating SVG file: %w", err) - } - defer svgFile.Close() - - return q.doWriteAsSVG(config, svgFile, light, dark) -} - -// WriteAsSVG writes the QR code as SVG with QrCodeImgConfig, light, dark color to the provided io.Writer. -// -// light is the color to use for light sections of the QR code, for example, "#FFFFFF". -// dark is the color to use for dark sections of the QR code, for example, "#000000". -func (q *QrCode) WriteAsSVG(config *QrCodeImgConfig, writer io.Writer, light, dark string) error { - err := config.Valid() - if err != nil { - return err - } - - return q.doWriteAsSVG(config, writer, light, dark) -} - -// doWriteAsSVG writes the QR code as SVG with QrCodeImgConfig, light, dark color to the provided io.Writer. -// -// light is the color to use for light sections of the QR code, for example, "#FFFFFF". -// dark is the color to use for dark sections of the QR code, for example, "#000000". -func (q *QrCode) doWriteAsSVG(config *QrCodeImgConfig, writer io.Writer, light, dark string) error { - svg := q.toSVGString(config, light, dark) - - _, err := writer.Write([]byte(svg)) - - if err != nil { - return fmt.Errorf("error writing SVG: %w", err) - } - - return nil -} - -// toSVGString generates a SVG string image with QrCodeImgConfig, light and dark color -func (q *QrCode) toSVGString(config *QrCodeImgConfig, lightColor, darkColor string) string { - brd := int64(config.border) - scl := int64(config.scale) - size := int64(q.GetSize()) - - sb := strings.Builder{} - sb.Grow(128) - sb.WriteString("\n") - sb.WriteString("\n") - sb.WriteString(fmt.Sprintf("\n", - (size*scl)+brd*2, (size*scl)+brd*2)) - sb.WriteString(fmt.Sprintf("\t\n", (size*scl)+brd*2, (size*scl)+brd*2, lightColor)) - sb.WriteString("\t\n", darkColor)) - sb.WriteString("\n") - - return sb.String() -} - -// EncodeText takes a string and an error correction level (ecl), -// encodes the text to segments and returns a QR code or an error. -func EncodeText(text string, ecl Ecc) (*QrCode, error) { - segs, err := MakeSegments(text) - if err != nil { - return nil, err - } - - return EncodeStandardSegments(segs, ecl) -} - -// EncodeBinary takes a byte array and an error correction level (ecl), -// converts the bytes to QR code segments and returns a QR code or an error. -func EncodeBinary(data []byte, ecl Ecc) (*QrCode, error) { - segs, err := MakeBytes(data) - if err != nil { - return nil, err - } - - return EncodeStandardSegments([]*QrSegment{segs}, ecl) -} - -// EncodeStandardSegments takes QR code segments and an error correction level, -// creates a standard QR code using these parameters and returns it or an error. -func EncodeStandardSegments(segs []*QrSegment, ecl Ecc) (*QrCode, error) { - return EncodeSegments(segs, ecl, MinVersion, MaxVersion, -1, true) -} - -// EncodeSegments is a more flexible version of EncodeStandardSegments. It allows -// the specification of minVer, maxVer, mask in addition to the regular parameters. -// Returns a QR code object or an error. -func EncodeSegments(segs []*QrSegment, ecl Ecc, minVer, maxVer, mask int, boostEcl bool) (*QrCode, error) { - if segs == nil { - return nil, errors.New("slice of QrSegment is nil") - } - - if !isValidVersion(minVer, maxVer) { - return nil, errors.New("invalid version") - } - - // Loop over all versions between minVer and maxVer to find a suitable one - version, dataUsedBits := 0, 0 - for version = minVer; ; version++ { - // Calculate data capacity bits - dataCapacityBits := getNumDataCodewords(version, ecl) * 8 - // Count total bits used - dataUsedBits = getTotalBits(segs, version) - if dataUsedBits != -1 && dataUsedBits <= dataCapacityBits { - break - } - - // If no suitable version found then throw a Segment too long error - if version >= maxVer { - msg := "Segment too long" - if dataUsedBits != -1 { - msg = fmt.Sprintf("Data length = %d bits, Max capacity = %d bits", dataUsedBits, dataCapacityBits) - } - return nil, &DataTooLongException{Msg: msg} - } - } - - // If boostEcl is set to true, try to upgrade the error correction level - // as far as the data can fit. - for _, newEcl := range []Ecc{Medium, Quartile, High} { - numDataCodewords := getNumDataCodewords(version, newEcl) - if boostEcl && dataUsedBits <= numDataCodewords*8 { - ecl = newEcl - } - } - - bb := BitBuffer{} - for _, seg := range segs { - if seg == nil { - continue - } - - err := bb.appendBits(seg.mode.modeBits, 4) - if err != nil { - return nil, err - } - err = bb.appendBits(seg.numChars, seg.mode.numCharCountBits(version)) - if err != nil { - return nil, err - } - err = bb.appendData(seg.data) - if err != nil { - return nil, err - } - } - - // Getting the final data capacity after all segments have been processed. - dataCapacityBits := getNumDataCodewords(version, ecl) * 8 - err := bb.appendBits(0, min(4, dataCapacityBits-bb.len())) - if err != nil { - return nil, err - } - - err = bb.appendBits(0, (8-bb.len()%8)%8) - if err != nil { - return nil, err - } - - // Writing pad bytes until the BitBuffer length reaches the final data capacity - for padByte := 0xEC; bb.len() < dataCapacityBits; padByte ^= 0xEC ^ 0x11 { - err = bb.appendBits(padByte, 8) - if err != nil { - return nil, err - } - } - - dataCodewords := make([]byte, bb.len()/8) - for i := 0; i < bb.len(); i++ { - bit := 0 - if bb.getBit(i) { - bit = 1 - } - dataCodewords[i>>3] |= byte(bit << (7 - (i & 7))) - } - return newQrCode(version, ecl, dataCodewords, mask) -} - -// isValidVersion is a function that checks if the given minVer and maxVer are within the valid QR code version range. -// The function returns true if both minVer and maxVer lie between the constant values MinVersion and MaxVersion (inclusive). -func isValidVersion(minVer, maxVer int) bool { - return MinVersion <= minVer && minVer <= maxVer && maxVer <= MaxVersion -} - -// getNumDataCodewords function calculates the number of data codewords for a given version and error correction level. -func getNumDataCodewords(ver int, ecl Ecc) int { - eccCodewordsPerBlock := getEccCodeWordsPerBlock() - numRawDataModules := getNumRawDataModules(ver) - numErrorCorrectionBlocks := getNumErrorCorrectionBlocks() - return numRawDataModules/8 - - int(eccCodewordsPerBlock[ecl][ver])*int(numErrorCorrectionBlocks[ecl][ver]) -} - -// finderPenaltyCountPatterns is a method of QrCode structure. -// It checks if patterns in runHistory follow a specific pattern (1:1:3:1:1 ratio) and returns a penalty score. -// The parameter runHistory is an array of recent pixel colors (0 or 1), where each number is a count of consecutive pixels of that color. -func (q *QrCode) finderPenaltyCountPatterns(runHistory []int) int { - n := runHistory[1] - - // core checks whether the middle part of the pattern matches the ratio 1:3:1 - core := n > 0 && runHistory[2] == n && runHistory[3] == n*3 && runHistory[4] == n && runHistory[5] == n - - res := 0 - // Check if both sides of the core pattern have at least 4 modules of white pixels. - if core && runHistory[0] >= n*4 && runHistory[6] >= n { - res = 1 - } - - // Check if both sides of the core pattern have at least 4 modules of black pixels. - if core && runHistory[6] >= n*4 && runHistory[0] >= n { - res += 1 - } - return res -} - -// finderPenaltyTerminateAndCount is a method of QrCode structure. -// It finalizes the history of seen modules when the color changes and calculates the penalty score. -// currentRunColor is a boolean representing the current color (false=white, true=black). -// currentRunLen is the count of consecutive modules of the same color. -// runHistory is an array of counts of alternating color runs, ending with the most recent color. -func (q *QrCode) finderPenaltyTerminateAndCount(currentRunColor bool, currentRunLen int, runHistory []int) int { - if currentRunColor { - q.finderPenaltyAddHistory(currentRunLen, runHistory) - currentRunLen = 0 - } - - currentRunLen += q.size - q.finderPenaltyAddHistory(currentRunLen, runHistory) - return q.finderPenaltyCountPatterns(runHistory) -} - -// getNumRawDataModules calculates the number of raw data modules for a specific QR code version. -func getNumRawDataModules(ver int) int { - // Calculate the size of the QR code grid. - // For each version, the size increases by 4 modules. - size := ver*4 + 17 - - // Start with the total number of modules in the QR code grid (size^2) - res := size * size - - // Subtract the three position detection patterns (each is 8x8 modules) - res -= 8 * 8 * 3 - - // Subtract the two horizontal timing patterns and the two vertical timing patterns - // (each is 15 modules long), along with the single dark module reserved for format information - res -= 15*2 + 1 - - // Subtract the border modules around the timing patterns - res -= (size - 16) * 2 - - // If version is 2 or higher, there are alignment patterns - if ver >= 2 { - // Get the number of alignment patterns for this version of QR code - numAlign := ver/7 + 2 - - // Subtract the space taken up by the alignment patterns (each is 5x5 modules) - res -= (numAlign - 1) * (numAlign - 1) * 25 - - // Subtract the two sets of border modules around the alignment patterns - res -= (numAlign - 2) * 2 * 20 - - // For versions 7 and above, subtract the space for version information (6x3 modules on both sides) - if ver >= 7 { - res -= 6 * 3 * 2 - } - } - return res -} - -// reedSolomonComputeDivisor computes a Reed-Solomon divisor for a given degree. -// The degree must be between 1 and 255 inclusive and determines the size of the output byte slice. -// The Reed-Solomon divisor computed by this function is used in error detection and correction codes. -func reedSolomonComputeDivisor(degree int) ([]byte, error) { - if degree < 1 || degree > 255 { - return nil, errors.New("degree out of range") - } - - res := make([]byte, degree) - res[degree-1] = 1 - - root := 1 - for i := 0; i < degree; i++ { - for j := 0; j < len(res); j++ { - // Multiply the jth element of res by root using Reed-Solomon multiplication - res[j] = byte(reedSolomonMultiply(int(res[j]&0xFF), root)) - if j+1 < len(res) { - res[j] ^= res[j+1] - } - } - root = reedSolomonMultiply(root, 0x02) - } - return res, nil -} - -// reedSolomonComputeRemainder computes the remainder of Reed-Solomon encoding. -// Reed-Solomon is an error correction technique used in QR codes (and other data storage). -// This function takes two parameters: data and divisor which are both slices of bytes. -func reedSolomonComputeRemainder(data, divisor []byte) []byte { - res := make([]byte, len(divisor)) - for _, b := range data { - factor := (b ^ res[0]) & 0xFF - copy(res, res[1:]) - res[len(res)-1] = byte(0) - for i := 0; i < len(res); i++ { - res[i] ^= byte(reedSolomonMultiply(int(divisor[i]&0xFF), int(factor))) - } - } - return res -} - -// reedSolomonMultiply is a helper function that performs multiplication in Galois Field 2^8. -// It takes two integer parameters x and y. -func reedSolomonMultiply(x, y int) int { - z := 0 - for i := 7; i >= 0; i-- { - z = (z << 1) ^ ((z >> 7) * 0x11D) - z ^= ((y >> i) & 1) * x - } - return z -} - -// finderPenaltyAddHistory is a method on the QrCode struct which updates -// the history of run lengths with the current run length. -// -// This method is likely part of the QR code error correction process, -// which uses historical data to identify and correct errors. -// -// The penalty score increases when there are many blocks of modules that have the same color in a row, -// or there are patterns that look similar to the position detection pattern. -// Here we're calculating the penalty based on the length of consecutive runs of same-color modules. -func (q *QrCode) finderPenaltyAddHistory(currentRunLen int, runHistory []int) { - if runHistory[0] == 0 { - currentRunLen += q.size - } - copy(runHistory[1:], runHistory[:len(runHistory)-1]) - runHistory[0] = currentRunLen -} - -// getBit gets the bit at position i from x. -func getBit(x, i int) bool { - return ((x >> uint(i)) & 1) != 0 -} diff --git a/goqr/qr_segment.go b/goqr/qr_segment.go deleted file mode 100644 index 6e09f8d..0000000 --- a/goqr/qr_segment.go +++ /dev/null @@ -1,299 +0,0 @@ -package goqr - -import ( - "errors" - "math" - "regexp" - "strconv" - "strings" -) - -// Mode is the representation of the mode of a QR code character. -type Mode struct { - modeBits int // 4-bit mode indicator used in QR Code's data encoding - numBitsCharCount []int // number of bits used for character count indicator for different versions -} - -// newMode creates a new Mode with a given mode indicator and character count bits -func newMode(mode int, ccbits ...int) Mode { - return Mode{ - modeBits: mode, - numBitsCharCount: ccbits, - } -} - -// numCharCountBits returns the number of character count bits -// for a specific QR code version. -func (m Mode) numCharCountBits(ver int) int { - return m.numBitsCharCount[(ver+7)/17] -} - -// Predefined Mode values as defined by the QR Code standard. -var ( - // Numeric mode is typically used for decimal digits (0 through 9). - Numeric = newMode(0x1, 10, 12, 14) - - // Alphanumeric mode includes digits 0-9, uppercase letters A-Z and nine special characters. - Alphanumeric = newMode(0x2, 9, 11, 13) - - // Byte mode can encode binary/byte data(default: ISO-8859-1) - Byte = newMode(0x4, 8, 16, 16) // Byte mode: binary/byte data (default: ISO-8859-1) - - // Kanji mode is used for encoding Japanese Kanji characters. - Kanji = newMode(0x8, 8, 10, 12) - - // Eci mode is designed for providing a method of extending features and functions - // in bar code symbols beyond those envisioned by the original standard. - Eci = newMode(0x7, 0, 0, 0) -) - -// getModeBits function to return the bits representing a particular mode -func (m Mode) getModeBits() int { - return m.modeBits -} - -// isNumeric checks if the mode is Numeric. -func (m Mode) isNumeric() bool { - return m.modeBits == Numeric.getModeBits() -} - -// isAlphanumeric checks if the mode is Alphanumeric. -func (m Mode) isAlphanumeric() bool { - return m.modeBits == Alphanumeric.getModeBits() -} - -// isByte checks if the mode is Byte. -func (m Mode) isByte() bool { - return m.modeBits == Byte.getModeBits() -} - -// isKanji checks if the mode is Kanji. -func (m Mode) isKanji() bool { - return m.modeBits == Kanji.getModeBits() -} - -// isEci checks if the mode is ECI. -func (m Mode) isEci() bool { - return m.modeBits == Eci.getModeBits() -} - -var ( - // numericRegex is a regular expression that matches strings consisting only of numbers (0-9). - numericRegex = regexp.MustCompile(`^\d+$`) - - // alphanumericRegex is a regular expression that matches strings - // consisting only of numeric characters, uppercase alphabets, and certain special characters ($%*+-./:). - alphanumericRegex = regexp.MustCompile(`^[0-9A-Z $%*+\-.\/:]*$`) - - // alphanumericCharset is a string listing all the characters that can be used in an alphanumeric string in QR codes. - alphanumericCharset = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ $%*+-./:" -) - -// QrSegment is the representation of a segment of a QR code. -type QrSegment struct { - mode Mode // The mode of the QR segment (e.g., Numeric, Alphanumeric, etc.) - numChars int // Number of characters in the segment - data *BitBuffer // The actual binary data represented by this QR segment -} - -// newQrSegment function creates a new QR segment with the given mode, number of characters, and data. -func newQrSegment(mode Mode, numCh int, data *BitBuffer) (*QrSegment, error) { - if numCh < 0 { - return nil, errors.New("invalid value") - } - return &QrSegment{ - mode: mode, - numChars: numCh, - data: data.clone(), // Use a cloned copy of the data to prevent modifications - }, nil -} - -// getData method clones and returns the BitBuffer data of the QR segment. -func (q *QrSegment) getData() *BitBuffer { - return q.data.clone() -} - -// MakeBytes converts a byte slice into a QR segment in Byte mode. -// It returns an error if the input data is nil. -func MakeBytes(data []byte) (*QrSegment, error) { - if data == nil { - return nil, errors.New("data is nil") - } - - bb := &BitBuffer{} - for _, b := range data { - err := bb.appendBits(int(b&0xFF), 8) // Append 8 bits at once to the bit buffer - if err != nil { - return nil, err - } - } - return newQrSegment(Byte, len(data), bb) -} - -// MakeNumeric converts a string of digits into a QR code segment in Numeric mode -// It returns an error if the string contains non-numeric characters. -func MakeNumeric(digits string) (*QrSegment, error) { - if !isNumeric(digits) { - return nil, errors.New("string contains non-numeric characters") - } - - bb := &BitBuffer{} - for i := 0; i < len(digits); { - n := min(len(digits)-i, 3) // find the length of the current chunk (up to 3 digits) - num, _ := strconv.Atoi(digits[i : i+n]) // convert the current chunk to an integer - err := bb.appendBits(num, n*3+1) - if err != nil { - return nil, err - } - i += n // advance to the next chunk - } - - return newQrSegment(Numeric, len(digits), bb) -} - -// isNumeric function takes a string as input and returns a boolean indicating whether the string is numeric. -// It uses the MatchString method on the numericRegex to check the input string. -func isNumeric(numb string) bool { - return numericRegex.MatchString(numb) -} - -// isAlphanumeric function takes a string as input and returns a boolean indicating whether the string is alphanumeric. -// It uses the MatchString method on the alphanumericRegex to check the input string. -func isAlphanumeric(text string) bool { - return alphanumericRegex.MatchString(text) -} - -func min(a, b int) int { - if a < b { - return a - } - return b -} - -func max(a, b int) int { - if a < b { - return b - } - return a -} - -func abs(x int) int { - if x < 0 { - return -x - } - return x -} - -// MakeAlphanumeric converts a string into a QR code segment in Alphanumeric mode -// It returns an error if the string contains non-alphanumeric characters. -func MakeAlphanumeric(text string) (*QrSegment, error) { - if !isAlphanumeric(text) { - return nil, errors.New("string contains unencodable characters in alphanumeric mode") - } - - bb := &BitBuffer{} - i := 0 - for ; i <= len(text)-2; i += 2 { - // Process each pair of characters in text. - temp := strings.IndexByte(alphanumericCharset, text[i]) * 45 - temp += strings.IndexByte(alphanumericCharset, text[i+1]) - err := bb.appendBits(temp, 11) - if err != nil { - return nil, err - } - } - - if i < len(text) { - err := bb.appendBits(strings.IndexByte(alphanumericCharset, text[i]), 6) - if err != nil { - return nil, err - } - } - - return newQrSegment(Alphanumeric, len(text), bb) -} - -// MakeSegments converts data into QR segments based on the mode of text (Numeric, Alphanumeric or Byte, etc). -func MakeSegments(text string) ([]*QrSegment, error) { - res := make([]*QrSegment, 0) - if text == "" { - } else if isNumeric(text) { - seg, err := MakeNumeric(text) - if err != nil { - return nil, err - } - - res = append(res, seg) - } else if isAlphanumeric(text) { - seg, err := MakeAlphanumeric(text) - if err != nil { - return nil, err - } - res = append(res, seg) - } else { - seg, err := MakeBytes([]byte(text)) - if err != nil { - return nil, err - } - res = append(res, seg) - } - return res, nil -} - -// MakeEci converts an integer into a QR code segment in Eci mode -// It returns an error if the integer value is out of range. -func MakeEci(val int) (*QrSegment, error) { - bb := &BitBuffer{} - if val < 0 { - return nil, errors.New("ECI assignment value out of range") - } else if val < (1 << 7) { - err := bb.appendBits(val, 8) - if err != nil { - return nil, err - } - } else if val < (1 << 14) { - err := bb.appendBits(0b10, 2) - if err != nil { - return nil, err - } - - err = bb.appendBits(val, 14) - if err != nil { - return nil, err - } - } else if val < 1e6 { - err := bb.appendBits(0b110, 3) - if err != nil { - return nil, err - } - - err = bb.appendBits(val, 21) - if err != nil { - return nil, err - } - } else { - return nil, errors.New("ECI assignment value out of range") - } - return newQrSegment(Eci, 0, bb) -} - -// getTotalBits calculates and returns the total number of bits required to encode the segments at the specified QR version. -// It returns -1 if the number of characters exceeds the maximum capacity. -func getTotalBits(segs []*QrSegment, ver int) int { - var res int64 - for _, seg := range segs { - if seg == nil { - continue - } - - ccbits := seg.mode.numCharCountBits(ver) - if seg.numChars >= (1 << ccbits) { - return -1 - } - res += int64(4 + ccbits + seg.data.len()) - if res > math.MaxInt32 { - return -1 - } - } - return int(res) -} diff --git a/goqr/qr_segment_advanced.go b/goqr/qr_segment_advanced.go deleted file mode 100644 index 273bd1a..0000000 --- a/goqr/qr_segment_advanced.go +++ /dev/null @@ -1,380 +0,0 @@ -package goqr - -import ( - "encoding/base64" - "errors" - "fmt" - "reflect" - "unicode/utf16" -) - -// MakeSegmentsOptimally takes a string and error correction level, and attempts to -// make QR segments in the most efficient way possible. It validates the version -// range and converts the input text into code points. Then, it loops through -// each version, attempting to make segments until the data fits within the -// capacity of the version. Returns an array of pointers to QrSegment or an error. -func MakeSegmentsOptimally(text string, ecl Ecc, minVersion, maxVersion int) ([]*QrSegment, error) { - if !isValidVersion(minVersion, maxVersion) { - return nil, errors.New("invalid value") - } - - codePoints, err := toCodePoints(text) - if err != nil { - return nil, err - } - - for version := minVersion; ; version++ { - if version == minVersion || version == 10 || version == 27 { - segs, err := makeSegmentsOptimallyWithVersion(codePoints, version) - if err != nil { - return nil, err - } - - dataCapacityBits := getNumDataCodewords(version, ecl) * 8 - dataUsedBits := getTotalBits(segs, version) - if dataUsedBits != -1 && dataUsedBits <= dataCapacityBits { - return segs, nil - } - if version >= maxVersion { - msg := "segment too long" - if dataUsedBits != -1 { - msg = fmt.Sprintf("data length = %d bits, max capacity = %d bits", dataUsedBits, dataCapacityBits) - } - return nil, &DataTooLongException{Msg: msg} - } - } - } -} - -// makeSegmentsOptimallyWithVersion takes code points and a version number, -// computes the character modes suitable for that version, and then splits the -// code points into segments accordingly. Returns an array of pointers to -// QrSegment or an error. -func makeSegmentsOptimallyWithVersion(codePoints []int, version int) ([]*QrSegment, error) { - charModes, err := computeCharacterModes(codePoints, version) - if err != nil { - return nil, err - } - return splitIntoSegments(codePoints, charModes) -} - -// toCodePoints returns a new slice of Unicode code points (effectively -// UTF-32 / UCS-4) representing the given UTF-16 string. -func toCodePoints(s string) ([]int, error) { - runes := []rune(s) - codePoints := make([]int, len(runes)) - for i, r := range runes { - if utf16.IsSurrogate(r) { - return nil, errors.New("invalid UTF-16 string") - } - codePoints[i] = int(r) - } - - return codePoints, nil -} - -// countUtf8Bytes counts the number of bytes required to represent a Unicode code point in UTF-8. -func countUtf8Bytes(cp int) (int, error) { - if cp < 0 { - return 0, errors.New("invalid code point") - } else if cp < 0x80 { - return 1, nil - } else if cp < 0x800 { - return 2, nil - } else if cp < 0x10000 { - return 3, nil - } else if cp < 0x110000 { - return 4, nil - } else { - return 0, errors.New("invalid code point") - } -} - -// computeCharacterModes determines the optimal encoding mode for each character in the input string. -func computeCharacterModes(codePoints []int, version int) ([]Mode, error) { - if len(codePoints) > 7089 { - return nil, errors.New("string too long") - } - modeTypes := []Mode{Byte, Alphanumeric, Numeric, Kanji} - numModes := len(modeTypes) - - headCosts := make([]int, numModes) - charModes := make([][]Mode, len(codePoints)) - for i := 0; i < numModes; i++ { - headCosts[i] = (4 + modeTypes[i].numCharCountBits(version)) * 6 - } - - for i := range charModes { - charModes[i] = make([]Mode, numModes) - } - - prevCosts := make([]int, numModes) - copy(prevCosts, headCosts) - - // Determine the mode type for each character based on cost calculation - for i := 0; i < len(codePoints); i++ { - c := codePoints[i] - curCosts := make([]int, numModes) - { - count, err := countUtf8Bytes(c) - if err != nil { - return nil, err - } - curCosts[0] = prevCosts[0] + count*8*6 - charModes[i][0] = modeTypes[0] - } - - if isAlphanumeric(string(rune(c))) { - curCosts[1] = prevCosts[1] + 33 - charModes[i][1] = modeTypes[1] - } - - if isNumeric(string(rune(c))) { - curCosts[2] = prevCosts[2] + 20 - charModes[i][2] = modeTypes[2] - } - - if isKanji(c) { - curCosts[3] = prevCosts[3] + 78 - charModes[i][3] = modeTypes[3] - } - - for j := 0; j < numModes; j++ { - for k := 0; k < numModes; k++ { - newCost := (curCosts[k]+5)/6*6 + headCosts[j] - if charModes[i][k].getModeBits() != 0 && (charModes[i][j].getModeBits() == 0 || newCost < curCosts[j]) { - curCosts[j] = newCost - charModes[i][j] = modeTypes[k] - } - } - } - - prevCosts = curCosts - } - - curMode := Mode{} - for i, minCost := 0, 0; i < numModes; i++ { - if curMode.getModeBits() == 0 || prevCosts[i] < minCost { - minCost = prevCosts[i] - curMode = modeTypes[i] - } - } - - res := make([]Mode, len(charModes)) - for i := len(res) - 1; i >= 0; i-- { - for j := 0; j < numModes; j++ { - if reflect.DeepEqual(modeTypes[j], curMode) { - curMode = charModes[i][j] - res[i] = curMode - break - } - } - } - return res, nil -} - -// splitIntoSegments is used to splits the input into multiple QR segments according to the given modes. -// Each change in mode results in a new segment being created. -func splitIntoSegments(codePoints []int, charModes []Mode) ([]*QrSegment, error) { - res := make([]*QrSegment, 0) - curMode := charModes[0] - start := 0 - for i := 1; ; i++ { - if i < len(codePoints) && reflect.DeepEqual(charModes[i], curMode) { - continue - } - - runes := make([]rune, i-start) - for j, cp := range codePoints[start:i] { - runes[j] = rune(cp) - } - - s := string(runes) - - // Create a QR segment based on the current mode - if curMode.isByte() { - qs, err := MakeBytes([]byte(s)) - if err != nil { - return nil, err - } - res = append(res, qs) - } else if curMode.isNumeric() { - qs, err := MakeNumeric(s) - if err != nil { - return nil, err - } - res = append(res, qs) - } else if curMode.isAlphanumeric() { - qs, err := MakeAlphanumeric(s) - if err != nil { - return nil, err - } - res = append(res, qs) - } else if curMode.isKanji() { - qs, err := MakeKanji(s) - if err != nil { - return nil, err - } - res = append(res, qs) - } else { - return nil, errors.New("invalid mode") - } - if i >= len(codePoints) { - return res, nil - } - curMode = charModes[i] - start = i - } -} - -// MakeKanji converts a string into a QR code segment in Kanji mode -// It returns an error if the string contains non-kanji characters. -func MakeKanji(text string) (*QrSegment, error) { - bb := &BitBuffer{} - runes := []rune(text) - for _, c := range text { - if !isKanji(int(c)) { - return nil, errors.New("string contains non-kanji-mode characters") - } - val := unicdeToQRKanji[c] - err := bb.appendBits(val, 13) - if err != nil { - return nil, err - } - } - return newQrSegment(Kanji, len(runes), bb) -} - -// isKanji function takes a integer as input and returns a boolean indicating whether the integer is Kanji. -func isKanji(c int) bool { - return c < len(unicdeToQRKanji) && unicdeToQRKanji[c] != -1 && c >= 0 -} - -const packedQRKanjiToUnicode = "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==" - -var unicdeToQRKanji [1 << 16]int - -func init() { - for i := range unicdeToQRKanji { - unicdeToQRKanji[i] = -1 - } - - bytes, _ := base64.StdEncoding.DecodeString(packedQRKanjiToUnicode) - for i := 0; i < len(bytes); i += 2 { - c := int(bytes[i]&0xFF)<<8 | int(bytes[i+1]&0xFF) - if c == 0xFFFF { - continue - } - - unicdeToQRKanji[c] = i / 2 - } -} diff --git a/qrApi/main.go b/qrApi/main.go index 4970be0..61008c9 100644 --- a/qrApi/main.go +++ b/qrApi/main.go @@ -2,9 +2,9 @@ package qrApi import ( "bytes" - goqr "git.stuve.uni-ulm.de/stuve-it/stuve-it-backend/goqr" "git.stuve.uni-ulm.de/stuve-it/stuve-it-backend/logger" "github.com/labstack/echo/v5" + goqr "github.com/piglig/go-qr" "github.com/pocketbase/pocketbase" "github.com/pocketbase/pocketbase/apis" "github.com/pocketbase/pocketbase/core"