aboutsummaryrefslogtreecommitdiff
path: root/source/codes/Ean.mc
blob: 1b7554f5c4a911c7c42ddaf933e8a15156843baa (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
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;
    }
}