diff options
Diffstat (limited to 'source/codes/Ean.mc')
| -rw-r--r-- | source/codes/Ean.mc | 215 |
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; + } +} |
