diff options
Diffstat (limited to 'source/codes/Code128.mc')
| -rw-r--r-- | source/codes/Code128.mc | 137 |
1 files changed, 137 insertions, 0 deletions
diff --git a/source/codes/Code128.mc b/source/codes/Code128.mc new file mode 100644 index 0000000..0f11489 --- /dev/null +++ b/source/codes/Code128.mc @@ -0,0 +1,137 @@ +// Author: cirakg & claude ai +// Summary: Code 128 encoder (sets B and C) with zxing's look-ahead rules for when to switch to digit pairs. +// +// Accepts printable ASCII (32-126). Set C packs two digits per symbol; the +// encoder starts in C for leading digit pairs and switches to C for runs +// of at least four digits, choosing the switch point so an odd digit is +// encoded in B first (the rules of zxing's Code128Writer). zxing-cpp 3, +// the test reference, writes through zint, which breaks ties between equally +// short encodings differently; the tests require the same length (it was +// equal for 5,000 random strings) and decoding. GS1-128 (FNC1) is deferred. + +import Toybox.Lang; + +module Code128 { + + // Bar/space widths of symbol values 0-105 (6 elements, starting with a + // bar); STOP (106) has 7 elements. + const PATTERNS = [ + "212222", "222122", "222221", "121223", "121322", "131222", "122213", "122312", "132212", "221213", + "221312", "231212", "112232", "122132", "122231", "113222", "123122", "123221", "223211", "221132", + "221231", "213212", "223112", "312131", "311222", "321122", "321221", "312212", "322112", "322211", + "212123", "212321", "232121", "111323", "131123", "131321", "112313", "132113", "132311", "211313", + "231113", "231311", "112133", "112331", "132131", "113123", "113321", "133121", "313121", "211331", + "231131", "213113", "213311", "213131", "311123", "311321", "331121", "312113", "312311", "332111", + "314111", "221411", "431111", "111224", "111422", "121124", "121421", "141122", "141221", "112214", + "112412", "122114", "122411", "142112", "142211", "241211", "221114", "413111", "241112", "134111", + "111242", "121142", "121241", "114212", "124112", "124211", "411212", "421112", "421211", "212141", + "214121", "412121", "111143", "111341", "131141", "114113", "114311", "411113", "411311", "113141", + "114131", "311141", "411131", "211412", "211214", "211232", "2331112" + ]; // lint: allow-literal (published Code 128 symbol widths) + + const CODE_C = 99; + const CODE_B = 100; + const START_B = 104; + const START_C = 105; + const STOP = 106; + + // Look-ahead classes. + const UNCODABLE = 0; // not a digit, or the end + const ONE_DIGIT = 1; + const TWO_DIGITS = 2; + + function encode(text as String) as Codes.LinearCode or Codes.CodeError { + var chars = text.toCharArray(); + if (chars.size() == 0) { + return new Codes.CodeError("Code 128 needs some text"); + } + for (var i = 0; i < chars.size(); i++) { + var c = chars[i].toNumber(); + if (c < 32 || c > 126) { + return new Codes.CodeError("Code 128: '" + chars[i] + "' (position " + (i + 1) + ") is not a printable ASCII character"); + } + } + var values = [] as Array<Number>; + var codeSet = 0; + var position = 0; + while (position < chars.size()) { + var next = choose(chars, position, codeSet); + var value = 0; + if (next == codeSet) { + if (codeSet == CODE_C) { + value = (chars[position].toNumber() - 48) * 10 + chars[position + 1].toNumber() - 48; + position += 2; + } else { + value = chars[position].toNumber() - 32; + position += 1; + } + } else { + value = codeSet == 0 ? (next == CODE_C ? START_C : START_B) : next; + codeSet = next; + } + values.add(value); + } + // Checksum: start value plus each following value times its position. + var sum = values[0]; + for (var i = 1; i < values.size(); i++) { + sum += values[i] * i; + } + values.add(sum % 103); + values.add(STOP); + var m = []b; + for (var i = 0; i < values.size(); i++) { + Codes.addRuns(m, widths(PATTERNS[values[i]] as String), true); + } + return new Codes.LinearCode(m, text); + } + + function widths(pattern as String) as Array<Number> { + var chars = pattern.toCharArray(); + var out = [] as Array<Number>; + for (var i = 0; i < chars.size(); i++) { + out.add(chars[i].toNumber() - 48); + } + return out; + } + + function cType(chars as Array<Char>, start as Number) as Number { + if (start >= chars.size() || !isDigit(chars[start])) { + return UNCODABLE; + } + if (start + 1 >= chars.size() || !isDigit(chars[start + 1])) { + return ONE_DIGIT; + } + return TWO_DIGITS; + } + + function isDigit(c as Char) as Boolean { + return c >= '0' && c <= '9'; + } + + // The code set for the symbol at `start` (zxing's chooseCode, without + // set A and FNC1, which this encoder doesn't use). + function choose(chars as Array<Char>, start as Number, oldCode as Number) as Number { + var lookahead = cType(chars, start); + if (lookahead != TWO_DIGITS) { + return CODE_B; + } + if (oldCode == CODE_C) { + return CODE_C; + } + if (oldCode == CODE_B) { + lookahead = cType(chars, start + 2); + if (lookahead != TWO_DIGITS) { + return CODE_B; // fewer than 4 digits: not worth switching + } + var index = start + 4; + lookahead = cType(chars, index); + while (lookahead == TWO_DIGITS) { + index += 2; + lookahead = cType(chars, index); + } + // An odd number of digits: encode one in B, then switch. + return lookahead == ONE_DIGIT ? CODE_B : CODE_C; + } + return CODE_C; // the first symbol: start in C for a digit pair + } +} |
