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

     1  // Package code128 can create Code128 barcodes
     2  package code128
     3  
     4  import (
     5  	"fmt"
     6  	"strings"
     7  	"unicode/utf8"
     8  
     9  	"git.sr.ht/~pingoo/stdx/barcode"
    10  	"git.sr.ht/~pingoo/stdx/barcode/utils"
    11  )
    12  
    13  func strToRunes(str string) []rune {
    14  	result := make([]rune, utf8.RuneCountInString(str))
    15  	i := 0
    16  	for _, r := range str {
    17  		result[i] = r
    18  		i++
    19  	}
    20  	return result
    21  }
    22  
    23  func shouldUseCTable(nextRunes []rune, curEncoding byte) bool {
    24  	requiredDigits := 4
    25  	if curEncoding == startCSymbol {
    26  		requiredDigits = 2
    27  	}
    28  	if len(nextRunes) < requiredDigits {
    29  		return false
    30  	}
    31  	for i := 0; i < requiredDigits; i++ {
    32  		if i%2 == 0 && nextRunes[i] == FNC1 {
    33  			requiredDigits++
    34  			if len(nextRunes) < requiredDigits {
    35  				return false
    36  			}
    37  			continue
    38  		}
    39  		if nextRunes[i] < '0' || nextRunes[i] > '9' {
    40  			return false
    41  		}
    42  	}
    43  	return true
    44  }
    45  
    46  func tableContainsRune(table string, r rune) bool {
    47  	return strings.ContainsRune(table, r) || r == FNC1 || r == FNC2 || r == FNC3 || r == FNC4
    48  }
    49  
    50  func shouldUseATable(nextRunes []rune, curEncoding byte) bool {
    51  	nextRune := nextRunes[0]
    52  	if !tableContainsRune(bTable, nextRune) || curEncoding == startASymbol {
    53  		return tableContainsRune(aTable, nextRune)
    54  	}
    55  	if curEncoding == 0 {
    56  		for _, r := range nextRunes {
    57  			if tableContainsRune(abTable, r) {
    58  				continue
    59  			}
    60  			if strings.ContainsRune(aOnlyTable, r) {
    61  				return true
    62  			}
    63  			break
    64  		}
    65  	}
    66  	return false
    67  }
    68  
    69  func getCodeIndexList(content []rune) *utils.BitList {
    70  	result := new(utils.BitList)
    71  	curEncoding := byte(0)
    72  	for i := 0; i < len(content); i++ {
    73  		if shouldUseCTable(content[i:], curEncoding) {
    74  			if curEncoding != startCSymbol {
    75  				if curEncoding == byte(0) {
    76  					result.AddByte(startCSymbol)
    77  				} else {
    78  					result.AddByte(codeCSymbol)
    79  				}
    80  				curEncoding = startCSymbol
    81  			}
    82  			if content[i] == FNC1 {
    83  				result.AddByte(102)
    84  			} else {
    85  				idx := (content[i] - '0') * 10
    86  				i++
    87  				idx = idx + (content[i] - '0')
    88  				result.AddByte(byte(idx))
    89  			}
    90  		} else if shouldUseATable(content[i:], curEncoding) {
    91  			if curEncoding != startASymbol {
    92  				if curEncoding == byte(0) {
    93  					result.AddByte(startASymbol)
    94  				} else {
    95  					result.AddByte(codeASymbol)
    96  				}
    97  				curEncoding = startASymbol
    98  			}
    99  			var idx int
   100  			switch content[i] {
   101  			case FNC1:
   102  				idx = 102
   103  				break
   104  			case FNC2:
   105  				idx = 97
   106  				break
   107  			case FNC3:
   108  				idx = 96
   109  				break
   110  			case FNC4:
   111  				idx = 101
   112  				break
   113  			default:
   114  				idx = strings.IndexRune(aTable, content[i])
   115  				break
   116  			}
   117  			if idx < 0 {
   118  				return nil
   119  			}
   120  			result.AddByte(byte(idx))
   121  		} else {
   122  			if curEncoding != startBSymbol {
   123  				if curEncoding == byte(0) {
   124  					result.AddByte(startBSymbol)
   125  				} else {
   126  					result.AddByte(codeBSymbol)
   127  				}
   128  				curEncoding = startBSymbol
   129  			}
   130  			var idx int
   131  			switch content[i] {
   132  			case FNC1:
   133  				idx = 102
   134  				break
   135  			case FNC2:
   136  				idx = 97
   137  				break
   138  			case FNC3:
   139  				idx = 96
   140  				break
   141  			case FNC4:
   142  				idx = 100
   143  				break
   144  			default:
   145  				idx = strings.IndexRune(bTable, content[i])
   146  				break
   147  			}
   148  
   149  			if idx < 0 {
   150  				return nil
   151  			}
   152  			result.AddByte(byte(idx))
   153  		}
   154  	}
   155  	return result
   156  }
   157  
   158  // Encode creates a Code 128 barcode for the given content
   159  func Encode(content string) (barcode.BarcodeIntCS, error) {
   160  	contentRunes := strToRunes(content)
   161  	if len(contentRunes) <= 0 || len(contentRunes) > 80 {
   162  		return nil, fmt.Errorf("content length should be between 1 and 80 runes but got %d", len(contentRunes))
   163  	}
   164  	idxList := getCodeIndexList(contentRunes)
   165  
   166  	if idxList == nil {
   167  		return nil, fmt.Errorf("\"%s\" could not be encoded", content)
   168  	}
   169  
   170  	result := new(utils.BitList)
   171  	sum := 0
   172  	for i, idx := range idxList.GetBytes() {
   173  		if i == 0 {
   174  			sum = int(idx)
   175  		} else {
   176  			sum += i * int(idx)
   177  		}
   178  		result.AddBit(encodingTable[idx]...)
   179  	}
   180  	sum = sum % 103
   181  	result.AddBit(encodingTable[sum]...)
   182  	result.AddBit(encodingTable[stopSymbol]...)
   183  	return utils.New1DCodeIntCheckSum(barcode.TypeCode128, content, result, sum), nil
   184  }
   185  
   186  func EncodeWithoutChecksum(content string) (barcode.Barcode, error) {
   187  	contentRunes := strToRunes(content)
   188  	if len(contentRunes) <= 0 || len(contentRunes) > 80 {
   189  		return nil, fmt.Errorf("content length should be between 1 and 80 runes but got %d", len(contentRunes))
   190  	}
   191  	idxList := getCodeIndexList(contentRunes)
   192  
   193  	if idxList == nil {
   194  		return nil, fmt.Errorf("\"%s\" could not be encoded", content)
   195  	}
   196  
   197  	result := new(utils.BitList)
   198  	for _, idx := range idxList.GetBytes() {
   199  		result.AddBit(encodingTable[idx]...)
   200  	}
   201  	result.AddBit(encodingTable[stopSymbol]...)
   202  	return utils.New1DCode(barcode.TypeCode128, content, result), nil
   203  }