diff --git a/goqr/bit_buffer.go b/goqr/bit_buffer.go new file mode 100644 index 0000000..0601c6b --- /dev/null +++ b/goqr/bit_buffer.go @@ -0,0 +1,81 @@ +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 new file mode 100644 index 0000000..c45f0d9 --- /dev/null +++ b/goqr/error.go @@ -0,0 +1,9 @@ +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 new file mode 100644 index 0000000..8f70599 --- /dev/null +++ b/goqr/qr_code.go @@ -0,0 +1,1019 @@ +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 new file mode 100644 index 0000000..6e09f8d --- /dev/null +++ b/goqr/qr_segment.go @@ -0,0 +1,299 @@ +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 new file mode 100644 index 0000000..273bd1a --- /dev/null +++ b/goqr/qr_segment_advanced.go @@ -0,0 +1,380 @@ +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 b317a81..1a0a10e 100644 --- a/qrApi/main.go +++ b/qrApi/main.go @@ -2,7 +2,7 @@ package qrApi import ( "bytes" - goqr "git.stuve.uni-ulm.de/stuve-it/stuve-it-backend/go-qr" + 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" "github.com/pocketbase/pocketbase"