269 lines
7.3 KiB
Go
269 lines
7.3 KiB
Go
|
// 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
|
||
|
}
|