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 }