aboutsummaryrefslogtreecommitdiff
path: root/source/codes/Ean.mc
diff options
context:
space:
mode:
Diffstat (limited to 'source/codes/Ean.mc')
-rw-r--r--source/codes/Ean.mc215
1 files changed, 215 insertions, 0 deletions
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;
+ }
+}