// 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; } } // A 2D code ready to draw: width x height stored rows of modules, // row-major, 1 = dark; each stored row is rowHeight modules high (PDF417 // rows are 3); plus the quiet zone its symbology needs (QR 4, Data // Matrix 1, PDF417 2). class Matrix { var width as Number; var height as Number; var quiet as Number; var rowHeight as Number = 1; var modules as ByteArray; function initialize(w as Number, h as Number, quietModules as Number, bits as ByteArray) { width = w; height = h; quiet = quietModules; modules = bits; } // Height in modules as drawn. function drawnHeight() as Number { return height * rowHeight; } // Compact form for the encrypted cache: width, height, quiet zone, // row height (one byte each), then the modules packed 8 per byte // (eight per loop round: this runs in one event, under the watchdog). function toBytes() as ByteArray { var m = modules; var n = m.size(); var full = n / 8; var out = new [4 + (n + 7) / 8]b; out[0] = width; out[1] = height; out[2] = quiet; out[3] = rowHeight; for (var i = 0; i < full; i++) { var k = i * 8; out[4 + i] = (m[k] << 7) | (m[k + 1] << 6) | (m[k + 2] << 5) | (m[k + 3] << 4) | (m[k + 4] << 3) | (m[k + 5] << 2) | (m[k + 6] << 1) | m[k + 7]; } for (var k = full * 8; k < n; k++) { out[4 + full] = out[4 + full] | (m[k] << (7 - k % 8)); } return out; } static function fromBytes(data as ByteArray) as Matrix? { if (data.size() < 4) { return null; } var w = data[0]; var h = data[1]; var n = w * h; if (w == 0 || h == 0 || data[3] == 0 || data.size() != 4 + (n + 7) / 8) { return null; } var bits = new [(n + 7) / 8 * 8]b; for (var i = 0; i < (n + 7) / 8; i++) { var v = data[4 + i]; var k = i * 8; bits[k] = (v >> 7) & 1; bits[k + 1] = (v >> 6) & 1; bits[k + 2] = (v >> 5) & 1; bits[k + 3] = (v >> 4) & 1; bits[k + 4] = (v >> 3) & 1; bits[k + 5] = (v >> 2) & 1; bits[k + 6] = (v >> 1) & 1; bits[k + 7] = v & 1; } var matrix = new Matrix(w, h, data[2], bits.size() == n ? bits : bits.slice(0, n)); matrix.rowHeight = data[3]; return matrix; } } // A 2D encoder run as a resumable job (watchdog): when step() returns // true, either error is set or matrix() is the code. class MatrixJob extends Jobs.Job { var error as String? = null; function initialize() { Job.initialize(); } // Progress 0-100 for the UI. function percent() as Number { return 0; } function matrix() as Matrix? { return null; } } // The job that encodes a 2D symbology, or null for linear ones. PDF417 // chooses its columns for the screen, so it gets the screen's size. function matrixJob(symbology as Number, payload as String, options as Number, screenW as Number, screenH as Number, round as Boolean) as MatrixJob? { if (symbology == QR) { return new Qr.QrJob(payload, ecLevel(options), 0, -1); } if (symbology == DATAMATRIX) { return new DataMatrix.DataMatrixJob(payload); } if (symbology == PDF417) { return new Pdf417.Pdf417Job(payload, ecLevel(options), screenW, screenH, round); } return null; } // Smallest module size allowed (README 12.2): 1 px for linear codes and // PDF417 (a stacked linear code), 2 px for the square 2D codes. function minModulePx(symbology as Number) as Number { return isLinear(symbology) || symbology == PDF417 ? 1 : 2; } // True when a code of w x h modules (quiet zone included) at px pixels // per module fits the screen: inside the circle (less a 4 px margin) on // round screens; elsewhere the full width and the height less room for // the name and ID lines. function fits(w as Number, h as Number, px as Number, screenW as Number, screenH as Number, round as Boolean) as Boolean { var width = w * px; var height = h * px; if (round) { var r = screenW / 2 - 4; return width.toLong() * width + height.toLong() * height <= 4l * r * r; } return width <= screenW - 4 && height <= screenH * 76 / 100; } function isMatrix(symbology as Number) as Boolean { return symbology == QR || symbology == DATAMATRIX || symbology == PDF417 || symbology == AZTEC; } function ecLevel(options as Number) as Number { return options & OPT_EC_MASK; } // 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; } }