git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/barcode/aztec/encoder.go (about) 1 // Package aztec can create Aztec Code barcodes 2 package aztec 3 4 import ( 5 "fmt" 6 7 "git.sr.ht/~pingoo/stdx/barcode" 8 "git.sr.ht/~pingoo/stdx/barcode/utils" 9 ) 10 11 const ( 12 DEFAULT_EC_PERCENT = 33 13 DEFAULT_LAYERS = 0 14 max_nb_bits = 32 15 max_nb_bits_compact = 4 16 ) 17 18 var ( 19 word_size = []int{ 20 4, 6, 6, 8, 8, 8, 8, 8, 8, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 10, 21 12, 12, 12, 12, 12, 12, 12, 12, 12, 12, 22 } 23 ) 24 25 func totalBitsInLayer(layers int, compact bool) int { 26 tmp := 112 27 if compact { 28 tmp = 88 29 } 30 return (tmp + 16*layers) * layers 31 } 32 33 func stuffBits(bits *utils.BitList, wordSize int) *utils.BitList { 34 out := new(utils.BitList) 35 n := bits.Len() 36 mask := (1 << uint(wordSize)) - 2 37 for i := 0; i < n; i += wordSize { 38 word := 0 39 for j := 0; j < wordSize; j++ { 40 if i+j >= n || bits.GetBit(i+j) { 41 word |= 1 << uint(wordSize-1-j) 42 } 43 } 44 if (word & mask) == mask { 45 out.AddBits(word&mask, byte(wordSize)) 46 i-- 47 } else if (word & mask) == 0 { 48 out.AddBits(word|1, byte(wordSize)) 49 i-- 50 } else { 51 out.AddBits(word, byte(wordSize)) 52 } 53 } 54 return out 55 } 56 57 func generateModeMessage(compact bool, layers, messageSizeInWords int) *utils.BitList { 58 modeMessage := new(utils.BitList) 59 if compact { 60 modeMessage.AddBits(layers-1, 2) 61 modeMessage.AddBits(messageSizeInWords-1, 6) 62 modeMessage = generateCheckWords(modeMessage, 28, 4) 63 } else { 64 modeMessage.AddBits(layers-1, 5) 65 modeMessage.AddBits(messageSizeInWords-1, 11) 66 modeMessage = generateCheckWords(modeMessage, 40, 4) 67 } 68 return modeMessage 69 } 70 71 func drawModeMessage(matrix *aztecCode, compact bool, matrixSize int, modeMessage *utils.BitList) { 72 center := matrixSize / 2 73 if compact { 74 for i := 0; i < 7; i++ { 75 offset := center - 3 + i 76 if modeMessage.GetBit(i) { 77 matrix.set(offset, center-5) 78 } 79 if modeMessage.GetBit(i + 7) { 80 matrix.set(center+5, offset) 81 } 82 if modeMessage.GetBit(20 - i) { 83 matrix.set(offset, center+5) 84 } 85 if modeMessage.GetBit(27 - i) { 86 matrix.set(center-5, offset) 87 } 88 } 89 } else { 90 for i := 0; i < 10; i++ { 91 offset := center - 5 + i + i/5 92 if modeMessage.GetBit(i) { 93 matrix.set(offset, center-7) 94 } 95 if modeMessage.GetBit(i + 10) { 96 matrix.set(center+7, offset) 97 } 98 if modeMessage.GetBit(29 - i) { 99 matrix.set(offset, center+7) 100 } 101 if modeMessage.GetBit(39 - i) { 102 matrix.set(center-7, offset) 103 } 104 } 105 } 106 } 107 108 func drawBullsEye(matrix *aztecCode, center, size int) { 109 for i := 0; i < size; i += 2 { 110 for j := center - i; j <= center+i; j++ { 111 matrix.set(j, center-i) 112 matrix.set(j, center+i) 113 matrix.set(center-i, j) 114 matrix.set(center+i, j) 115 } 116 } 117 matrix.set(center-size, center-size) 118 matrix.set(center-size+1, center-size) 119 matrix.set(center-size, center-size+1) 120 matrix.set(center+size, center-size) 121 matrix.set(center+size, center-size+1) 122 matrix.set(center+size, center+size-1) 123 } 124 125 // Encode returns an aztec barcode with the given content 126 func Encode(data []byte, minECCPercent int, userSpecifiedLayers int) (barcode.Barcode, error) { 127 bits := highlevelEncode(data) 128 eccBits := ((bits.Len() * minECCPercent) / 100) + 11 129 totalSizeBits := bits.Len() + eccBits 130 var layers, TotalBitsInLayer, wordSize int 131 var compact bool 132 var stuffedBits *utils.BitList 133 if userSpecifiedLayers != DEFAULT_LAYERS { 134 compact = userSpecifiedLayers < 0 135 if compact { 136 layers = -userSpecifiedLayers 137 } else { 138 layers = userSpecifiedLayers 139 } 140 if (compact && layers > max_nb_bits_compact) || (!compact && layers > max_nb_bits) { 141 return nil, fmt.Errorf("Illegal value %d for layers", userSpecifiedLayers) 142 } 143 TotalBitsInLayer = totalBitsInLayer(layers, compact) 144 wordSize = word_size[layers] 145 usableBitsInLayers := TotalBitsInLayer - (TotalBitsInLayer % wordSize) 146 stuffedBits = stuffBits(bits, wordSize) 147 if stuffedBits.Len()+eccBits > usableBitsInLayers { 148 return nil, fmt.Errorf("Data to large for user specified layer") 149 } 150 if compact && stuffedBits.Len() > wordSize*64 { 151 return nil, fmt.Errorf("Data to large for user specified layer") 152 } 153 } else { 154 wordSize = 0 155 stuffedBits = nil 156 // We look at the possible table sizes in the order Compact1, Compact2, Compact3, 157 // Compact4, Normal4,... Normal(i) for i < 4 isn't typically used since Compact(i+1) 158 // is the same size, but has more data. 159 for i := 0; ; i++ { 160 if i > max_nb_bits { 161 return nil, fmt.Errorf("Data too large for an aztec code") 162 } 163 compact = i <= 3 164 layers = i 165 if compact { 166 layers = i + 1 167 } 168 TotalBitsInLayer = totalBitsInLayer(layers, compact) 169 if totalSizeBits > TotalBitsInLayer { 170 continue 171 } 172 // [Re]stuff the bits if this is the first opportunity, or if the 173 // wordSize has changed 174 if wordSize != word_size[layers] { 175 wordSize = word_size[layers] 176 stuffedBits = stuffBits(bits, wordSize) 177 } 178 usableBitsInLayers := TotalBitsInLayer - (TotalBitsInLayer % wordSize) 179 if compact && stuffedBits.Len() > wordSize*64 { 180 // Compact format only allows 64 data words, though C4 can hold more words than that 181 continue 182 } 183 if stuffedBits.Len()+eccBits <= usableBitsInLayers { 184 break 185 } 186 } 187 } 188 messageBits := generateCheckWords(stuffedBits, TotalBitsInLayer, wordSize) 189 messageSizeInWords := stuffedBits.Len() / wordSize 190 modeMessage := generateModeMessage(compact, layers, messageSizeInWords) 191 192 // allocate symbol 193 var baseMatrixSize int 194 if compact { 195 baseMatrixSize = 11 + layers*4 196 } else { 197 baseMatrixSize = 14 + layers*4 198 } 199 alignmentMap := make([]int, baseMatrixSize) 200 var matrixSize int 201 202 if compact { 203 // no alignment marks in compact mode, alignmentMap is a no-op 204 matrixSize = baseMatrixSize 205 for i := 0; i < len(alignmentMap); i++ { 206 alignmentMap[i] = i 207 } 208 } else { 209 matrixSize = baseMatrixSize + 1 + 2*((baseMatrixSize/2-1)/15) 210 origCenter := baseMatrixSize / 2 211 center := matrixSize / 2 212 for i := 0; i < origCenter; i++ { 213 newOffset := i + i/15 214 alignmentMap[origCenter-i-1] = center - newOffset - 1 215 alignmentMap[origCenter+i] = center + newOffset + 1 216 } 217 } 218 code := newAztecCode(matrixSize) 219 code.content = data 220 221 // draw data bits 222 for i, rowOffset := 0, 0; i < layers; i++ { 223 rowSize := (layers - i) * 4 224 if compact { 225 rowSize += 9 226 } else { 227 rowSize += 12 228 } 229 230 for j := 0; j < rowSize; j++ { 231 columnOffset := j * 2 232 for k := 0; k < 2; k++ { 233 if messageBits.GetBit(rowOffset + columnOffset + k) { 234 code.set(alignmentMap[i*2+k], alignmentMap[i*2+j]) 235 } 236 if messageBits.GetBit(rowOffset + rowSize*2 + columnOffset + k) { 237 code.set(alignmentMap[i*2+j], alignmentMap[baseMatrixSize-1-i*2-k]) 238 } 239 if messageBits.GetBit(rowOffset + rowSize*4 + columnOffset + k) { 240 code.set(alignmentMap[baseMatrixSize-1-i*2-k], alignmentMap[baseMatrixSize-1-i*2-j]) 241 } 242 if messageBits.GetBit(rowOffset + rowSize*6 + columnOffset + k) { 243 code.set(alignmentMap[baseMatrixSize-1-i*2-j], alignmentMap[i*2+k]) 244 } 245 } 246 } 247 rowOffset += rowSize * 8 248 } 249 250 // draw mode message 251 drawModeMessage(code, compact, matrixSize, modeMessage) 252 253 // draw alignment marks 254 if compact { 255 drawBullsEye(code, matrixSize/2, 5) 256 } else { 257 drawBullsEye(code, matrixSize/2, 7) 258 for i, j := 0, 0; i < baseMatrixSize/2-1; i, j = i+15, j+16 { 259 for k := (matrixSize / 2) & 1; k < matrixSize; k += 2 { 260 code.set(matrixSize/2-j, k) 261 code.set(matrixSize/2+j, k) 262 code.set(k, matrixSize/2-j) 263 code.set(k, matrixSize/2+j) 264 } 265 } 266 } 267 return code, nil 268 }