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

     1  // Package pdf417 can create PDF-417 barcodes
     2  package pdf417
     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  	padding_codeword = 900
    13  )
    14  
    15  // Encodes the given data as PDF417 barcode.
    16  // securityLevel should be between 0 and 8. The higher the number, the more
    17  // additional error-correction codes are added.
    18  func Encode(data string, securityLevel byte) (barcode.Barcode, error) {
    19  	if securityLevel >= 9 {
    20  		return nil, fmt.Errorf("Invalid security level %d", securityLevel)
    21  	}
    22  
    23  	sl := securitylevel(securityLevel)
    24  
    25  	dataWords, err := highlevelEncode(data)
    26  	if err != nil {
    27  		return nil, err
    28  	}
    29  
    30  	columns, rows := calcDimensions(len(dataWords), sl.ErrorCorrectionWordCount())
    31  	if columns < minCols || columns > maxCols || rows < minRows || rows > maxRows {
    32  		return nil, fmt.Errorf("Unable to fit data in barcode")
    33  	}
    34  
    35  	barcode := new(pdfBarcode)
    36  	barcode.data = data
    37  
    38  	codeWords, err := encodeData(dataWords, columns, sl)
    39  	if err != nil {
    40  		return nil, err
    41  	}
    42  
    43  	grid := [][]int{}
    44  	for i := 0; i < len(codeWords); i += columns {
    45  		grid = append(grid, codeWords[i:min(i+columns, len(codeWords))])
    46  	}
    47  
    48  	codes := [][]int{}
    49  
    50  	for rowNum, row := range grid {
    51  		table := rowNum % 3
    52  		rowCodes := make([]int, 0, columns+4)
    53  
    54  		rowCodes = append(rowCodes, start_word)
    55  		rowCodes = append(rowCodes, getCodeword(table, getLeftCodeWord(rowNum, rows, columns, securityLevel)))
    56  
    57  		for _, word := range row {
    58  			rowCodes = append(rowCodes, getCodeword(table, word))
    59  		}
    60  
    61  		rowCodes = append(rowCodes, getCodeword(table, getRightCodeWord(rowNum, rows, columns, securityLevel)))
    62  		rowCodes = append(rowCodes, stop_word)
    63  
    64  		codes = append(codes, rowCodes)
    65  	}
    66  
    67  	barcode.code = renderBarcode(codes)
    68  	barcode.width = (columns+4)*17 + 1
    69  
    70  	return barcode, nil
    71  }
    72  
    73  func encodeData(dataWords []int, columns int, sl securitylevel) ([]int, error) {
    74  	dataCount := len(dataWords)
    75  
    76  	ecCount := sl.ErrorCorrectionWordCount()
    77  
    78  	padWords := getPadding(dataCount, ecCount, columns)
    79  	dataWords = append(dataWords, padWords...)
    80  
    81  	length := len(dataWords) + 1
    82  	dataWords = append([]int{length}, dataWords...)
    83  
    84  	ecWords := sl.Compute(dataWords)
    85  
    86  	return append(dataWords, ecWords...), nil
    87  }
    88  
    89  func getLeftCodeWord(rowNum int, rows int, columns int, securityLevel byte) int {
    90  	tableId := rowNum % 3
    91  
    92  	var x int
    93  
    94  	switch tableId {
    95  	case 0:
    96  		x = (rows - 3) / 3
    97  	case 1:
    98  		x = int(securityLevel) * 3
    99  		x += (rows - 1) % 3
   100  	case 2:
   101  		x = columns - 1
   102  	}
   103  
   104  	return 30*(rowNum/3) + x
   105  }
   106  
   107  func getRightCodeWord(rowNum int, rows int, columns int, securityLevel byte) int {
   108  	tableId := rowNum % 3
   109  
   110  	var x int
   111  
   112  	switch tableId {
   113  	case 0:
   114  		x = columns - 1
   115  	case 1:
   116  		x = (rows - 1) / 3
   117  	case 2:
   118  		x = int(securityLevel) * 3
   119  		x += (rows - 1) % 3
   120  	}
   121  
   122  	return 30*(rowNum/3) + x
   123  }
   124  
   125  func min(a, b int) int {
   126  	if a <= b {
   127  		return a
   128  	}
   129  	return b
   130  }
   131  
   132  func getPadding(dataCount int, ecCount int, columns int) []int {
   133  	totalCount := dataCount + ecCount + 1
   134  	mod := totalCount % columns
   135  
   136  	padding := []int{}
   137  
   138  	if mod > 0 {
   139  		padCount := columns - mod
   140  		padding = make([]int, padCount)
   141  		for i := 0; i < padCount; i++ {
   142  			padding[i] = padding_codeword
   143  		}
   144  	}
   145  
   146  	return padding
   147  }
   148  
   149  func renderBarcode(codes [][]int) *utils.BitList {
   150  	bl := new(utils.BitList)
   151  	for _, row := range codes {
   152  		lastIdx := len(row) - 1
   153  		for i, col := range row {
   154  			if i == lastIdx {
   155  				bl.AddBits(col, 18)
   156  			} else {
   157  				bl.AddBits(col, 17)
   158  			}
   159  		}
   160  	}
   161  	return bl
   162  }