// 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) 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) as Array 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); 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; 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; 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; } 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) + " 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); var note = [] as Array; 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; 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) 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; } }