package pdf417 import ( "errors" "math/big" "github.com/boombuler/barcode/utils" ) type encodingMode byte type subMode byte const ( encText encodingMode = iota encNumeric encBinary subUpper subMode = iota subLower subMixed subPunct latch_to_text = 900 latch_to_byte_padded = 901 latch_to_numeric = 902 latch_to_byte = 924 shift_to_byte = 913 min_numeric_count = 13 ) var ( mixedMap map[rune]int punctMap map[rune]int ) func init() { mixedMap = make(map[rune]int) mixedRaw := []rune{ 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 38, 13, 9, 44, 58, 35, 45, 46, 36, 47, 43, 37, 42, 61, 94, 0, 32, 0, 0, 0, } for idx, ch := range mixedRaw { if ch > 0 { mixedMap[ch] = idx } } punctMap = make(map[rune]int) punctRaw := []rune{ 59, 60, 62, 64, 91, 92, 93, 95, 96, 126, 33, 13, 9, 44, 58, 10, 45, 46, 36, 47, 34, 124, 42, 40, 41, 63, 123, 125, 39, 0, } for idx, ch := range punctRaw { if ch > 0 { punctMap[ch] = idx } } } func determineConsecutiveDigitCount(data []rune) int { cnt := 0 for _, r := range data { if utils.RuneToInt(r) == -1 { break } cnt++ } return cnt } func encodeNumeric(digits []rune) ([]int, error) { digitCount := len(digits) chunkCount := digitCount / 44 if digitCount%44 != 0 { chunkCount++ } codeWords := []int{} for i := 0; i < chunkCount; i++ { start := i * 44 end := start + 44 if end > digitCount { end = digitCount } chunk := digits[start:end] chunkNum := big.NewInt(0) _, ok := chunkNum.SetString("1"+string(chunk), 10) if !ok { return nil, errors.New("Failed converting: " + string(chunk)) } cws := []int{} for chunkNum.Cmp(big.NewInt(0)) > 0 { newChunk, cw := chunkNum.DivMod(chunkNum, big.NewInt(900), big.NewInt(0)) chunkNum = newChunk cws = append([]int{int(cw.Int64())}, cws...) } codeWords = append(codeWords, cws...) } return codeWords, nil } func determineConsecutiveTextCount(msg []rune) int { result := 0 isText := func(ch rune) bool { return ch == '\t' || ch == '\n' || ch == '\r' || (ch >= 32 && ch <= 126) } for i, ch := range msg { numericCount := determineConsecutiveDigitCount(msg[i:]) if numericCount >= min_numeric_count || (numericCount == 0 && !isText(ch)) { break } result++ } return result } func encodeText(text []rune, submode subMode) (subMode, []int) { isAlphaUpper := func(ch rune) bool { return ch == ' ' || (ch >= 'A' && ch <= 'Z') } isAlphaLower := func(ch rune) bool { return ch == ' ' || (ch >= 'a' && ch <= 'z') } isMixed := func(ch rune) bool { _, ok := mixedMap[ch] return ok } isPunctuation := func(ch rune) bool { _, ok := punctMap[ch] return ok } idx := 0 var tmp []int for idx < len(text) { ch := text[idx] switch submode { case subUpper: if isAlphaUpper(ch) { if ch == ' ' { tmp = append(tmp, 26) //space } else { tmp = append(tmp, int(ch-'A')) } } else { if isAlphaLower(ch) { submode = subLower tmp = append(tmp, 27) // lower latch continue } else if isMixed(ch) { submode = subMixed tmp = append(tmp, 28) // mixed latch continue } else { tmp = append(tmp, 29) // punctuation switch tmp = append(tmp, punctMap[ch]) break } } break case subLower: if isAlphaLower(ch) { if ch == ' ' { tmp = append(tmp, 26) //space } else { tmp = append(tmp, int(ch-'a')) } } else { if isAlphaUpper(ch) { tmp = append(tmp, 27) //upper switch tmp = append(tmp, int(ch-'A')) break } else if isMixed(ch) { submode = subMixed tmp = append(tmp, 28) //mixed latch continue } else { tmp = append(tmp, 29) //punctuation switch tmp = append(tmp, punctMap[ch]) break } } break case subMixed: if isMixed(ch) { tmp = append(tmp, mixedMap[ch]) } else { if isAlphaUpper(ch) { submode = subUpper tmp = append(tmp, 28) //upper latch continue } else if isAlphaLower(ch) { submode = subLower tmp = append(tmp, 27) //lower latch continue } else { if idx+1 < len(text) { next := text[idx+1] if isPunctuation(next) { submode = subPunct tmp = append(tmp, 25) //punctuation latch continue } } tmp = append(tmp, 29) //punctuation switch tmp = append(tmp, punctMap[ch]) } } break default: //subPunct if isPunctuation(ch) { tmp = append(tmp, punctMap[ch]) } else { submode = subUpper tmp = append(tmp, 29) //upper latch continue } } idx++ } h := 0 result := []int{} for i, val := range tmp { if i%2 != 0 { h = (h * 30) + val result = append(result, h) } else { h = val } } if len(tmp)%2 != 0 { result = append(result, (h*30)+29) } return submode, result } func determineConsecutiveBinaryCount(msg []byte) int { result := 0 for i, _ := range msg { numericCount := determineConsecutiveDigitCount([]rune(string(msg[i:]))) if numericCount >= min_numeric_count { break } textCount := determineConsecutiveTextCount([]rune(string(msg[i:]))) if textCount > 5 { break } result++ } return result } func encodeBinary(data []byte, startmode encodingMode) []int { result := []int{} count := len(data) if count == 1 && startmode == encText { result = append(result, shift_to_byte) } else if (count % 6) == 0 { result = append(result, latch_to_byte) } else { result = append(result, latch_to_byte_padded) } idx := 0 // Encode sixpacks if count >= 6 { words := make([]int, 5) for (count - idx) >= 6 { var t int64 = 0 for i := 0; i < 6; i++ { t = t << 8 t += int64(data[idx+i]) } for i := 0; i < 5; i++ { words[4-i] = int(t % 900) t = t / 900 } result = append(result, words...) idx += 6 } } //Encode rest (remaining n<5 bytes if any) for i := idx; i < count; i++ { result = append(result, int(data[i]&0xff)) } return result } func highlevelEncode(dataStr string) ([]int, error) { encodingMode := encText textSubMode := subUpper result := []int{} data := []byte(dataStr) for len(data) > 0 { numericCount := determineConsecutiveDigitCount([]rune(string(data))) if numericCount >= min_numeric_count || numericCount == len(data) { result = append(result, latch_to_numeric) encodingMode = encNumeric textSubMode = subUpper numData, err := encodeNumeric([]rune(string(data[:numericCount]))) if err != nil { return nil, err } result = append(result, numData...) data = data[numericCount:] } else { textCount := determineConsecutiveTextCount([]rune(string(data))) if textCount >= 5 || textCount == len(data) { if encodingMode != encText { result = append(result, latch_to_text) encodingMode = encText textSubMode = subUpper } var txtData []int textSubMode, txtData = encodeText([]rune(string(data[:textCount])), textSubMode) result = append(result, txtData...) data = data[textCount:] } else { binaryCount := determineConsecutiveBinaryCount(data) if binaryCount == 0 { binaryCount = 1 } bytes := data[:binaryCount] if len(bytes) != 1 || encodingMode != encText { encodingMode = encBinary textSubMode = subUpper } byteData := encodeBinary(bytes, encodingMode) result = append(result, byteData...) data = data[binaryCount:] } } } return result, nil }