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  }