git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/barcode/ean/encoder.go (about)

     1  // Package ean can create EAN 8 and EAN 13 barcodes.
     2  package ean
     3  
     4  import (
     5  	"errors"
     6  
     7  	"git.sr.ht/~pingoo/stdx/barcode"
     8  	"git.sr.ht/~pingoo/stdx/barcode/utils"
     9  )
    10  
    11  type encodedNumber struct {
    12  	LeftOdd  []bool
    13  	LeftEven []bool
    14  	Right    []bool
    15  	CheckSum []bool
    16  }
    17  
    18  var encoderTable = map[rune]encodedNumber{
    19  	'0': encodedNumber{
    20  		[]bool{false, false, false, true, true, false, true},
    21  		[]bool{false, true, false, false, true, true, true},
    22  		[]bool{true, true, true, false, false, true, false},
    23  		[]bool{false, false, false, false, false, false},
    24  	},
    25  	'1': encodedNumber{
    26  		[]bool{false, false, true, true, false, false, true},
    27  		[]bool{false, true, true, false, false, true, true},
    28  		[]bool{true, true, false, false, true, true, false},
    29  		[]bool{false, false, true, false, true, true},
    30  	},
    31  	'2': encodedNumber{
    32  		[]bool{false, false, true, false, false, true, true},
    33  		[]bool{false, false, true, true, false, true, true},
    34  		[]bool{true, true, false, true, true, false, false},
    35  		[]bool{false, false, true, true, false, true},
    36  	},
    37  	'3': encodedNumber{
    38  		[]bool{false, true, true, true, true, false, true},
    39  		[]bool{false, true, false, false, false, false, true},
    40  		[]bool{true, false, false, false, false, true, false},
    41  		[]bool{false, false, true, true, true, false},
    42  	},
    43  	'4': encodedNumber{
    44  		[]bool{false, true, false, false, false, true, true},
    45  		[]bool{false, false, true, true, true, false, true},
    46  		[]bool{true, false, true, true, true, false, false},
    47  		[]bool{false, true, false, false, true, true},
    48  	},
    49  	'5': encodedNumber{
    50  		[]bool{false, true, true, false, false, false, true},
    51  		[]bool{false, true, true, true, false, false, true},
    52  		[]bool{true, false, false, true, true, true, false},
    53  		[]bool{false, true, true, false, false, true},
    54  	},
    55  	'6': encodedNumber{
    56  		[]bool{false, true, false, true, true, true, true},
    57  		[]bool{false, false, false, false, true, false, true},
    58  		[]bool{true, false, true, false, false, false, false},
    59  		[]bool{false, true, true, true, false, false},
    60  	},
    61  	'7': encodedNumber{
    62  		[]bool{false, true, true, true, false, true, true},
    63  		[]bool{false, false, true, false, false, false, true},
    64  		[]bool{true, false, false, false, true, false, false},
    65  		[]bool{false, true, false, true, false, true},
    66  	},
    67  	'8': encodedNumber{
    68  		[]bool{false, true, true, false, true, true, true},
    69  		[]bool{false, false, false, true, false, false, true},
    70  		[]bool{true, false, false, true, false, false, false},
    71  		[]bool{false, true, false, true, true, false},
    72  	},
    73  	'9': encodedNumber{
    74  		[]bool{false, false, false, true, false, true, true},
    75  		[]bool{false, false, true, false, true, true, true},
    76  		[]bool{true, true, true, false, true, false, false},
    77  		[]bool{false, true, true, false, true, false},
    78  	},
    79  }
    80  
    81  func calcCheckNum(code string) rune {
    82  	x3 := len(code) == 7
    83  	sum := 0
    84  	for _, r := range code {
    85  		curNum := utils.RuneToInt(r)
    86  		if curNum < 0 || curNum > 9 {
    87  			return 'B'
    88  		}
    89  		if x3 {
    90  			curNum = curNum * 3
    91  		}
    92  		x3 = !x3
    93  		sum += curNum
    94  	}
    95  
    96  	return utils.IntToRune((10 - (sum % 10)) % 10)
    97  }
    98  
    99  func encodeEAN8(code string) *utils.BitList {
   100  	result := new(utils.BitList)
   101  	result.AddBit(true, false, true)
   102  
   103  	for cpos, r := range code {
   104  		num, ok := encoderTable[r]
   105  		if !ok {
   106  			return nil
   107  		}
   108  		var data []bool
   109  		if cpos < 4 {
   110  			data = num.LeftOdd
   111  		} else {
   112  			data = num.Right
   113  		}
   114  
   115  		if cpos == 4 {
   116  			result.AddBit(false, true, false, true, false)
   117  		}
   118  		result.AddBit(data...)
   119  	}
   120  	result.AddBit(true, false, true)
   121  
   122  	return result
   123  }
   124  
   125  func encodeEAN13(code string) *utils.BitList {
   126  	result := new(utils.BitList)
   127  	result.AddBit(true, false, true)
   128  
   129  	var firstNum []bool
   130  	for cpos, r := range code {
   131  		num, ok := encoderTable[r]
   132  		if !ok {
   133  			return nil
   134  		}
   135  		if cpos == 0 {
   136  			firstNum = num.CheckSum
   137  			continue
   138  		}
   139  
   140  		var data []bool
   141  		if cpos < 7 { // Left
   142  			if firstNum[cpos-1] {
   143  				data = num.LeftEven
   144  			} else {
   145  				data = num.LeftOdd
   146  			}
   147  		} else {
   148  			data = num.Right
   149  		}
   150  
   151  		if cpos == 7 {
   152  			result.AddBit(false, true, false, true, false)
   153  		}
   154  		result.AddBit(data...)
   155  	}
   156  	result.AddBit(true, false, true)
   157  	return result
   158  }
   159  
   160  // Encode returns a EAN 8 or EAN 13 barcode for the given code
   161  func Encode(code string) (barcode.BarcodeIntCS, error) {
   162  	var checkSum int
   163  	if len(code) == 7 || len(code) == 12 {
   164  		code += string(calcCheckNum(code))
   165  		checkSum = utils.RuneToInt(calcCheckNum(code))
   166  	} else if len(code) == 8 || len(code) == 13 {
   167  		check := code[0 : len(code)-1]
   168  		check += string(calcCheckNum(check))
   169  		if check != code {
   170  			return nil, errors.New("checksum missmatch")
   171  		}
   172  		checkSum = utils.RuneToInt(rune(code[len(code)-1]))
   173  	}
   174  
   175  	if len(code) == 8 {
   176  		result := encodeEAN8(code)
   177  		if result != nil {
   178  			return utils.New1DCodeIntCheckSum(barcode.TypeEAN8, code, result, checkSum), nil
   179  		}
   180  	} else if len(code) == 13 {
   181  		result := encodeEAN13(code)
   182  		if result != nil {
   183  			return utils.New1DCodeIntCheckSum(barcode.TypeEAN13, code, result, checkSum), nil
   184  		}
   185  	}
   186  	return nil, errors.New("invalid ean code data")
   187  }