git.sr.ht/~pingoo/stdx@v0.0.0-20240218134121-094174641f6e/barcode/pdf417/highlevel.go (about) 1 package pdf417 2 3 import ( 4 "errors" 5 "math/big" 6 7 "git.sr.ht/~pingoo/stdx/barcode/utils" 8 ) 9 10 type encodingMode byte 11 12 type subMode byte 13 14 const ( 15 encText encodingMode = iota 16 encNumeric 17 encBinary 18 19 subUpper subMode = iota 20 subLower 21 subMixed 22 subPunct 23 24 latch_to_text = 900 25 latch_to_byte_padded = 901 26 latch_to_numeric = 902 27 latch_to_byte = 924 28 shift_to_byte = 913 29 30 min_numeric_count = 13 31 ) 32 33 var ( 34 mixedMap map[rune]int 35 punctMap map[rune]int 36 ) 37 38 func init() { 39 mixedMap = make(map[rune]int) 40 mixedRaw := []rune{ 41 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 38, 13, 9, 44, 58, 42 35, 45, 46, 36, 47, 43, 37, 42, 61, 94, 0, 32, 0, 0, 0, 43 } 44 for idx, ch := range mixedRaw { 45 if ch > 0 { 46 mixedMap[ch] = idx 47 } 48 } 49 50 punctMap = make(map[rune]int) 51 punctRaw := []rune{ 52 59, 60, 62, 64, 91, 92, 93, 95, 96, 126, 33, 13, 9, 44, 58, 53 10, 45, 46, 36, 47, 34, 124, 42, 40, 41, 63, 123, 125, 39, 0, 54 } 55 for idx, ch := range punctRaw { 56 if ch > 0 { 57 punctMap[ch] = idx 58 } 59 } 60 } 61 62 func determineConsecutiveDigitCount(data []rune) int { 63 cnt := 0 64 for _, r := range data { 65 if utils.RuneToInt(r) == -1 { 66 break 67 } 68 cnt++ 69 } 70 return cnt 71 } 72 73 func encodeNumeric(digits []rune) ([]int, error) { 74 digitCount := len(digits) 75 chunkCount := digitCount / 44 76 if digitCount%44 != 0 { 77 chunkCount++ 78 } 79 80 codeWords := []int{} 81 82 for i := 0; i < chunkCount; i++ { 83 start := i * 44 84 end := start + 44 85 if end > digitCount { 86 end = digitCount 87 } 88 chunk := digits[start:end] 89 90 chunkNum := big.NewInt(0) 91 _, ok := chunkNum.SetString("1"+string(chunk), 10) 92 93 if !ok { 94 return nil, errors.New("Failed converting: " + string(chunk)) 95 } 96 97 cws := []int{} 98 99 for chunkNum.Cmp(big.NewInt(0)) > 0 { 100 newChunk, cw := chunkNum.DivMod(chunkNum, big.NewInt(900), big.NewInt(0)) 101 chunkNum = newChunk 102 cws = append([]int{int(cw.Int64())}, cws...) 103 } 104 105 codeWords = append(codeWords, cws...) 106 } 107 108 return codeWords, nil 109 } 110 111 func determineConsecutiveTextCount(msg []rune) int { 112 result := 0 113 114 isText := func(ch rune) bool { 115 return ch == '\t' || ch == '\n' || ch == '\r' || (ch >= 32 && ch <= 126) 116 } 117 118 for i, ch := range msg { 119 numericCount := determineConsecutiveDigitCount(msg[i:]) 120 if numericCount >= min_numeric_count || (numericCount == 0 && !isText(ch)) { 121 break 122 } 123 124 result++ 125 } 126 return result 127 } 128 129 func encodeText(text []rune, submode subMode) (subMode, []int) { 130 isAlphaUpper := func(ch rune) bool { 131 return ch == ' ' || (ch >= 'A' && ch <= 'Z') 132 } 133 isAlphaLower := func(ch rune) bool { 134 return ch == ' ' || (ch >= 'a' && ch <= 'z') 135 } 136 isMixed := func(ch rune) bool { 137 _, ok := mixedMap[ch] 138 return ok 139 } 140 isPunctuation := func(ch rune) bool { 141 _, ok := punctMap[ch] 142 return ok 143 } 144 145 idx := 0 146 var tmp []int 147 for idx < len(text) { 148 ch := text[idx] 149 switch submode { 150 case subUpper: 151 if isAlphaUpper(ch) { 152 if ch == ' ' { 153 tmp = append(tmp, 26) //space 154 } else { 155 tmp = append(tmp, int(ch-'A')) 156 } 157 } else { 158 if isAlphaLower(ch) { 159 submode = subLower 160 tmp = append(tmp, 27) // lower latch 161 continue 162 } else if isMixed(ch) { 163 submode = subMixed 164 tmp = append(tmp, 28) // mixed latch 165 continue 166 } else { 167 tmp = append(tmp, 29) // punctuation switch 168 tmp = append(tmp, punctMap[ch]) 169 break 170 } 171 } 172 break 173 case subLower: 174 if isAlphaLower(ch) { 175 if ch == ' ' { 176 tmp = append(tmp, 26) //space 177 } else { 178 tmp = append(tmp, int(ch-'a')) 179 } 180 } else { 181 if isAlphaUpper(ch) { 182 tmp = append(tmp, 27) //upper switch 183 tmp = append(tmp, int(ch-'A')) 184 break 185 } else if isMixed(ch) { 186 submode = subMixed 187 tmp = append(tmp, 28) //mixed latch 188 continue 189 } else { 190 tmp = append(tmp, 29) //punctuation switch 191 tmp = append(tmp, punctMap[ch]) 192 break 193 } 194 } 195 break 196 case subMixed: 197 if isMixed(ch) { 198 tmp = append(tmp, mixedMap[ch]) 199 } else { 200 if isAlphaUpper(ch) { 201 submode = subUpper 202 tmp = append(tmp, 28) //upper latch 203 continue 204 } else if isAlphaLower(ch) { 205 submode = subLower 206 tmp = append(tmp, 27) //lower latch 207 continue 208 } else { 209 if idx+1 < len(text) { 210 next := text[idx+1] 211 if isPunctuation(next) { 212 submode = subPunct 213 tmp = append(tmp, 25) //punctuation latch 214 continue 215 } 216 } 217 tmp = append(tmp, 29) //punctuation switch 218 tmp = append(tmp, punctMap[ch]) 219 } 220 } 221 break 222 default: //subPunct 223 if isPunctuation(ch) { 224 tmp = append(tmp, punctMap[ch]) 225 } else { 226 submode = subUpper 227 tmp = append(tmp, 29) //upper latch 228 continue 229 } 230 } 231 idx++ 232 } 233 234 h := 0 235 result := []int{} 236 for i, val := range tmp { 237 if i%2 != 0 { 238 h = (h * 30) + val 239 result = append(result, h) 240 } else { 241 h = val 242 } 243 } 244 if len(tmp)%2 != 0 { 245 result = append(result, (h*30)+29) 246 } 247 return submode, result 248 } 249 250 func determineConsecutiveBinaryCount(msg []byte) int { 251 result := 0 252 253 for i, _ := range msg { 254 numericCount := determineConsecutiveDigitCount([]rune(string(msg[i:]))) 255 if numericCount >= min_numeric_count { 256 break 257 } 258 textCount := determineConsecutiveTextCount([]rune(string(msg[i:]))) 259 if textCount > 5 { 260 break 261 } 262 result++ 263 } 264 return result 265 } 266 267 func encodeBinary(data []byte, startmode encodingMode) []int { 268 result := []int{} 269 270 count := len(data) 271 if count == 1 && startmode == encText { 272 result = append(result, shift_to_byte) 273 } else if (count % 6) == 0 { 274 result = append(result, latch_to_byte) 275 } else { 276 result = append(result, latch_to_byte_padded) 277 } 278 279 idx := 0 280 // Encode sixpacks 281 if count >= 6 { 282 words := make([]int, 5) 283 for (count - idx) >= 6 { 284 var t int64 = 0 285 for i := 0; i < 6; i++ { 286 t = t << 8 287 t += int64(data[idx+i]) 288 } 289 for i := 0; i < 5; i++ { 290 words[4-i] = int(t % 900) 291 t = t / 900 292 } 293 result = append(result, words...) 294 idx += 6 295 } 296 } 297 //Encode rest (remaining n<5 bytes if any) 298 for i := idx; i < count; i++ { 299 result = append(result, int(data[i]&0xff)) 300 } 301 return result 302 } 303 304 func highlevelEncode(dataStr string) ([]int, error) { 305 encodingMode := encText 306 textSubMode := subUpper 307 308 result := []int{} 309 310 data := []byte(dataStr) 311 312 for len(data) > 0 { 313 numericCount := determineConsecutiveDigitCount([]rune(string(data))) 314 if numericCount >= min_numeric_count || numericCount == len(data) { 315 result = append(result, latch_to_numeric) 316 encodingMode = encNumeric 317 textSubMode = subUpper 318 numData, err := encodeNumeric([]rune(string(data[:numericCount]))) 319 if err != nil { 320 return nil, err 321 } 322 result = append(result, numData...) 323 data = data[numericCount:] 324 } else { 325 textCount := determineConsecutiveTextCount([]rune(string(data))) 326 if textCount >= 5 || textCount == len(data) { 327 if encodingMode != encText { 328 result = append(result, latch_to_text) 329 encodingMode = encText 330 textSubMode = subUpper 331 } 332 var txtData []int 333 textSubMode, txtData = encodeText([]rune(string(data[:textCount])), textSubMode) 334 result = append(result, txtData...) 335 data = data[textCount:] 336 } else { 337 binaryCount := determineConsecutiveBinaryCount(data) 338 if binaryCount == 0 { 339 binaryCount = 1 340 } 341 bytes := data[:binaryCount] 342 if len(bytes) != 1 || encodingMode != encText { 343 encodingMode = encBinary 344 textSubMode = subUpper 345 } 346 byteData := encodeBinary(bytes, encodingMode) 347 result = append(result, byteData...) 348 data = data[binaryCount:] 349 } 350 } 351 } 352 353 return result, nil 354 }