diff --git a/golang/examples/demo.go b/golang/examples/demo.go new file mode 100644 index 0000000..53dd3f0 --- /dev/null +++ b/golang/examples/demo.go @@ -0,0 +1,231 @@ +package main + +import ( + "errors" + "fmt" + "strings" + + "github.com/nayuki/qrcodegen" + "github.com/nayuki/qrcodegen/mask" + "github.com/nayuki/qrcodegen/qrcodeecc" + "github.com/nayuki/qrcodegen/qrsegment" + "github.com/nayuki/qrcodegen/version" +) + +// The main application program. +func main() { + doBasicDemo() + doVarietyDemo() + doSegmentDemo() + doMaskDemo() +} + +/*---- Demo suite ----*/ + +// Creates a single QR Code, then prints it to the console. +func doBasicDemo() { + text := "Hello, world!" // User-supplied Unicode text + errcorlevel := qrcodeecc.Low // Error correction level + + // Make and print the QR Code symbol + qr, _ := qrcodegen.EncodeText(text, errcorlevel) + printQr(qr) + svg, _ := toSvgString(qr, 4) + fmt.Printf("%s", svg) +} + +// Creates a variety of QR Codes that exercise different features of the library, and prints each one to the console. +func doVarietyDemo() { + // Numeric mode encoding (3.33 bits per digit) + qr, _ := qrcodegen.EncodeText("314159265358979323846264338327950288419716939937510", qrcodeecc.Medium) + printQr(qr) + + // Alphanumeric mode encoding (5.5 bits per character) + qr, _ = qrcodegen.EncodeText("DOLLAR-AMOUNT:$39.87 PERCENTAGE:100.00% OPERATIONS:+-*/", qrcodeecc.High) + printQr(qr) + + // Unicode text as UTF-8 + qr, _ = qrcodegen.EncodeText("こんにちwa、世界! αβγδ", qrcodeecc.Quartile) + printQr(qr) + + // Moderately large QR Code using longer text (from Lewis Carroll's Alice in Wonderland) + qr, _ = qrcodegen.EncodeText( + strings.Join( + []string{ + "Alice was beginning to get very tired of sitting by her sister on the bank, ", + "and of having nothing to do: once or twice she had peeped into the book her sister was reading, ", + "but it had no pictures or conversations in it, 'and what is the use of a book,' thought Alice ", + "'without pictures or conversations?' So she was considering in her own mind (as well as she could, ", + "for the hot day made her feel very sleepy and stupid), whether the pleasure of making a ", + "daisy-chain would be worth the trouble of getting up and picking the daisies, when suddenly ", + "a White Rabbit with pink eyes ran close by her.", + }, + "", + ), + qrcodeecc.High, + ) + printQr(qr) +} + +// Creates QR Codes with manually specified segments for better compactness. +func doSegmentDemo() { + // Illustration "silver" + silver0 := "THE SQUARE ROOT OF 2 IS 1." + silver1 := "41421356237309504880168872420969807856967187537694807317667973799" + qr, _ := qrcodegen.EncodeText( + strings.Join( + []string{ + silver0, + silver1, + }, + "", + ), + qrcodeecc.Low, + ) + printQr(qr) + + segs := []qrsegment.QrSegment{ + qrsegment.MakeAlphanumeric(toChars(silver0)), + qrsegment.MakeNumeric(toChars(silver1)), + } + qr, _ = qrcodegen.EncodeSegments(segs, qrcodeecc.Low) + printQr(qr) + + // Illustration "golden" + golden0 := "Golden ratio φ = 1." + golden1 := "6180339887498948482045868343656381177203091798057628621354486227052604628189024497072072041893911374" + golden2 := "......" + qr, _ = qrcodegen.EncodeText( + strings.Join( + []string{ + golden0, + golden1, + golden2, + }, + "", + ), + qrcodeecc.Low, + ) + printQr(qr) + + segs = []qrsegment.QrSegment{ + qrsegment.MakeBytes([]byte(golden0)), + qrsegment.MakeNumeric(toChars(golden1)), + qrsegment.MakeAlphanumeric(toChars(golden2)), + } + qr, _ = qrcodegen.EncodeSegments(segs, qrcodeecc.Low) + printQr(qr) + + // Illustration "Madoka": kanji, kana, Cyrillic, full-width Latin, Greek characters + madoka := "「魔法少女まどか☆マギカ」って、 ИАИ desu κα?" + qr, _ = qrcodegen.EncodeText(madoka, qrcodeecc.Low) + printQr(qr) + + kanjichars := []uint32{ // Kanji mode encoding (13 bits per character) + 0x0035, 0x1002, 0x0FC0, 0x0AED, 0x0AD7, + 0x015C, 0x0147, 0x0129, 0x0059, 0x01BD, + 0x018D, 0x018A, 0x0036, 0x0141, 0x0144, + 0x0001, 0x0000, 0x0249, 0x0240, 0x0249, + 0x0000, 0x0104, 0x0105, 0x0113, 0x0115, + 0x0000, 0x0208, 0x01FF, 0x0008, + } + bb := qrsegment.BitBuffer{} + for _, c := range kanjichars { + bb.AppendBits(c, 13) + } + segs = []qrsegment.QrSegment{ + qrsegment.New( + qrsegment.ModeKanji, + uint(len(kanjichars)), + bb, + ), + } + qr, _ = qrcodegen.EncodeSegments(segs, qrcodeecc.Low) + printQr(qr) +} + +// Creates QR Codes with the same size and contents but different mask patterns. +func doMaskDemo() { + // Project Nayuki URL + segs := qrsegment.MakeSegments(toChars("https://www.nayuki.io/")) + qr, _ := qrcodegen.EncodeSegmentsAdvanced(segs, qrcodeecc.High, version.Min, version.Max, nil, true) // Automatic mask + printQr(qr) + m := mask.New(3) + qr, _ = qrcodegen.EncodeSegmentsAdvanced(segs, qrcodeecc.High, version.Min, version.Max, &m, true) // Force mask 3 + printQr(qr) + + // Chinese text as UTF-8 + segs = qrsegment.MakeSegments(toChars("維基百科(Wikipedia,聆聽i/ˌwɪkᵻˈpiːdi.ə/)是一個自由內容、公開編輯且多語言的網路百科全書協作計畫")) + m = mask.New(0) + qr, _ = qrcodegen.EncodeSegmentsAdvanced(segs, qrcodeecc.Medium, version.Min, version.Max, &m, true) // Force mask 0 + printQr(qr) + m = mask.New(1) + qr, _ = qrcodegen.EncodeSegmentsAdvanced(segs, qrcodeecc.Medium, version.Min, version.Max, &m, true) // Force mask 1 + printQr(qr) + m = mask.New(5) + qr, _ = qrcodegen.EncodeSegmentsAdvanced(segs, qrcodeecc.Medium, version.Min, version.Max, &m, true) // Force mask 5 + printQr(qr) + m = mask.New(7) + qr, _ = qrcodegen.EncodeSegmentsAdvanced(segs, qrcodeecc.Medium, version.Min, version.Max, &m, true) // Force mask 7 + printQr(qr) +} + +/*---- Utilities ----*/ + +// Returns a string of SVG code for an image depicting +// the given QR Code, with the given number of border modules. +// The string always uses Unix newlines (\n), regardless of the platform. +func toSvgString(qr *qrcodegen.QrCode, border int32) (string, error) { + if border < 0 { + return "", errors.New("Border must be non-negative") + } + + var sb strings.Builder + sb.WriteString("\n") + sb.WriteString("\n") + + // TODO: check overflow? + dimension := qr.Size() + border*2 + sb.WriteString(fmt.Sprintf("\n", dimension, dimension)) + sb.WriteString("\t\n") + sb.WriteString("\t\n") + sb.WriteString("\n") + + return sb.String(), nil +} + +// Prints the given QrCode object to the console. +func printQr(qr *qrcodegen.QrCode) { + border := int32(4) + + for y := -border; y < qr.Size()+border; y++ { + for x := -border; x < qr.Size()+border; x++ { + var c rune + if qr.GetModule(x, y) { + c = '█' + } else { + c = ' ' + } + fmt.Printf("%c%c", c, c) + } + fmt.Println() + } + fmt.Println() +} + +// Converts the given borrowed string slice to a new character vector. +func toChars(str string) []rune { + return []rune(str) +} diff --git a/golang/go.mod b/golang/go.mod new file mode 100644 index 0000000..2714f9c --- /dev/null +++ b/golang/go.mod @@ -0,0 +1,3 @@ +module github.com/nayuki/qrcodegen + +go 1.16 diff --git a/golang/go.sum b/golang/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/golang/internal/bitx/bitx.go b/golang/internal/bitx/bitx.go new file mode 100644 index 0000000..4ffa3b9 --- /dev/null +++ b/golang/internal/bitx/bitx.go @@ -0,0 +1,6 @@ +package bitx + +// GetBit returns true iff the i'th bit of x is set to 1. +func GetBit(x uint32, i int32) bool { + return (x>>i)&1 != 0 +} diff --git a/golang/internal/mathx/mathx.go b/golang/internal/mathx/mathx.go new file mode 100644 index 0000000..e68f054 --- /dev/null +++ b/golang/internal/mathx/mathx.go @@ -0,0 +1,57 @@ +package mathx + +// TODO: refer to rust wrapping_neg(), not sure if same behavior on edge cases +func WrappingNeg(x int) int { + if x > 0 { + return -x + } + + return x +} + +func MinUint(left, right uint) uint { + if left < right { + return left + } + + return right +} + +func MinInt(left, right int) int { + if left < right { + return left + } + + return right +} + +func MaxInt32(left, right int32) int32 { + if left > right { + return left + } + + return right +} + +func AbsInt32(x int32) int32 { + if x < 0 { + return -x + } + return x +} + +func BoolToUint8(b bool) uint8 { + if b { + return 1 + } else { + return 0 + } +} + +func BoolToInt32(b bool) int32 { + if b { + return 1 + } else { + return 0 + } +} diff --git a/golang/mask/mask.go b/golang/mask/mask.go new file mode 100644 index 0000000..a944b19 --- /dev/null +++ b/golang/mask/mask.go @@ -0,0 +1,19 @@ +package mask + +// Mask is a number between 0 and 7 (inclusive). +type Mask uint8 + +// New creates a mask object from the given number. +func New(mask uint8) Mask { + // Panics if the number is outside the range [0, 7]. + if mask > 7 { + panic("Mask value out of range") + } + + return Mask(mask) +} + +// Value returns the value, which is in the range [0, 7]. +func (m Mask) Value() uint8 { + return uint8(m) +} diff --git a/golang/qrcodeecc/qrcodeecc.go b/golang/qrcodeecc/qrcodeecc.go new file mode 100644 index 0000000..69bd564 --- /dev/null +++ b/golang/qrcodeecc/qrcodeecc.go @@ -0,0 +1,49 @@ +package qrcodeecc + +/*---- QrCodeEcc functionality ----*/ + +// QrCodeEcc is the error correction level in a QR Code symbol. +type QrCodeEcc uint + +const ( + // Low means the QR Code can tolerate about 7% erroneous codewords. + Low QrCodeEcc = 0 + // Medium means the QR Code can tolerate about 15% erroneous codewords. + Medium QrCodeEcc = 1 + // Quartile means the QR Code can tolerate about 25% erroneous codewords. + Quartile QrCodeEcc = 2 + // High means the QR Code can tolerate about 30% erroneous codewords. + High QrCodeEcc = 3 +) + +// Ordinal returns an unsigned 2-bit integer (in the range 0 to 3). +func (q QrCodeEcc) Ordinal() uint { + switch q { + case Low: + return 0 + case Medium: + return 1 + case Quartile: + return 2 + case High: + return 3 + default: + panic("unknown QrCodeEcc") + } +} + +// FormatBits returns an unsigned 2-bit integer (in the range 0 to 3). +func (q QrCodeEcc) FormatBits() uint8 { + switch q { + case Low: + return 1 + case Medium: + return 0 + case Quartile: + return 3 + case High: + return 2 + default: + panic("unknown QrCodeEcc") + } +} diff --git a/golang/qrcodegen.go b/golang/qrcodegen.go new file mode 100644 index 0000000..200379c --- /dev/null +++ b/golang/qrcodegen.go @@ -0,0 +1,889 @@ +package qrcodegen + +import ( + "errors" + "fmt" + "math" + + "github.com/nayuki/qrcodegen/internal/bitx" + "github.com/nayuki/qrcodegen/internal/mathx" + "github.com/nayuki/qrcodegen/mask" + "github.com/nayuki/qrcodegen/qrcodeecc" + "github.com/nayuki/qrcodegen/qrsegment" + "github.com/nayuki/qrcodegen/version" +) + +/*---- Miscellaneous values ----*/ + +var ( + // ErrDataTooLong is the error type when the supplied data does not fit any QR Code version. + // + // Ways to handle this exception include: + // + // - Decrease the error correction level if it was greater than `QrCodeEcc::Low`. + // - If the `encode_segments_advanced()` function was called, then increase the maxversion + // argument if it was less than `Version::MAX`. (This advice does not apply to the + // other factory functions because they search all versions up to `Version::MAX`.) + // - Split the text data into better or optimal segments in order to reduce the number of bits required. + // - Change the text or binary data to be shorter. + // - Change the text to fit the character set of a particular segment mode (e.g. alphanumeric). + // - Propagate the error upward to the caller/user. + ErrDataTooLong = errors.New("DataTooLong") +) + +// alias +type Version = version.Version +type QrCodeEcc = qrcodeecc.QrCodeEcc +type Mask = mask.Mask + +/*---- QrCode functionality ----*/ + +// QrCode is a QR Code symbol, which is a type of two-dimension barcode. +// +// Invented by Denso Wave and described in the ISO/IEC 18004 standard. +// +// Instances of this struct represent an immutable square grid of dark and light cells. +// The impl provides static factory functions to create a QR Code from text or binary data. +// The struct and impl cover the QR Code Model 2 specification, supporting all versions +// (sizes) from 1 to 40, all 4 error correction levels, and 4 character encoding modes. +// +// Ways to create a QR Code object: +// +// - High level: Take the payload data and call `QrCode::encode_text()` or `QrCode::encode_binary()`. +// - Mid level: Custom-make the list of segments and call +// `QrCode::encode_segments()` or `QrCode::encode_segments_advanced()`. +// - Low level: Custom-make the array of data codeword bytes (including segment +// headers and final padding, excluding error correction codewords), supply the +// appropriate version number, and call the `QrCode::encode_codewords()` constructor. +// +// (Note that all ways require supplying the desired error correction level.) +type QrCode struct { + // Scalar parameters: + + // The version number of this QR Code, which is between 1 and 40 (inclusive). + // This determines the size of this barcode. + version Version + // The width and height of this QR Code, measured in modules, between + // 21 and 177 (inclusive). This is equal to version * 4 + 17. + size int32 + // The error correction level used in this QR Code. + errorcorrectionlevel QrCodeEcc + // The index of the mask pattern used in this QR Code, which is between 0 and 7 (inclusive). + // Even if a QR Code is created with automatic masking requested (mask = None), + // the resulting object still has a mask value between 0 and 7. + mask Mask + + // Grids of modules/pixels, with dimensions of size*size: + + // The modules of this QR Code (false = light, true = dark). + // Immutable after constructor finishes. Accessed through get_module(). + modules []bool + // Indicates function modules that are not subjected to masking. Discarded when constructor finishes. + isfunction []bool +} + +/*---- Static factory functions (high level) ----*/ + +// EncodeText returns a QR Code representing the given Unicode text string at the given error correction level. +// +// As a conservative upper bound, this function is guaranteed to succeed for strings that have 738 or fewer Unicode +// code points (not UTF-8 code units) if the low error correction level is used. The smallest possible +// QR Code version is automatically chosen for the output. The ECC level of the result may be higher than +// the ecl argument if it can be done without increasing the version. +// +// Returns a wrapped `QrCode` if successful, or `Err` if the +// data is too long to fit in any version at the given ECC level. +func EncodeText(text string, ecl QrCodeEcc) (*QrCode, error) { + chrs := []rune(text) + segs := qrsegment.MakeSegments(chrs) + + return EncodeSegments(segs, ecl) +} + +// EncodeBinary returns a QR Code representing the given binary data at the given error correction level. +// +// This function always encodes using the binary segment mode, not any text mode. The maximum number of +// bytes allowed is 2953. The smallest possible QR Code version is automatically chosen for the output. +// The ECC level of the result may be higher than the ecl argument if it can be done without increasing the version. +// +// Returns a wrapped `QrCode` if successful, or `Err` if the +// data is too long to fit in any version at the given ECC level. +func EncodeBinary(data []uint8, ecl QrCodeEcc) (*QrCode, error) { + seg := qrsegment.MakeBytes(data) + segs := []qrsegment.QrSegment{seg} + + return EncodeSegments(segs, ecl) +} + +/*---- Static factory functions (mid level) ----*/ + +// EncodeSegments returns a QR Code representing the given segments at the given error correction level. +// +// The smallest possible QR Code version is automatically chosen for the output. The ECC level +// of the result may be higher than the ecl argument if it can be done without increasing the version. +// +// This function allows the user to create a custom sequence of segments that switches +// between modes (such as alphanumeric and byte) to encode text in less space. +// This is a mid-level API; the high-level API is `encode_text()` and `encode_binary()`. +// +// Returns a wrapped `QrCode` if successful, or `Err` if the +// data is too long to fit in any version at the given ECC level. +func EncodeSegments(segs []qrsegment.QrSegment, ecl QrCodeEcc) (*QrCode, error) { + return EncodeSegmentsAdvanced(segs, ecl, version.Min, version.Max, nil, true) +} + +// EncodeSegmentsAdvanced returns a QR Code representing the given segments with the given encoding parameters. +// +// The smallest possible QR Code version within the given range is automatically +// chosen for the output. Iff boostecl is `true`, then the ECC level of the result +// may be higher than the ecl argument if it can be done without increasing the +// version. The mask number is either between 0 to 7 (inclusive) to force that +// mask, or `None` to automatically choose an appropriate mask (which may be slow). +// +// This function allows the user to create a custom sequence of segments that switches +// between modes (such as alphanumeric and byte) to encode text in less space. +// This is a mid-level API; the high-level API is `encode_text()` and `encode_binary()`. +// +// Returns a wrapped `QrCode` if successful, or `Err` if the data is too +// long to fit in any version in the given range at the given ECC level. +func EncodeSegmentsAdvanced( + segs []qrsegment.QrSegment, + ecl QrCodeEcc, + minversion Version, + maxversion Version, + mask *Mask, + boostecl bool, +) (q *QrCode, err error) { + if minversion > maxversion { + panic("Invalid value") + } + + // Find the minimal version number to use + ver := minversion + var datausedbits uint + for { + // Number of data bits available + datacapacitybits := getNumDataCodewords(ver, ecl) * 8 + dataused := qrsegment.GetTotalBits(segs, ver) + + // TODO: refactor to match closer to the semantics of rust counterpart map_or + mapOr := false + if dataused != nil { + mapOr = *dataused <= datacapacitybits + } + + if mapOr { + datausedbits = *dataused // This version number is found to be suitable + break + } else if ver.Value() >= maxversion.Value() { // All versions in the range could not fit the given data + if dataused == nil { + return nil, fmt.Errorf("%w: Segment too long", ErrDataTooLong) + } + return nil, fmt.Errorf("%w: Data length = %v bits, Max capacity = %v bits", ErrDataTooLong, *dataused, datacapacitybits) + } else { + ver = version.New(ver.Value() + 1) + } + } + + // Increase the error correction level while the data still fits in the current version number + for _, newecl := range []QrCodeEcc{qrcodeecc.Medium, qrcodeecc.Quartile, qrcodeecc.High} { // From low to high + if boostecl && datausedbits <= getNumDataCodewords(ver, newecl)*8 { + ecl = newecl + } + } + + // Concatenate all segments to create the data bit string + bb := qrsegment.BitBuffer{} + for _, seg := range segs { + bb.AppendBits(seg.Mode().ModeBits(), 4) + bb.AppendBits(uint32(seg.NumChars()), seg.Mode().NumCharCountBits(ver)) + bb = append(bb, seg.Data()...) + } + if len(bb) != int(datausedbits) { + panic("len(bb) != int(datausedbits)") + } + + // Add terminator and pad up to a byte if applicable + datacapacitybits := getNumDataCodewords(ver, ecl) * 8 + if len(bb) > int(datacapacitybits) { + panic("len(bb) > int(datacapacitybits)") + } + numzerobits := mathx.MinUint(4, datacapacitybits-uint(len(bb))) + bb.AppendBits(0, uint8(numzerobits)) + + // TODO: check edge case for WrappingNeg + numzerobits = uint(mathx.WrappingNeg(len(bb)) & 7) + bb.AppendBits(0, uint8(numzerobits)) + if len(bb)%8 != 0 { + panic("len(bb)%8 != 0") + } + + // TODO: refactor to match closer to the semantics of rust counterpart .iter().cycle() + // Pad with alternating bytes until data capacity is reached + for { + for _, padByte := range []uint32{0xEC, 0x11} { + if len(bb) >= int(datacapacitybits) { + goto Donepad + } + bb.AppendBits(padByte, 8) + } + } +Donepad: + + // Pack bits into bytes in big endian + datacodewords := make([]uint8, len(bb)/8) + for i, bit := range bb { + datacodewords[i>>3] |= mathx.BoolToUint8(bit) << (7 - (i & 7)) + } + + // Create the QR Code object + q = EncodeCodewords(ver, ecl, datacodewords, mask) + + return q, nil +} + +/*---- Constructor (low level) ----*/ + +// EncodeCodewords creates a new QR Code with the given version number, +// error correction level, data codeword bytes, and mask number. +// +// This is a low-level API that most users should not use directly. +// A mid-level API is the `encode_segments()` function. +func EncodeCodewords(ver Version, ecl QrCodeEcc, datacodewords []uint8, m *Mask) *QrCode { + size := uint(ver.Value())*4 + 17 + + result := &QrCode{ + version: ver, + size: int32(size), + mask: mask.New(0), // Dummy value + errorcorrectionlevel: ecl, + modules: make([]bool, size*size), // Initially all light + isfunction: make([]bool, size*size), + } + + // Compute ECC, draw modules + result.drawFunctionPatterns() + allcodewords := result.addEccAndInterleave(datacodewords) + result.drawCodewords(allcodewords) + + // Do masking + if m == nil { // Automatically choose best mask + minpenalty := int32(math.MaxInt32) + for i, max := uint8(0), uint8(8); i < max; i++ { + newmask := mask.New(i) + result.applyMask(newmask) + result.drawFormatBits(newmask) + penalty := result.getPenaltyScore() + if penalty < minpenalty { + m = &newmask + minpenalty = penalty + } + + result.applyMask(newmask) // Undoes the mask due to XOR + } + } + newmask := *m + result.mask = newmask + result.applyMask(newmask) // Apply the final choice of mask + result.drawFormatBits(newmask) // Overwrite old format bits + + result.isfunction = result.isfunction[:0] + // TODO: need to implement rust shrink_to_fit() ? + + return result +} + +/*---- Public methods ----*/ + +// Version returns this QR Code's version, in the range [1, 40]. +func (q QrCode) Version() Version { + return q.version +} + +// Size returns this QR Code's size, in the range [21, 177]. +func (q QrCode) Size() int32 { + return q.size +} + +// ErrorCorrectionLevel returns this QR Code's error correction level. +func (q QrCode) ErrorCorrectionLevel() QrCodeEcc { + return q.errorcorrectionlevel +} + +// Mask returns this QR Code's mask, in the range [0, 7]. +func (q QrCode) Mask() Mask { + return q.mask +} + +// GetModule returns the color of the module (pixel) at the given coordinates, +// which is `false` for light or `true` for dark. +// +// The top left corner has the coordinates (x=0, y=0). If the given +// coordinates are out of bounds, then `false` (light) is returned. +func (q QrCode) GetModule(x, y int32) bool { + return 0 <= x && x < q.size && 0 <= y && y < q.size && q.module(x, y) +} + +// Returns the color of the module at the given coordinates, which must be in bounds. +func (q QrCode) module(x, y int32) bool { + return q.modules[uint(y*q.size+x)] +} + +// TODO: refactor to match closer to the semantics of rust counterpart +// Returns a mutable reference to the module's color at the given coordinates, which must be in bounds. +func (q *QrCode) moduleMut(x, y int32, mut bool) { + q.modules[uint(y*q.size+x)] = mut +} + +/*---- Private helper methods for constructor: Drawing function modules ----*/ + +// Reads this object's version field, and draws and marks all function modules. +func (q *QrCode) drawFunctionPatterns() { + // Draw horizontal and vertical timing patterns + size := int(q.size) + for i := 0; i < size; i++ { + q.setFunctionModule(6, int32(i), i%2 == 0) + q.setFunctionModule(int32(i), 6, i%2 == 0) + } + + // Draw 3 finder patterns (all corners except bottom right; overwrites some timing modules) + q.drawFinderPattern(3, 3) + q.drawFinderPattern(q.size-4, 3) + q.drawFinderPattern(3, q.size-4) + + // Draw numerous alignment patterns + alignpatpos := q.getAlignmentPatternPositions() + numalign := len(alignpatpos) + for i := 0; i < numalign; i++ { + for j := 0; j < numalign; j++ { + // Don't draw on the three finder corners + if !(i == 0 && j == 0 || i == 0 && j == numalign-1 || i == numalign-1 && j == 0) { + q.drawAlignmentPattern(alignpatpos[i], alignpatpos[j]) + } + } + } + + // Draw configuration data + q.drawFormatBits(mask.New(0)) // Dummy mask value; overwritten later in the constructor + q.drawVersion() +} + +// Draws two copies of the format bits (with its own error correction code) +// based on the given mask and this object's error correction level field. +func (q *QrCode) drawFormatBits(mask Mask) { + // Calculate error correction code and pack bits + var bits uint32 + { + // errorcorrectionlevel is uint2, mask is uint3 + data := uint32(q.errorcorrectionlevel.FormatBits()<<3 | mask.Value()) + rem := data + for i := 0; i < 10; i++ { + rem = (rem << 1) ^ ((rem >> 9) * 0x537) + } + bits = (data<<10 | rem) ^ 0x5412 // uint15 + } + if bits>>15 != 0 { + panic("bits>>15 != 0") + } + + // Draw first copy + for i := int32(0); i < 6; i++ { + q.setFunctionModule(8, i, bitx.GetBit(bits, i)) + } + q.setFunctionModule(8, 7, bitx.GetBit(bits, 6)) + q.setFunctionModule(8, 8, bitx.GetBit(bits, 7)) + q.setFunctionModule(7, 8, bitx.GetBit(bits, 8)) + for i := int32(9); i < 15; i++ { + q.setFunctionModule(14-i, 8, bitx.GetBit(bits, i)) + } + + // Draw second copy + size := q.size + for i := int32(0); i < 8; i++ { + q.setFunctionModule(size-1-i, 8, bitx.GetBit(bits, i)) + } + for i := int32(8); i < 15; i++ { + q.setFunctionModule(8, size-15+i, bitx.GetBit(bits, i)) + } + q.setFunctionModule(8, size-8, true) // Always dark +} + +// Draws two copies of the version bits (with its own error correction code), +// based on this object's version field, iff 7 <= version <= 40. +func (q *QrCode) drawVersion() { + if q.version < 7 { + return + } + + // Calculate error correction code and pack bits + var bits uint32 + { + data := uint32(q.version.Value()) // uint6, in the range [7, 40] + rem := data + for i := 0; i < 12; i++ { + rem = (rem << 1) ^ ((rem >> 11) * 0x1F25) + } + bits = data<<12 | rem // uint18 + } + if bits>>18 != 0 { + panic("bits>>18 != 0") + } + + // Draw two copies + for i := int32(0); i < 18; i++ { + bit := bitx.GetBit(bits, i) + a := q.size - 11 + i%3 + b := i / 3 + q.setFunctionModule(a, b, bit) + q.setFunctionModule(b, a, bit) + } +} + +// Draws a 9*9 finder pattern including the border separator, +// with the center module at (x, y). Modules can be out of bounds. +func (q *QrCode) drawFinderPattern(x, y int32) { + for dy := int32(-4); dy <= 4; dy++ { + for dx := int32(-4); dx <= 4; dx++ { + xx := x + dx + yy := y + dy + if 0 <= xx && xx < q.size && 0 <= yy && yy < q.size { + dist := mathx.MaxInt32(mathx.AbsInt32(dx), mathx.AbsInt32(dy)) // Chebyshev/infinity norm + q.setFunctionModule(xx, yy, dist != 2 && dist != 4) + } + } + } +} + +// Draws a 5*5 alignment pattern, with the center module +// at (x, y). All modules must be in bounds. +func (q *QrCode) drawAlignmentPattern(x, y int32) { + for dy := int32(-2); dy <= 2; dy++ { + for dx := int32(-2); dx <= 2; dx++ { + q.setFunctionModule(x+dx, y+dy, mathx.MaxInt32(mathx.AbsInt32(dx), mathx.AbsInt32(dy)) != 1) + } + } +} + +// Sets the color of a module and marks it as a function module. +// Only used by the constructor. Coordinates must be in bounds. +func (q *QrCode) setFunctionModule(x int32, y int32, isdark bool) { + q.moduleMut(x, y, isdark) + q.isfunction[(y*q.size + x)] = true +} + +/*---- Private helper methods for constructor: Codewords and masking ----*/ + +// Returns a new byte string representing the given data with the appropriate error correction +// codewords appended to it, based on this object's version and error correction level. +func (q *QrCode) addEccAndInterleave(data []uint8) []uint8 { + ver := q.version + ecl := q.errorcorrectionlevel + if len(data) != int(getNumDataCodewords(ver, ecl)) { + panic("Illegal argument") + } + + // Calculate parameter numbers + numblocks := tableGet(NUM_ERROR_CORRECTION_BLOCKS, ver, ecl) + blockecclen := tableGet(ECC_CODEWORDS_PER_BLOCK, ver, ecl) + rawcodewords := getNumRawDataModules(ver) / 8 + numshortblocks := numblocks - (rawcodewords % numblocks) + shortblocklen := rawcodewords / numblocks + + // Split data into blocks and append ECC to each block + blocks := make([][]uint8, 0, numblocks) + rsdiv := reedSolomonComputeDivisor(blockecclen) + + var k uint + for i, max := uint(0), numblocks; i < max; i++ { + datlen := shortblocklen - blockecclen + uint(mathx.BoolToUint8(i >= numshortblocks)) + dat := make([]uint8, datlen) + _ = copy(dat, data[k:k+datlen]) + k += datlen + ecc := reedSolomonComputeRemainder(dat, rsdiv) + + if i < numshortblocks { + dat = append(dat, 0) + } + dat = append(dat, ecc...) + blocks = append(blocks, dat) + } + + // Interleave (not concatenate) the bytes from every block into a single sequence + result := make([]uint8, 0, rawcodewords) + for i, max := uint(0), shortblocklen; i <= max; i++ { + for j, block := range blocks { + // Skip the padding byte in short blocks + if i != shortblocklen-blockecclen || uint(j) >= numshortblocks { + result = append(result, block[i]) + } + } + } + + return result +} + +// Draws the given sequence of 8-bit codewords (data and error correction) onto the entire +// data area of this QR Code. Function modules need to be marked off before this is called. +func (q *QrCode) drawCodewords(data []uint8) { + if uint(len(data)) != getNumRawDataModules(q.version)/8 { + panic("Illegal argument") + } + + var i uint // Bit index into the data + // Do the funny zigzag scan + right := q.size - 1 + for right >= 1 { // Index of right column in each column pair + if right == 6 { + right = 5 + } + for vert := int32(0); vert < q.size; vert++ { // Vertical counter + for j := int32(0); j < 2; j++ { + x := right - j // Actual x coordinate + upward := (right+1)&2 == 0 + var y int32 + if upward { + y = q.size - 1 - vert + } else { + y = vert + } + if !q.isfunction[(y*q.size+x)] && i < uint(len(data)*8) { + q.moduleMut(x, y, bitx.GetBit(uint32(data[i>>3]), int32(7-(i&7)))) + i += 1 + } + // If this QR Code has any remainder bits (0 to 7), they were assigned as + // 0/false/light by the constructor and are left unchanged by this method + } + } + right -= 2 + } + + if i != uint(len(data)*8) { + panic("i != uint(len(data)*8)") + } +} + +// XORs the codeword modules in this QR Code with the given mask pattern. +// The function modules must be marked and the codeword bits must be drawn +// before masking. Due to the arithmetic of XOR, calling apply_mask() with +// the same mask value a second time will undo the mask. A final well-formed +// QR Code needs exactly one (not zero, two, etc.) mask applied. +func (q *QrCode) applyMask(mask Mask) { + for y := int32(0); y < q.size; y++ { + for x := int32(0); x < q.size; x++ { + var invert bool + switch mask.Value() { + 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 + default: + panic("unreachable") + } + newModule := q.module(x, y) != (invert && !q.isfunction[(y*q.size+x)]) + q.moduleMut(x, y, newModule) + } + } +} + +// Calculates and returns the penalty score based on state of this QR Code's current modules. +// This is used by the automatic mask choice algorithm to find the mask pattern that yields the lowest score. +func (q QrCode) getPenaltyScore() int32 { + var result int32 + size := q.size + + // Adjacent modules in row having same color, and finder-like patterns + for y := int32(0); y < size; y++ { + var runcolor bool + var runx int32 + runhistory := newFinderPenalty(size) + for x := int32(0); x < size; x++ { + if q.module(x, y) == runcolor { + runx += 1 + if runx == 5 { + result += PENALTY_N1 + } else if runx > 5 { + result += 1 + } + } else { + runhistory.addHistory(runx) + if !runcolor { + result += runhistory.countPatterns() * PENALTY_N3 + } + runcolor = q.module(x, y) + runx = 1 + } + } + result += runhistory.terminateAndCount(runcolor, runx) * PENALTY_N3 + } + + // Adjacent modules in column having same color, and finder-like patterns + for x := int32(0); x < size; x++ { + var runcolor bool + var runy int32 + runhistory := newFinderPenalty(size) + for y := int32(0); y < size; y++ { + if q.module(x, y) == runcolor { + runy += 1 + if runy == 5 { + result += PENALTY_N1 + } else if runy > 5 { + result += 1 + } + } else { + runhistory.addHistory(runy) + if !runcolor { + result += runhistory.countPatterns() * PENALTY_N3 + } + runcolor = q.module(x, y) + runy = 1 + } + } + result += runhistory.terminateAndCount(runcolor, runy) * PENALTY_N3 + } + + // 2*2 blocks of modules having same color + for y := int32(0); y < size-1; y++ { + for x := int32(0); x < size-1; x++ { + color := q.module(x, y) + if color == q.module(x+1, y) && + color == q.module(x, y+1) && + color == q.module(x+1, y+1) { + result += PENALTY_N2 + } + } + } + + // TODO: refactor to match closer to the semantics of rust counterpart for map().sum() + // Balance of dark and light modules + var dark int32 + for _, mod := range q.modules { + dark += mathx.BoolToInt32(mod) + } + total := size * size // Note that size is odd, so dark/total != 1/2 + // Compute the smallest integer k >= 0 such that (45-5k)% <= dark/total <= (55+5k)% + k := (mathx.AbsInt32((dark*20-total*10))+total-1)/total - 1 + result += k * PENALTY_N4 + + return result +} + +/*---- Private helper functions ----*/ + +// Returns an ascending list of positions of alignment patterns for this version number. +// Each position is in the range [0,177), and are used on both the x and y axes. +// This could be implemented as lookup table of 40 variable-length lists of unsigned bytes. +func (q QrCode) getAlignmentPatternPositions() []int32 { + ver := q.version.Value() + if ver == 1 { + return []int32{} + } else { + numalign := int32(ver)/7 + 2 + var step int32 + if ver == 32 { + step = 26 + } else { + step = (int32(ver)*4 + numalign*2 + 1) / (numalign*2 - 2) * 2 + } + result := make([]int32, numalign) + for i := int32(0); i < numalign-1; i++ { + result[i] = q.size - 7 - i*step + } + result[numalign-1] = 6 + + // TODO: refactor to match closer to the semantics of rust counterpart, reverse() + invertedResult := make([]int32, numalign) + for i, val := range result { + invertedResult[numalign-1-int32(i)] = val + } + + return invertedResult + } +} + +// Returns the number of data bits that can be stored in a QR Code of the given version number, after +// all function modules are excluded. This includes remainder bits, so it might not be a multiple of 8. +// The result is in the range [208, 29648]. This could be implemented as a 40-entry lookup table. +func getNumRawDataModules(v Version) uint { + ver := uint(v.Value()) + result := (16*ver+128)*ver + 64 + if ver >= 2 { + numalign := ver/7 + 2 + result -= (25*numalign-10)*numalign - 55 + if ver >= 7 { + result -= 36 + } + } + if result < 208 || result > 29648 { + panic("result < 208 || result > 29648") + } + + return result +} + +// Returns the number of 8-bit data (i.e. not error correction) codewords contained in any +// QR Code of the given version number and error correction level, with remainder bits discarded. +// This stateless pure function could be implemented as a (40*4)-cell lookup table. +func getNumDataCodewords(ver Version, ecl QrCodeEcc) uint { + return getNumRawDataModules(ver)/8 - tableGet(ECC_CODEWORDS_PER_BLOCK, ver, ecl)*tableGet(NUM_ERROR_CORRECTION_BLOCKS, ver, ecl) +} + +// Returns an entry from the given table based on the given values. +func tableGet(table [4][41]int8, ver Version, ecl QrCodeEcc) uint { + return uint(table[ecl.Ordinal()][uint(ver.Value())]) +} + +// Returns a Reed-Solomon ECC generator polynomial for the given degree. This could be +// implemented as a lookup table over all possible parameter values, instead of as an algorithm. +func reedSolomonComputeDivisor(degree uint) []uint8 { + if degree < 1 || degree > 255 { + panic("Degree out of range") + } + + // Polynomial coefficients are stored from highest to lowest power, excluding the leading term which is always 1. + // For example the polynomial x^3 + 255x^2 + 8x + 93 is stored as the uint8 array [255, 8, 93]. + result := make([]uint8, degree-1) + result = append(result, 1) // Start off with the monomial x^0 + + // Compute the product polynomial (x - r^0) * (x - r^1) * (x - r^2) * ... * (x - r^{degree-1}), + // and drop the highest monomial term which is always 1x^degree. + // Note that r = 0x02, which is a generator element of this field GF(2^8/0x11D). + root := uint8(1) + for i := uint(0); i < degree; i++ { // Unused variable i + // Multiply the current product by (x - r^i) + for j := uint(0); j < degree; j++ { + result[j] = reedSolomonMultiply(result[j], root) + if j+1 < uint(len(result)) { + result[j] ^= result[j+1] + } + } + root = reedSolomonMultiply(root, 0x02) + } + return result +} + +// Returns the Reed-Solomon error correction codeword for the given data and divisor polynomials. +func reedSolomonComputeRemainder(data []uint8, divisor []uint8) []uint8 { + result := make([]uint8, len(divisor)) + for _, b := range data { // Polynomial division + var pop uint8 + pop, result = result[0], result[1:] + factor := b ^ pop + result = append(result, 0) + + // TODO: refactor to match closer to the semantics of rust counterpart, zip() + iterLen := mathx.MinInt(len(result), len(divisor)) + for i := 0; i < iterLen; i++ { + // x := result[i] + y := divisor[i] + + result[i] ^= reedSolomonMultiply(y, factor) + } + } + + return result +} + +// Returns the product of the two given field elements modulo GF(2^8/0x11D). +// All inputs are valid. This could be implemented as a 256*256 lookup table. +func reedSolomonMultiply(x, y uint8) uint8 { + // Russian peasant multiplication + var z uint8 + // TODO: refactor to match closer to the semantics of rust counterpart, rev() + for i := 7; i > -1; i-- { + z = (z << 1) ^ ((z >> 7) * 0x1D) + z ^= ((y >> i) & 1) * x + } + + return z +} + +/*---- Helper struct for get_penalty_score() ----*/ + +type finderPenalty struct { + qrSize int32 + runHistory [7]int32 +} + +func newFinderPenalty(size int32) *finderPenalty { + return &finderPenalty{ + qrSize: size, + runHistory: [7]int32{}, + } +} + +// Pushes the given value to the front and drops the last value. +func (p *finderPenalty) addHistory(currentrunlength int32) { + if p.runHistory[0] == 0 { + currentrunlength += p.qrSize // Add light border to initial run + } + rh := &p.runHistory + // TODO: refactor to match closer to the semantics of rust counterpart, rev() + for i := len(rh) - 1 - 1; i > -1; i-- { + p.runHistory[i+1] = rh[i] + } + rh[0] = currentrunlength +} + +// Can only be called immediately after a light run is added, and returns either 0, 1, or 2. +func (p finderPenalty) countPatterns() int32 { + rh := p.runHistory + n := rh[1] + if n > p.qrSize*3 { + panic("n > p.qrSize*3") + } + core := n > 0 && rh[2] == n && rh[3] == n*3 && rh[4] == n && rh[5] == n + return mathx.BoolToInt32(core && rh[0] >= n*4 && rh[6] >= n) + mathx.BoolToInt32(core && rh[6] >= n*4 && rh[0] >= n) +} + +// Must be called at the end of a line (row or column) of modules. +func (p *finderPenalty) terminateAndCount(currentruncolor bool, currentrunlength int32) int32 { + if currentruncolor { // Terminate dark run + p.addHistory(currentrunlength) + currentrunlength = 0 + } + currentrunlength += p.qrSize // Add light border to final run + p.addHistory(currentrunlength) + return p.countPatterns() +} + +/*---- Constants and tables ----*/ + +// For use in getPenaltyScore(), when evaluating which mask is best. +const ( + PENALTY_N1 int32 = 3 + PENALTY_N2 int32 = 3 + PENALTY_N3 int32 = 40 + PENALTY_N4 int32 = 10 +) + +var ( + ECC_CODEWORDS_PER_BLOCK [4][41]int8 = [4][41]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 + } + + NUM_ERROR_CORRECTION_BLOCKS [4][41]int8 = [4][41]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 + } +) diff --git a/golang/qrsegment/bitbuffer.go b/golang/qrsegment/bitbuffer.go new file mode 100644 index 0000000..7a9db93 --- /dev/null +++ b/golang/qrsegment/bitbuffer.go @@ -0,0 +1,32 @@ +package qrsegment + +import "github.com/nayuki/qrcodegen/internal/bitx" + +/*---- Bit buffer functionality ----*/ + +// BitBuffer is an appendable sequence of bits (0s and 1s). +// +// Mainly used by QrSegment. +type BitBuffer []bool + +// AppendBits appends the given number of low-order bits of the given value to this buffer. +// +// Requires len ≤ 31 and val < 2len. +func (b *BitBuffer) AppendBits(val uint32, len uint8) { + if len > 31 || (val>>len) != 0 { + panic("Value out of range") + } + + // TODO: refactor to match closer to the semantics of rust counterpart, rev() + if len == 0 { + return + } + tmp := make([]bool, len) + for i := int32(len - 1); i > -1; i-- { // Append bit by bit + v := bitx.GetBit(val, i) + tmp[int32(len-1)-i] = v + } + + res := append([]bool(*b), tmp...) + *b = BitBuffer(res) +} diff --git a/golang/qrsegment/mode.go b/golang/qrsegment/mode.go new file mode 100644 index 0000000..f811eb0 --- /dev/null +++ b/golang/qrsegment/mode.go @@ -0,0 +1,59 @@ +package qrsegment + +import "github.com/nayuki/qrcodegen/version" + +/*---- QrSegmentMode functionality ----*/ + +// QrSegmentMode describes how a segment's data bits are interpreted. +type QrSegmentMode uint32 + +const ( + ModeNumeric QrSegmentMode = iota + ModeAlphanumeric + ModeByte + ModeKanji + ModeEci +) + +// ModeBits returns an unsigned 4-bit integer value (range 0 to 15) +// representing the mode indicator bits for this mode object. +func (m QrSegmentMode) ModeBits() uint32 { + switch m { + case ModeNumeric: + return 0x1 + case ModeAlphanumeric: + return 0x2 + case ModeByte: + return 0x4 + case ModeKanji: + return 0x8 + case ModeEci: + return 0x7 + default: + panic("unknown QrSegmentMode") + } +} + +// NumCharCountBits returns the bit width of the character count field for a segment in this mode +// in a QR Code at the given version number. The result is in the range [0, 16]. +func (m QrSegmentMode) NumCharCountBits(ver version.Version) uint8 { + var tmp [3]uint8 + + switch m { + case ModeNumeric: + tmp = [3]uint8{10, 12, 14} + case ModeAlphanumeric: + tmp = [3]uint8{9, 11, 13} + case ModeByte: + tmp = [3]uint8{8, 16, 16} + case ModeKanji: + tmp = [3]uint8{8, 10, 12} + case ModeEci: + tmp = [3]uint8{0, 0, 0} + default: + panic("unknown QrSegmentMode") + } + + idx := (ver.Value() + 7) / 17 + return tmp[idx] +} diff --git a/golang/qrsegment/qrsegment.go b/golang/qrsegment/qrsegment.go new file mode 100644 index 0000000..990a5ea --- /dev/null +++ b/golang/qrsegment/qrsegment.go @@ -0,0 +1,260 @@ +package qrsegment + +import ( + "github.com/nayuki/qrcodegen/version" +) + +// The set of all legal characters in alphanumeric mode, +// where each character value maps to the index in the string. +var ( + ALPHANUMERIC_CHARSET = [45]rune{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z', + ' ', '$', '%', '*', '+', '-', '.', '/', ':'} + alphanumericCharset = make(map[rune]int, 45) +) + +func init() { + for i, c := range ALPHANUMERIC_CHARSET { + alphanumericCharset[c] = i + } +} + +/*---- QrSegment functionality ----*/ + +// QrSegment is a segment of character/binary/control data in a QR Code symbol. +// +// Instances of this struct are immutable. +// +// The mid-level way to create a segment is to take the payload data +// and call a static factory function such as `QrSegment::make_numeric()`. +// The low-level way to create a segment is to custom-make the bit buffer +// and call the `QrSegment::new()` constructor with appropriate values. +// +// This segment struct imposes no length restrictions, but QR Codes have restrictions. +// Even in the most favorable conditions, a QR Code can only hold 7089 characters of data. +// Any segment longer than this is meaningless for the purpose of generating QR Codes. +type QrSegment struct { + // The mode indicator of this segment. Accessed through mode(). + mode QrSegmentMode + // The length of this segment's unencoded data. Measured in characters for + // numeric/alphanumeric/kanji mode, bytes for byte mode, and 0 for ECI mode. + // Not the same as the data's bit length. Accessed through num_chars(). + numchars uint + // The data bits of this segment. Accessed through data(). + data []bool +} + +/*---- Static factory functions (mid level) ----*/ + +// MakeBytes returns a segment representing the given binary data encoded in byte mode. +// +// All input byte slices are acceptable. +// +// Any text string can be converted to UTF-8 bytes and encoded as a byte mode segment. +func MakeBytes(data []uint8) QrSegment { + bb := make(BitBuffer, 0, len(data)*8) + for _, b := range data { + bb.AppendBits(uint32(b), 8) + } + + return QrSegment{ + mode: ModeByte, + numchars: uint(len(data)), + data: bb, + } +} + +// MakeNumeric returns a segment representing the given string of decimal digits encoded in numeric mode. +// +// Panics if the string contains non-digit characters. +func MakeNumeric(text []rune) QrSegment { + bb := make(BitBuffer, 0, len(text)*3+(len(text)+2)/3) + var accumdata uint32 + var accumcount uint8 + for _, c := range text { + if '0' > c || c > '9' { + panic("String contains non-numeric characters") + } + accumdata = accumdata*10 + uint32(c) - uint32('0') + accumcount += 1 + if accumcount == 3 { + bb.AppendBits(accumdata, 10) + accumdata = 0 + accumcount = 0 + } + } + if accumcount > 0 { // 1 or 2 digits remaining + bb.AppendBits(accumdata, accumcount*3+1) + } + + return QrSegment{ + mode: ModeNumeric, + numchars: uint(len(text)), + data: bb, + } +} + +// MakeAlphanumeric returns a segment representing the given text string encoded in alphanumeric mode. +// +// The characters allowed are: 0 to 9, A to Z (uppercase only), space, +// dollar, percent, asterisk, plus, hyphen, period, slash, colon. +// +// Panics if the string contains non-encodable characters. +func MakeAlphanumeric(text []rune) QrSegment { + bb := make(BitBuffer, 0, len(text)*5+(len(text)+1)/2) + var accumdata uint32 + var accumcount uint32 + for _, c := range text { + idx, ok := alphanumericCharset[c] + if !ok { + panic("String contains unencodable characters in alphanumeric mode") + } + accumdata = accumdata*45 + uint32(idx) + accumcount += 1 + if accumcount == 2 { + bb.AppendBits(accumdata, 11) + accumdata = 0 + accumcount = 0 + } + } + if accumcount > 0 { // 1 character remaining + bb.AppendBits(accumdata, 6) + } + + return QrSegment{ + mode: ModeAlphanumeric, + numchars: uint(len(text)), + data: bb, + } +} + +// MakeSegments returns a list of zero or more segments to represent the given Unicode text string. +// +// The result may use various segment modes and switch +// modes to optimize the length of the bit stream. +func MakeSegments(text []rune) []QrSegment { + if len(text) == 0 { + return []QrSegment{} + } + + var seg QrSegment + if IsNumeric(text) { + seg = MakeNumeric(text) + } else if IsAlphanumeric(text) { + seg = MakeAlphanumeric(text) + } else { + seg = MakeBytes([]byte(string(text))) + } + + return []QrSegment{seg} +} + +// MakeEci returns a segment representing an Extended Channel Interpretation +// (ECI) designator with the given assignment value. +func MakeEci(assignval uint32) QrSegment { + bb := make(BitBuffer, 0, 24) + if assignval < (1 << 7) { + bb.AppendBits(assignval, 0) + } else if assignval < (1 << 14) { + bb.AppendBits(2, 2) + bb.AppendBits(assignval, 14) + } else if assignval < 1_000_000 { + bb.AppendBits(6, 3) + bb.AppendBits(assignval, 21) + } else { + panic("ECI assignment value out of range") + } + + return QrSegment{ + mode: ModeEci, + numchars: 0, + data: bb, + } +} + +/*---- Constructor (low level) ----*/ + +// New creates a new QR Code segment with the given attributes and data. +// +// The character count (numchars) must agree with the mode and +// the bit buffer length, but the constraint isn't checked. +func New(mode QrSegmentMode, numchars uint, data []bool) QrSegment { + return QrSegment{ + mode: mode, + numchars: numchars, + data: data, + } +} + +/*---- Instance field getters ----*/ + +// Mode returns the mode indicator of this segment. +func (s QrSegment) Mode() QrSegmentMode { + return s.mode +} + +// NumChars returns the character count field of this segment. +func (s QrSegment) NumChars() uint { + return s.numchars +} + +// Data returns the data bits of this segment. +func (s QrSegment) Data() []bool { + return s.data +} + +/*---- Other static functions ----*/ + +// GetTotalBits calculates and returns the number of bits needed to encode the given +// segments at the given version. The result is None if a segment has too many +// characters to fit its length field, or the total bits exceeds usize::MAX. +func GetTotalBits(segs []QrSegment, ver version.Version) *uint { + var result uint + for _, seg := range segs { + ccbits := seg.mode.NumCharCountBits(ver) + // TODO: refactor to match closer to the semantics of rust counterpart to check overflow + // // ccbits can be as large as 16, but usize can be as small as 16 + // if let Some(limit) = 1usize.checked_shl(u32::from(ccbits)) { + // if seg.numchars >= limit { + // return None; // The segment's length doesn't fit the field's bit width + // } + // } + limit := uint(1) << ccbits + if seg.numchars >= limit { + return nil // The segment's length doesn't fit the field's bit width + } + + result += 4 + uint(ccbits) + result += uint(len(seg.data)) + } + + return &result +} + +// IsNumeric tests whether the given string can be encoded as a segment in numeric mode. +// +// A string is encodable iff each character is in the range 0 to 9. +func IsNumeric(text []rune) bool { + for _, c := range text { + if c < '0' || c > '9' { + return false + } + } + + return true +} + +// IsAlphanumeric tests whether the given string can be encoded as a segment in alphanumeric mode. +// +// A string is encodable iff each character is in the following set: 0 to 9, A to Z +// (uppercase only), space, dollar, percent, asterisk, plus, hyphen, period, slash, colon. +func IsAlphanumeric(text []rune) bool { + for _, c := range text { + _, ok := alphanumericCharset[c] + if !ok { + return false + } + } + + return true +} diff --git a/golang/version/version.go b/golang/version/version.go new file mode 100644 index 0000000..0671e9c --- /dev/null +++ b/golang/version/version.go @@ -0,0 +1,27 @@ +package version + +// Version is a number between 1 and 40 (inclusive). +type Version uint8 + +const ( + // Min is the minimum version number supported in the QR Code Model 2 standard. + Min = Version(1) + // Max is the maximum version number supported in the QR Code Model 2 standard. + Max = Version(40) +) + +// New creates a version object from the given number. +// +// Panics if the number is outside the range [1, 40]. +func New(ver uint8) Version { + if ver < uint8(Min) || ver > uint8(Max) { + panic("Version number out of range") + } + + return Version(ver) +} + +// Value returns the value, which is in the range [1, 40]. +func (v Version) Value() uint8 { + return uint8(v) +}