summaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
authorcirakg <cirakg@cirakg.xyz>2026-10-20 02:43:08 +0200
committercirakg <cirakg@cirakg.xyz>2026-10-20 02:43:08 +0200
commit776a563b97b99b054323dc06f02b4d374805777a (patch)
tree1d37e37cb401ceeb5a519ee36c036dab118f59e2 /source
parent0edb4bb6d76939aa456cbbc8deafeed50fd6182d (diff)
feat(codes): add EAN/UPC, Code 128, Code 39, ITF and Codabar encoders
Check digits added, verified, fixed or off; canonical UPC-E zero suppression; Code 128 sets B and C with zxing's switching rules; module rows match zxing-cpp (Code 128 by length, the tie-breaking differs). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Diffstat (limited to 'source')
-rw-r--r--source/codes/Codabar.mc58
-rw-r--r--source/codes/Code128.mc137
-rw-r--r--source/codes/Code39.mc63
-rw-r--r--source/codes/Codes.mc149
-rw-r--r--source/codes/Ean.mc215
-rw-r--r--source/codes/Itf.mc39
6 files changed, 661 insertions, 0 deletions
diff --git a/source/codes/Codabar.mc b/source/codes/Codabar.mc
new file mode 100644
index 0000000..63c78bb
--- /dev/null
+++ b/source/codes/Codabar.mc
@@ -0,0 +1,58 @@
+// Author: cirakg & claude ai
+// Summary: Codabar encoder: start and stop characters A-D (added as A...A when missing), wide bars 2 modules (as zxing).
+
+import Toybox.Lang;
+
+module Codabar {
+
+ const ALPHABET = "0123456789-$:/.+ABCD"; // lint: allow-literal (Codabar character set)
+ // Seven elements per character, first element = bit 6, 1 = wide.
+ const PATTERNS = [0x003, 0x006, 0x009, 0x060, 0x012, 0x042, 0x021, 0x024, 0x030, 0x048,
+ 0x00C, 0x018, 0x045, 0x051, 0x054, 0x015, 0x01A, 0x029, 0x00B, 0x00E];
+ const FIRST_GUARD = 16; // index of 'A'
+
+ function encode(text as String) as Codes.LinearCode or Codes.CodeError {
+ var upper = text.toUpper();
+ var chars = upper.toCharArray();
+ if (chars.size() == 0) {
+ return new Codes.CodeError("Codabar needs some digits");
+ }
+ var startsGuarded = isGuard(chars[0]);
+ var endsGuarded = chars.size() > 1 && isGuard(chars[chars.size() - 1]);
+ var note = null;
+ if (startsGuarded != endsGuarded) {
+ return new Codes.CodeError("Codabar: use start and stop characters (A-D) at both ends or at neither");
+ }
+ if (!startsGuarded) {
+ upper = "A" + upper + "A";
+ chars = upper.toCharArray();
+ note = "start and stop characters A...A added";
+ }
+ var m = []b;
+ for (var i = 0; i < chars.size(); i++) {
+ var v = ALPHABET.find(chars[i].toString());
+ var inner = i > 0 && i < chars.size() - 1;
+ if (v == null || (inner && v >= FIRST_GUARD)) {
+ return new Codes.CodeError("Codabar: '" + chars[i] + "' isn't allowed there (0-9 - $ : / . + between A-D)");
+ }
+ if (i > 0) {
+ m.add(0); // narrow gap
+ }
+ var pattern = PATTERNS[v] as Number;
+ for (var k = 6; k >= 0; k--) {
+ var width = ((pattern >> k) & 1) == 1 ? 2 : 1;
+ for (var w = 0; w < width; w++) {
+ m.add((6 - k) % 2 == 0 ? 1 : 0);
+ }
+ }
+ }
+ m.add(0); // zxing also ends with a narrow gap (inside the quiet zone)
+ var code = new Codes.LinearCode(m, upper);
+ code.note = note;
+ return code;
+ }
+
+ function isGuard(c as Char) as Boolean {
+ return c >= 'A' && c <= 'D';
+ }
+}
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
+ }
+}
diff --git a/source/codes/Code39.mc b/source/codes/Code39.mc
new file mode 100644
index 0000000..e17ae8f
--- /dev/null
+++ b/source/codes/Code39.mc
@@ -0,0 +1,63 @@
+// Author: cirakg & claude ai
+// Summary: Code 39 encoder: 43 characters, optional mod-43 check character, wide bars 2 modules (as zxing).
+
+import Toybox.Lang;
+
+module Code39 {
+
+ const ALPHABET = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%"; // lint: allow-literal (Code 39 character set)
+ // Nine elements per character, bit 8 first, 1 = wide; index 43 is '*'.
+ const PATTERNS = [
+ 0x034, 0x121, 0x061, 0x160, 0x031, 0x130, 0x070, 0x025, 0x124, 0x064,
+ 0x109, 0x049, 0x148, 0x019, 0x118, 0x058, 0x00D, 0x10C, 0x04C, 0x01C,
+ 0x103, 0x043, 0x142, 0x013, 0x112, 0x052, 0x007, 0x106, 0x046, 0x016,
+ 0x181, 0x0C1, 0x1C0, 0x091, 0x190, 0x0D0, 0x085, 0x184, 0x0C4, 0x0A8,
+ 0x0A2, 0x08A, 0x02A, 0x094
+ ];
+ const STAR = 43;
+
+ // withCheck adds the mod-43 check character (the sum of the character
+ // values modulo 43).
+ function encode(text as String, withCheck as Boolean) as Codes.LinearCode or Codes.CodeError {
+ var chars = text.toCharArray();
+ if (chars.size() == 0) {
+ return new Codes.CodeError("Code 39 needs some text");
+ }
+ var values = [] as Array<Number>;
+ var sum = 0;
+ for (var i = 0; i < chars.size(); i++) {
+ var v = ALPHABET.find(chars[i].toString());
+ if (v == null) {
+ return new Codes.CodeError("Code 39: '" + chars[i] + "' (position " + (i + 1)
+ + ") isn't allowed (0-9, A-Z, space, - . $ / + %)");
+ }
+ values.add(v);
+ sum += v;
+ }
+ var readable = text;
+ if (withCheck) {
+ values.add(sum % 43);
+ readable += ALPHABET.substring(sum % 43, sum % 43 + 1);
+ }
+ var m = []b;
+ addChar(m, STAR);
+ for (var i = 0; i < values.size(); i++) {
+ m.add(0); // narrow gap between characters
+ addChar(m, values[i]);
+ }
+ m.add(0);
+ addChar(m, STAR);
+ return new Codes.LinearCode(m, readable);
+ }
+
+ function addChar(m as ByteArray, value as Number) as Void {
+ var pattern = PATTERNS[value] as Number;
+ for (var i = 8; i >= 0; i--) {
+ var width = ((pattern >> i) & 1) == 1 ? 2 : 1;
+ var bar = (8 - i) % 2 == 0;
+ for (var k = 0; k < width; k++) {
+ m.add(bar ? 1 : 0);
+ }
+ }
+ }
+}
diff --git a/source/codes/Codes.mc b/source/codes/Codes.mc
new file mode 100644
index 0000000..70c6c2d
--- /dev/null
+++ b/source/codes/Codes.mc
@@ -0,0 +1,149 @@
+// Author: cirakg & claude ai
+// Summary: Barcode symbology IDs, option bits, result types and the dispatcher to the encoders (README 12).
+//
+// A code entry stores its symbology in Entry.type and its options in
+// Entry.options (bits below). encode() turns the payload text into either a
+// LinearCode (one row of modules) or, from M6b, a matrix; validation errors
+// come back as a CodeError with a message for the user, never an exception.
+
+import Toybox.Lang;
+
+module Codes {
+
+ // Symbology IDs (Entry.type of code entries). Stored: never renumber.
+ const QR = 0;
+ const CODE128 = 1;
+ const CODE39 = 2;
+ const EAN13 = 3;
+ const EAN8 = 4;
+ const UPCA = 5;
+ const UPCE = 6;
+ const ITF = 7;
+ const CODABAR = 8;
+ const DATAMATRIX = 9;
+ const PDF417 = 10;
+ const AZTEC = 11;
+
+ const NAMES = ["QR", "Code 128", "Code 39", "EAN-13", "EAN-8", "UPC-A", "UPC-E", "ITF", "Codabar", "Data Matrix",
+ "PDF417", "Aztec"];
+
+ // Entry.options bits.
+ const OPT_EC_MASK = 0x3; // error correction: 0 low, 1 medium, 2 quartile, 3 high
+ const OPT_CHECK_SHIFT = 2; // check digit: 0 add or verify, 1 fix if wrong, 2 off
+ const OPT_CHECK_MASK = 0xC;
+ const OPT_SHOW_TEXT = 0x10; // human-readable text under linear codes
+ const DEFAULT_OPTIONS = 0x11; // medium, add or verify, text on
+
+ const CHECK_ADD_VERIFY = 0;
+ const CHECK_FIX = 1;
+ const CHECK_OFF = 2;
+
+ // A row of modules: modules[i] is 1 for a bar, 0 for a space. The quiet
+ // zone is not included (the renderer adds it).
+ class LinearCode {
+ var modules as ByteArray;
+ // The human-readable text (digits as encoded, check digit included).
+ var text as String;
+ // A remark for the user (check digit added or fixed), or null.
+ var note as String? = null;
+
+ function initialize(rowModules as ByteArray, readable as String) {
+ modules = rowModules;
+ text = readable;
+ }
+ }
+
+ // Why a payload can't be encoded, in words for the user.
+ class CodeError {
+ var message as String;
+
+ function initialize(msg as String) {
+ message = msg;
+ }
+ }
+
+ function checkMode(options as Number) as Number {
+ return (options & OPT_CHECK_MASK) >> OPT_CHECK_SHIFT;
+ }
+
+ function name(symbology as Number) as String {
+ return symbology >= 0 && symbology < NAMES.size() ? NAMES[symbology] as String : "Code";
+ }
+
+ function isLinear(symbology as Number) as Boolean {
+ return symbology >= CODE128 && symbology <= CODABAR;
+ }
+
+ // Encode a payload; unsupported symbologies give a CodeError until
+ // their milestone (README 16, M6b-M6d).
+ function encode(symbology as Number, payload as String, options as Number) as LinearCode or CodeError {
+ var check = checkMode(options);
+ if (symbology == EAN13) {
+ return Ean.ean13(payload, check, "EAN-13", false);
+ }
+ if (symbology == UPCA) {
+ return Ean.ean13(payload, check, "UPC-A", true);
+ }
+ if (symbology == EAN8) {
+ return Ean.ean8(payload, check);
+ }
+ if (symbology == UPCE) {
+ return Ean.upce(payload, check);
+ }
+ if (symbology == CODE128) {
+ return Code128.encode(payload);
+ }
+ if (symbology == CODE39) {
+ return Code39.encode(payload, check != CHECK_OFF);
+ }
+ if (symbology == ITF) {
+ return Itf.encode(payload, check != CHECK_OFF);
+ }
+ if (symbology == CODABAR) {
+ return Codabar.encode(payload);
+ }
+ return new CodeError(name(symbology) + " is not supported yet");
+ }
+
+ // Append bar/space runs to modules: widths alternate bar, space, ...,
+ // starting with a bar when startWithBar.
+ function addRuns(modules as ByteArray, widths as Array<Number>, startWithBar as Boolean) as Void {
+ var bar = startWithBar;
+ for (var i = 0; i < widths.size(); i++) {
+ var w = widths[i];
+ for (var k = 0; k < w; k++) {
+ modules.add(bar ? 1 : 0);
+ }
+ bar = !bar;
+ }
+ }
+
+ // Append a bit pattern of `count` modules, most significant bit first.
+ function addBits(modules as ByteArray, bits as Number, count as Number) as Void {
+ for (var i = count - 1; i >= 0; i--) {
+ modules.add((bits >> i) & 1);
+ }
+ }
+
+ // The digit values of text, or null when it holds anything else.
+ function digits(text as String) as Array<Number>? {
+ var chars = text.toCharArray();
+ var out = [] as Array<Number>;
+ for (var i = 0; i < chars.size(); i++) {
+ var c = chars[i];
+ if (c < '0' || c > '9') {
+ return null;
+ }
+ out.add(c.toNumber() - 48);
+ }
+ return out;
+ }
+
+ function digitsText(values as Array<Number>) as String {
+ var s = "";
+ for (var i = 0; i < values.size(); i++) {
+ s += values[i].toString();
+ }
+ return s;
+ }
+}
diff --git a/source/codes/Ean.mc b/source/codes/Ean.mc
new file mode 100644
index 0000000..1b7554f
--- /dev/null
+++ b/source/codes/Ean.mc
@@ -0,0 +1,215 @@
+// Author: cirakg & claude ai
+// Summary: EAN-13, UPC-A, EAN-8 and UPC-E encoders with check digit add, verify, fix or off (README 12.1).
+//
+// GS1 layouts: guard 101, left digits (L or G codes), centre guard 01010,
+// right digits (R codes), guard 101. EAN-13 takes the parity of its left
+// half from the first digit; UPC-A is EAN-13 with a leading 0; UPC-E is a
+// zero-suppressed UPC-A whose check digit (computed from the expanded
+// form) and number system choose the parity. Verified module by module
+// against zxing-cpp (test/data/CodeVectors.mc).
+
+import Toybox.Lang;
+
+module Ean {
+
+ // L (odd parity) codes; R = L inverted, G = R reversed.
+ const L_CODES = [0x0D, 0x19, 0x13, 0x3D, 0x23, 0x31, 0x2F, 0x3B, 0x37, 0x0B];
+ // EAN-13 left-half parity by first digit (bit 5 = second digit, 1 = G).
+ const EAN13_PARITY = [0x00, 0x0B, 0x0D, 0x0E, 0x13, 0x19, 0x1C, 0x15, 0x16, 0x1A];
+ // UPC-E parity for number system 0, by check digit (1 = even = G);
+ // number system 1 uses the complement.
+ const UPCE_PARITY = [0x38, 0x34, 0x32, 0x31, 0x2C, 0x26, 0x23, 0x2A, 0x29, 0x25];
+
+ function lCode(d as Number) as Number {
+ return L_CODES[d] as Number;
+ }
+
+ function rCode(d as Number) as Number {
+ return (L_CODES[d] as Number) ^ 0x7F;
+ }
+
+ function gCode(d as Number) as Number {
+ var r = rCode(d);
+ var g = 0;
+ for (var i = 0; i < 7; i++) {
+ g = (g << 1) | ((r >> i) & 1);
+ }
+ return g;
+ }
+
+ // GS1 mod-10 check digit of the data digits.
+ function checkDigit(data as Array<Number>) as Number {
+ var sum = 0;
+ var n = data.size();
+ for (var i = 0; i < n; i++) {
+ // Weight 3 for the rightmost data digit, then alternating.
+ sum += data[n - 1 - i] * (i % 2 == 0 ? 3 : 1);
+ }
+ return (10 - sum % 10) % 10;
+ }
+
+ // The data digits plus a valid (or, with check off, the given) check
+ // digit, or a CodeError. note receives what was changed.
+ function withCheck(text as String, dataLength as Number, mode as Number, label as String, note as Array<String>)
+ as Array<Number> or Codes.CodeError {
+ var values = Codes.digits(text);
+ if (values == null || values.size() == 0) {
+ return new Codes.CodeError(label + " takes digits only");
+ }
+ if (values.size() == dataLength) {
+ if (mode == Codes.CHECK_OFF) {
+ return new Codes.CodeError(label + " with the check digit off needs all " + (dataLength + 1) + " digits");
+ }
+ var c = checkDigit(values);
+ values.add(c);
+ note.add("check digit " + c + " added");
+ return values;
+ }
+ if (values.size() != dataLength + 1) {
+ return new Codes.CodeError(label + " needs " + dataLength + " or " + (dataLength + 1) + " digits, got " + values.size());
+ }
+ if (mode == Codes.CHECK_OFF) {
+ return values;
+ }
+ var expected = checkDigit(values.slice(0, dataLength) as Array<Number>);
+ var got = values[dataLength];
+ if (expected != got) {
+ if (mode == Codes.CHECK_FIX) {
+ values[dataLength] = expected;
+ note.add("check digit fixed: " + expected + " (was " + got + ")");
+ return values;
+ }
+ return new Codes.CodeError(label + ": check digit should be " + expected + ", got " + got
+ + ". Choose 'Fix if wrong' to correct it");
+ }
+ return values;
+ }
+
+ // EAN-13 (12 or 13 digits), or UPC-A (11 or 12 digits) when upcA.
+ function ean13(text as String, mode as Number, label as String, upcA as Boolean) as Codes.LinearCode or Codes.CodeError {
+ var note = [] as Array<String>;
+ var result = withCheck(upcA ? "0" + text : text, 12, mode, label, note);
+ if (result instanceof Codes.CodeError) {
+ if (upcA && Codes.digits(text) != null) {
+ // Lengths in the message refer to UPC-A, not the internal EAN-13.
+ var n = text.length();
+ if (n != 11 && n != 12) {
+ return new Codes.CodeError("UPC-A needs 11 or 12 digits, got " + n);
+ }
+ }
+ return result;
+ }
+ var d = result;
+ var m = []b;
+ Codes.addBits(m, 0x5, 3);
+ var parity = EAN13_PARITY[d[0]] as Number;
+ for (var i = 1; i <= 6; i++) {
+ var useG = (parity >> (6 - i)) & 1;
+ Codes.addBits(m, useG == 1 ? gCode(d[i]) : lCode(d[i]), 7);
+ }
+ Codes.addBits(m, 0x0A, 5);
+ for (var i = 7; i <= 12; i++) {
+ Codes.addBits(m, rCode(d[i]), 7);
+ }
+ Codes.addBits(m, 0x5, 3);
+ var digitsText = Codes.digitsText(d);
+ var code = new Codes.LinearCode(m, upcA ? digitsText.substring(1, 13) as String : digitsText);
+ code.note = note.size() > 0 ? note[0] : null;
+ return code;
+ }
+
+ // EAN-8: 7 or 8 digits.
+ function ean8(text as String, mode as Number) as Codes.LinearCode or Codes.CodeError {
+ var note = [] as Array<String>;
+ var result = withCheck(text, 7, mode, "EAN-8", note);
+ if (result instanceof Codes.CodeError) {
+ return result;
+ }
+ var d = result;
+ var m = []b;
+ Codes.addBits(m, 0x5, 3);
+ for (var i = 0; i < 4; i++) {
+ Codes.addBits(m, lCode(d[i]), 7);
+ }
+ Codes.addBits(m, 0x0A, 5);
+ for (var i = 4; i < 8; i++) {
+ Codes.addBits(m, rCode(d[i]), 7);
+ }
+ Codes.addBits(m, 0x5, 3);
+ var code = new Codes.LinearCode(m, Codes.digitsText(d));
+ code.note = note.size() > 0 ? note[0] : null;
+ return code;
+ }
+
+ // UPC-E: 6 digits (number system 0 assumed), 7 (number system and six
+ // digits) or 8 (with the check digit).
+ function upce(text as String, mode as Number) as Codes.LinearCode or Codes.CodeError {
+ var values = Codes.digits(text);
+ if (values == null) {
+ return new Codes.CodeError("UPC-E takes digits only");
+ }
+ if (values.size() == 6) {
+ values = [0].addAll(values) as Array<Number>;
+ }
+ if (values.size() != 7 && values.size() != 8) {
+ return new Codes.CodeError("UPC-E needs 6, 7 or 8 digits, got " + values.size());
+ }
+ var system = values[0];
+ if (system != 0 && system != 1) {
+ return new Codes.CodeError("UPC-E: the number system must be 0 or 1, got " + system);
+ }
+ // Only the canonical zero suppression is valid: a number that could
+ // be written with a smaller last digit must be (GS1, as zxing checks).
+ var last = values[6];
+ if ((last == 3 && values[3] <= 2) || (last == 4 && values[4] == 0) || (last >= 5 && values[5] == 0)) {
+ return new Codes.CodeError("UPC-E: " + Codes.digitsText(values.slice(1, 7) as Array<Number>)
+ + " is not a valid zero suppression (check the digits, or use UPC-A)");
+ }
+ // The check digit comes from the expanded UPC-A form.
+ var expandedText = expand(values.slice(0, 7) as Array<Number>);
+ var note = [] as Array<String>;
+ var given = values.size() == 8 ? expandedText + values[7] : expandedText;
+ var checked = withCheck(given, 11, mode, "UPC-E", note);
+ if (checked instanceof Codes.CodeError) {
+ return checked;
+ }
+ var check = checked[11];
+ var parity = UPCE_PARITY[check] as Number;
+ if (system == 1) {
+ parity = parity ^ 0x3F;
+ }
+ var m = []b;
+ Codes.addBits(m, 0x5, 3);
+ for (var i = 1; i <= 6; i++) {
+ var even = (parity >> (6 - i)) & 1;
+ Codes.addBits(m, even == 1 ? gCode(values[i]) : lCode(values[i]), 7);
+ }
+ Codes.addBits(m, 0x15, 6);
+ var digits = values.slice(0, 7) as Array<Number>;
+ digits.add(check);
+ var code = new Codes.LinearCode(m, Codes.digitsText(digits));
+ code.note = note.size() > 0 ? note[0] : null;
+ return code;
+ }
+
+ // The 11-digit UPC-A form of number system + six UPC-E digits.
+ function expand(v as Array<Number>) as String {
+ var ns = v[0].toString();
+ var d1 = v[1];
+ var d2 = v[2];
+ var d3 = v[3];
+ var d4 = v[4];
+ var d5 = v[5];
+ var d6 = v[6];
+ if (d6 <= 2) {
+ return ns + d1 + d2 + d6 + "0000" + d3 + d4 + d5;
+ }
+ if (d6 == 3) {
+ return ns + d1 + d2 + d3 + "00000" + d4 + d5;
+ }
+ if (d6 == 4) {
+ return ns + d1 + d2 + d3 + d4 + "00000" + d5;
+ }
+ return ns + d1 + d2 + d3 + d4 + d5 + "0000" + d6;
+ }
+}
diff --git a/source/codes/Itf.mc b/source/codes/Itf.mc
new file mode 100644
index 0000000..0d74c3b
--- /dev/null
+++ b/source/codes/Itf.mc
@@ -0,0 +1,39 @@
+// Author: cirakg & claude ai
+// Summary: Interleaved 2 of 5 (ITF) encoder: digit pairs, optional mod-10 check digit, wide bars 3 modules (as zxing).
+
+import Toybox.Lang;
+
+module Itf {
+
+ // Five elements per digit, first element = bit 4, 1 = wide.
+ const PATTERNS = [0x06, 0x11, 0x09, 0x18, 0x05, 0x14, 0x0C, 0x03, 0x12, 0x0A];
+
+ function encode(text as String, withCheck as Boolean) as Codes.LinearCode or Codes.CodeError {
+ var values = Codes.digits(text);
+ if (values == null || values.size() == 0) {
+ return new Codes.CodeError("ITF takes digits only");
+ }
+ if (withCheck) {
+ values.add(Ean.checkDigit(values));
+ }
+ if (values.size() % 2 != 0) {
+ return new Codes.CodeError(withCheck
+ ? "ITF with a check digit needs an odd number of digits: add a leading 0"
+ : "ITF needs an even number of digits: add a leading 0");
+ }
+ var m = []b;
+ Codes.addRuns(m, [1, 1, 1, 1], true);
+ for (var i = 0; i < values.size(); i += 2) {
+ var bars = PATTERNS[values[i]] as Number;
+ var spaces = PATTERNS[values[i + 1]] as Number;
+ var widths = [] as Array<Number>;
+ for (var k = 4; k >= 0; k--) {
+ widths.add(((bars >> k) & 1) == 1 ? 3 : 1);
+ widths.add(((spaces >> k) & 1) == 1 ? 3 : 1);
+ }
+ Codes.addRuns(m, widths, true);
+ }
+ Codes.addRuns(m, [3, 1, 1], true);
+ return new Codes.LinearCode(m, Codes.digitsText(values));
+ }
+}