From cc2fa8917b65eeda8d222dcbd69e9342884bb545 Mon Sep 17 00:00:00 2001 From: cirakg Date: Tue, 20 Oct 2026 11:24:58 +0200 Subject: feat(codes): add Data Matrix (ECC 200, 10x10-144x144) and PDF417 encoders as jobs Data Matrix: ASCII with digit pairs, Base256 or Upper Shift for UTF-8, interleaved Reed-Solomon in steps, utah placement; matches ppf.datamatrix. PDF417: pdf417gen's compaction, automatic or fixed EC level, columns fitted to the screen, pattern tables as generated JSON resources; matches pdf417gen. 2D matrices gain a row height and 8-at-a-time packing. Co-Authored-By: Claude Opus 5.5 --- source/codes/Codes.mc | 118 ++++++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 101 insertions(+), 17 deletions(-) (limited to 'source/codes/Codes.mc') diff --git a/source/codes/Codes.mc b/source/codes/Codes.mc index 2d307ef..bb1a4ed 100644 --- a/source/codes/Codes.mc +++ b/source/codes/Codes.mc @@ -53,12 +53,15 @@ module Codes { } } - // A 2D code ready to draw: width x height modules, row-major, 1 = dark, - // plus the quiet zone its symbology needs (QR 4, Data Matrix 1, ...). + // 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) { @@ -68,35 +71,116 @@ module Codes { modules = bits; } - // Compact form for the encrypted cache: width, height, quiet (one byte - // each), then the modules packed 8 per byte. + // 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 out = [width, height, quiet]b; - var packed = new [(modules.size() + 7) / 8]b; - for (var i = 0; i < modules.size(); i++) { - if (modules[i] == 1) { - packed[i / 8] = packed[i / 8] | (0x80 >> (i % 8)); - } + 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)); } - out.addAll(packed); return out; } static function fromBytes(data as ByteArray) as Matrix? { - if (data.size() < 3) { + if (data.size() < 4) { return null; } var w = data[0]; var h = data[1]; - if (w == 0 || h == 0 || data.size() != 3 + (w * h + 7) / 8) { + var n = w * h; + if (w == 0 || h == 0 || data[3] == 0 || data.size() != 4 + (n + 7) / 8) { return null; } - var bits = new [w * h]b; - for (var i = 0; i < w * h; i++) { - bits[i] = (data[3 + i / 8] >> (7 - i % 8)) & 1; + 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; } - return new Matrix(w, h, data[2], bits); + 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 { -- cgit v1.2.3