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  }