From 776a563b97b99b054323dc06f02b4d374805777a Mon Sep 17 00:00:00 2001 From: cirakg Date: Tue, 20 Oct 2026 02:43:08 +0200 Subject: 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 --- source/codes/Codabar.mc | 58 +++++++++++++ source/codes/Code128.mc | 137 ++++++++++++++++++++++++++++++ source/codes/Code39.mc | 63 ++++++++++++++ source/codes/Codes.mc | 149 +++++++++++++++++++++++++++++++++ source/codes/Ean.mc | 215 ++++++++++++++++++++++++++++++++++++++++++++++++ source/codes/Itf.mc | 39 +++++++++ 6 files changed, 661 insertions(+) create mode 100644 source/codes/Codabar.mc create mode 100644 source/codes/Code128.mc create mode 100644 source/codes/Code39.mc create mode 100644 source/codes/Codes.mc create mode 100644 source/codes/Ean.mc create mode 100644 source/codes/Itf.mc (limited to 'source') 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; + 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 { + var chars = pattern.toCharArray(); + var out = [] as Array; + for (var i = 0; i < chars.size(); i++) { + out.add(chars[i].toNumber() - 48); + } + return out; + } + + function cType(chars as Array, 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, 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; + 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, 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? { + var chars = text.toCharArray(); + var out = [] as Array; + 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) 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) 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; + } +} 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; + 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)); + } +} -- cgit v1.2.3