// Package aztec can create Aztec Code barcodes package aztec import ( "fmt" "github.com/boombuler/barcode" "github.com/boombuler/barcode/utils" ) const ( DEFAULT_EC_PERCENT = 33 DEFAULT_LAYERS = 0 max_nb_bits = 32 max_nb_bits_compact = 4 ) var ( word_size = []int{ 4, 6, 6, 8, 8, 8, 8, 8, 8, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, } ) func totalBitsInLayer(layers int, compact bool) int { tmp := 112 if compact { tmp = 88 } return (tmp + 16*layers) * layers } func stuffBits(bits *utils.BitList, wordSize int) *utils.BitList { out := new(utils.BitList) n := bits.Len() mask := (1 << uint(wordSize)) - 2 for i := 0; i < n; i += wordSize { word := 0 for j := 0; j < wordSize; j++ { if i+j >= n || bits.GetBit(i+j) { word |= 1 << uint(wordSize-1-j) } } if (word & mask) == mask { out.AddBits(word&mask, byte(wordSize)) i-- } else if (word & mask) == 0 { out.AddBits(word|1, byte(wordSize)) i-- } else { out.AddBits(word, byte(wordSize)) } } return out } func generateModeMessage(compact bool, layers, messageSizeInWords int) *utils.BitList { modeMessage := new(utils.BitList) if compact { modeMessage.AddBits(layers-1, 2) modeMessage.AddBits(messageSizeInWords-1, 6) modeMessage = generateCheckWords(modeMessage, 28, 4) } else { modeMessage.AddBits(layers-1, 5) modeMessage.AddBits(messageSizeInWords-1, 11) modeMessage = generateCheckWords(modeMessage, 40, 4) } return modeMessage } func drawModeMessage(matrix *aztecCode, compact bool, matrixSize int, modeMessage *utils.BitList) { center := matrixSize / 2 if compact { for i := 0; i < 7; i++ { offset := center - 3 + i if modeMessage.GetBit(i) { matrix.set(offset, center-5) } if modeMessage.GetBit(i + 7) { matrix.set(center+5, offset) } if modeMessage.GetBit(20 - i) { matrix.set(offset, center+5) } if modeMessage.GetBit(27 - i) { matrix.set(center-5, offset) } } } else { for i := 0; i < 10; i++ { offset := center - 5 + i + i/5 if modeMessage.GetBit(i) { matrix.set(offset, center-7) } if modeMessage.GetBit(i + 10) { matrix.set(center+7, offset) } if modeMessage.GetBit(29 - i) { matrix.set(offset, center+7) } if modeMessage.GetBit(39 - i) { matrix.set(center-7, offset) } } } } func drawBullsEye(matrix *aztecCode, center, size int) { for i := 0; i < size; i += 2 { for j := center - i; j <= center+i; j++ { matrix.set(j, center-i) matrix.set(j, center+i) matrix.set(center-i, j) matrix.set(center+i, j) } } matrix.set(center-size, center-size) matrix.set(center-size+1, center-size) matrix.set(center-size, center-size+1) matrix.set(center+size, center-size) matrix.set(center+size, center-size+1) matrix.set(center+size, center+size-1) } // Encode returns an aztec barcode with the given content func Encode(data []byte, minECCPercent int, userSpecifiedLayers int) (barcode.Barcode, error) { bits := highlevelEncode(data) eccBits := ((bits.Len() * minECCPercent) / 100) + 11 totalSizeBits := bits.Len() + eccBits var layers, TotalBitsInLayer, wordSize int var compact bool var stuffedBits *utils.BitList if userSpecifiedLayers != DEFAULT_LAYERS { compact = userSpecifiedLayers < 0 if compact { layers = -userSpecifiedLayers } else { layers = userSpecifiedLayers } if (compact && layers > max_nb_bits_compact) || (!compact && layers > max_nb_bits) { return nil, fmt.Errorf("Illegal value %d for layers", userSpecifiedLayers) } TotalBitsInLayer = totalBitsInLayer(layers, compact) wordSize = word_size[layers] usableBitsInLayers := TotalBitsInLayer - (TotalBitsInLayer % wordSize) stuffedBits = stuffBits(bits, wordSize) if stuffedBits.Len()+eccBits > usableBitsInLayers { return nil, fmt.Errorf("Data to large for user specified layer") } if compact && stuffedBits.Len() > wordSize*64 { return nil, fmt.Errorf("Data to large for user specified layer") } } else { wordSize = 0 stuffedBits = nil // We look at the possible table sizes in the order Compact1, Compact2, Compact3, // Compact4, Normal4,... Normal(i) for i < 4 isn't typically used since Compact(i+1) // is the same size, but has more data. for i := 0; ; i++ { if i > max_nb_bits { return nil, fmt.Errorf("Data too large for an aztec code") } compact = i <= 3 layers = i if compact { layers = i + 1 } TotalBitsInLayer = totalBitsInLayer(layers, compact) if totalSizeBits > TotalBitsInLayer { continue } // [Re]stuff the bits if this is the first opportunity, or if the // wordSize has changed if wordSize != word_size[layers] { wordSize = word_size[layers] stuffedBits = stuffBits(bits, wordSize) } usableBitsInLayers := TotalBitsInLayer - (TotalBitsInLayer % wordSize) if compact && stuffedBits.Len() > wordSize*64 { // Compact format only allows 64 data words, though C4 can hold more words than that continue } if stuffedBits.Len()+eccBits <= usableBitsInLayers { break } } } messageBits := generateCheckWords(stuffedBits, TotalBitsInLayer, wordSize) messageSizeInWords := stuffedBits.Len() / wordSize modeMessage := generateModeMessage(compact, layers, messageSizeInWords) // allocate symbol var baseMatrixSize int if compact { baseMatrixSize = 11 + layers*4 } else { baseMatrixSize = 14 + layers*4 } alignmentMap := make([]int, baseMatrixSize) var matrixSize int if compact { // no alignment marks in compact mode, alignmentMap is a no-op matrixSize = baseMatrixSize for i := 0; i < len(alignmentMap); i++ { alignmentMap[i] = i } } else { matrixSize = baseMatrixSize + 1 + 2*((baseMatrixSize/2-1)/15) origCenter := baseMatrixSize / 2 center := matrixSize / 2 for i := 0; i < origCenter; i++ { newOffset := i + i/15 alignmentMap[origCenter-i-1] = center - newOffset - 1 alignmentMap[origCenter+i] = center + newOffset + 1 } } code := newAztecCode(matrixSize) code.content = data // draw data bits for i, rowOffset := 0, 0; i < layers; i++ { rowSize := (layers - i) * 4 if compact { rowSize += 9 } else { rowSize += 12 } for j := 0; j < rowSize; j++ { columnOffset := j * 2 for k := 0; k < 2; k++ { if messageBits.GetBit(rowOffset + columnOffset + k) { code.set(alignmentMap[i*2+k], alignmentMap[i*2+j]) } if messageBits.GetBit(rowOffset + rowSize*2 + columnOffset + k) { code.set(alignmentMap[i*2+j], alignmentMap[baseMatrixSize-1-i*2-k]) } if messageBits.GetBit(rowOffset + rowSize*4 + columnOffset + k) { code.set(alignmentMap[baseMatrixSize-1-i*2-k], alignmentMap[baseMatrixSize-1-i*2-j]) } if messageBits.GetBit(rowOffset + rowSize*6 + columnOffset + k) { code.set(alignmentMap[baseMatrixSize-1-i*2-j], alignmentMap[i*2+k]) } } } rowOffset += rowSize * 8 } // draw mode message drawModeMessage(code, compact, matrixSize, modeMessage) // draw alignment marks if compact { drawBullsEye(code, matrixSize/2, 5) } else { drawBullsEye(code, matrixSize/2, 7) for i, j := 0, 0; i < baseMatrixSize/2-1; i, j = i+15, j+16 { for k := (matrixSize / 2) & 1; k < matrixSize; k += 2 { code.set(matrixSize/2-j, k) code.set(matrixSize/2+j, k) code.set(k, matrixSize/2-j) code.set(k, matrixSize/2+j) } } } return code, nil }