summaryrefslogtreecommitdiff
path: root/source/codes/Codes.mc
diff options
context:
space:
mode:
authorcirakg <cirakg@cirakg.xyz>2026-10-20 11:24:58 +0200
committercirakg <cirakg@cirakg.xyz>2026-10-20 11:24:58 +0200
commitcc2fa8917b65eeda8d222dcbd69e9342884bb545 (patch)
tree910b9852ac4c29d5cb43cd475dc2426b0de4dddb /source/codes/Codes.mc
parent0a39c695e1fc78a7c8b8af0b301040f422303953 (diff)
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 <noreply@anthropic.com>
Diffstat (limited to 'source/codes/Codes.mc')
-rw-r--r--source/codes/Codes.mc118
1 files changed, 101 insertions, 17 deletions
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 {