aboutsummaryrefslogtreecommitdiff
path: root/source/codes/Code128.mc
diff options
context:
space:
mode:
Diffstat (limited to 'source/codes/Code128.mc')
-rw-r--r--source/codes/Code128.mc137
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
+ }
+}