summaryrefslogtreecommitdiff
path: root/source
diff options
context:
space:
mode:
Diffstat (limited to 'source')
-rw-r--r--source/codes/Codes.mc118
-rw-r--r--source/codes/DataMatrix.mc398
-rw-r--r--source/codes/Pdf417.mc428
-rw-r--r--source/codes/Qr.mc11
4 files changed, 935 insertions, 20 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 {
diff --git a/source/codes/DataMatrix.mc b/source/codes/DataMatrix.mc
new file mode 100644
index 0000000..45d5ff0
--- /dev/null
+++ b/source/codes/DataMatrix.mc
@@ -0,0 +1,398 @@
+// Author: cirakg & claude ai
+// Summary: Data Matrix ECC 200 encoder (ISO/IEC 16022): square sizes 10x10 to 144x144, ASCII and Base256, as a resumable job.
+//
+// Steps:
+// 1. Encode the UTF-8 bytes: ASCII (digit pairs as one codeword) when
+// every byte is below 128, otherwise the cheaper of ASCII with Upper
+// Shift per byte and one Base256 field (randomised, ISO 16022 annex B).
+// C40, Text, X12 and EDIFACT are deferred (README 12.1).
+// 2. Pick the smallest square size, pad (129, then randomised pads).
+// 3. Reed-Solomon over GF(256) (x^8+x^5+x^3+x^2+1, roots a^1..a^n),
+// per interleaved block, in chunks.
+// 4. Place the codewords with the standard "utah" placement, a few
+// diagonal sweeps per step, then add the finder and clock patterns of
+// every data region.
+// Verified against ppf.datamatrix (ASCII mode) and by decoding with zxing-cpp.
+
+import Toybox.Lang;
+
+module DataMatrix {
+
+ // Square sizes: [symbol size, data region size, regions per side,
+ // data codewords, EC codewords, interleaved blocks] (ISO 16022 table 7).
+ const SIZES = [
+ [10, 8, 1, 3, 5, 1], [12, 10, 1, 5, 7, 1], [14, 12, 1, 8, 10, 1], [16, 14, 1, 12, 12, 1],
+ [18, 16, 1, 18, 14, 1], [20, 18, 1, 22, 18, 1], [22, 20, 1, 30, 20, 1], [24, 22, 1, 36, 24, 1],
+ [26, 24, 1, 44, 28, 1], [32, 14, 2, 62, 36, 1], [36, 16, 2, 86, 42, 1], [40, 18, 2, 114, 48, 1],
+ [44, 20, 2, 144, 56, 1], [48, 22, 2, 174, 68, 1], [52, 24, 2, 204, 84, 2], [64, 14, 4, 280, 112, 2],
+ [72, 16, 4, 368, 144, 4], [80, 18, 4, 456, 192, 4], [88, 20, 4, 576, 224, 4], [96, 22, 4, 696, 272, 4],
+ [104, 24, 4, 816, 336, 6], [120, 18, 6, 1050, 408, 6], [132, 20, 6, 1304, 496, 8], [144, 22, 6, 1558, 620, 10]
+ ];
+ const QUIET = 1;
+ const PAD = 129;
+ const UPPER_SHIFT = 235;
+ const BASE256_LATCH = 231;
+ // Work per step, sized for the 120,000-bytecode watchdog.
+ const RS_SYMBOLS_PER_STEP = 40;
+ const SWEEPS_PER_STEP = 6;
+
+ var field as ReedSolomon.Field? = null;
+
+ function theField() as ReedSolomon.Field {
+ var f = field;
+ if (f == null) {
+ f = new ReedSolomon.Field(0x12D, 256, 1);
+ field = f;
+ }
+ return f;
+ }
+
+ // Codewords for data: ASCII, or Base256 when cheaper (non-ASCII text).
+ function codewords(data as ByteArray) as Array<Number> {
+ var ascii = [] as Array<Number>;
+ var highBytes = 0;
+ var i = 0;
+ while (i < data.size()) {
+ var b = data[i];
+ if (b >= 0x30 && b <= 0x39 && i + 1 < data.size() && data[i + 1] >= 0x30 && data[i + 1] <= 0x39) {
+ ascii.add(130 + (b - 0x30) * 10 + data[i + 1] - 0x30);
+ i += 2;
+ continue;
+ }
+ if (b >= 128) {
+ ascii.add(UPPER_SHIFT);
+ ascii.add(b - 128 + 1);
+ highBytes++;
+ } else {
+ ascii.add(b + 1);
+ }
+ i++;
+ }
+ var n = data.size();
+ var base256Cost = 1 + (n <= 249 ? 1 : 2) + n;
+ if (highBytes == 0 || ascii.size() <= base256Cost) {
+ return ascii;
+ }
+ var out = [BASE256_LATCH] as Array<Number>;
+ if (n <= 249) {
+ out.add(randomise255(n, 2));
+ } else {
+ out.add(randomise255(n / 250 + 249, 2));
+ out.add(randomise255(n % 250, 3));
+ }
+ for (var k = 0; k < n; k++) {
+ out.add(randomise255(data[k], out.size() + 1));
+ }
+ return out;
+ }
+
+ // The 255-state randomising of Base256 values at 1-based position pos.
+ function randomise255(value as Number, pos as Number) as Number {
+ var v = value + ((149 * pos) % 255) + 1;
+ return v <= 255 ? v : v - 256;
+ }
+
+ // The 253-state randomised pad at 1-based position pos.
+ function pad253(pos as Number) as Number {
+ var v = PAD + ((149 * pos) % 253) + 1;
+ return v <= 254 ? v : v - 254;
+ }
+
+ // Encode synchronously (tests); the app uses DataMatrixJob.
+ function encode(text as String) as Codes.Matrix or Codes.CodeError {
+ var job = new DataMatrixJob(text);
+ Jobs.runToCompletion(job, 1);
+ var err = job.error;
+ return err != null ? new Codes.CodeError(err) : job.matrix() as Codes.Matrix;
+ }
+
+ class DataMatrixJob extends Codes.MatrixJob {
+
+ private var _data as ByteArray;
+ private var _phase as Number = 0;
+ private var _size as Array<Number> = [] as Array<Number>;
+ private var _cw as Array<Number> = [] as Array<Number>;
+ private var _blocks as Array<Array<Number>> = [] as Array<Array<Number>>;
+ private var _rem as Array<Number> = [] as Array<Number>;
+ private var _block as Number = 0;
+ private var _at as Number = 0;
+ private var _ecTotal as Array<Number> = [] as Array<Number>;
+ // Placement state.
+ private var _nrow as Number = 0;
+ private var _ncol as Number = 0;
+ private var _place as Array<Number> = [] as Array<Number>; // 10 * codeword + bit, 0 = empty, -1 = fixed dark
+ private var _chr as Number = 1;
+ private var _row as Number = 4;
+ private var _col as Number = 0;
+ private var _result as Codes.Matrix? = null;
+
+ function initialize(text as String) {
+ MatrixJob.initialize();
+ _data = Bytes.fromUtf8(text);
+ }
+
+ function percent() as Number {
+ return _phase * 25;
+ }
+
+ function matrix() as Codes.Matrix? {
+ return _result;
+ }
+
+ function step(budget as Number) as Boolean {
+ if (_phase == 0) {
+ prepare();
+ } else if (_phase == 1) {
+ eccStep();
+ } else if (_phase == 2) {
+ placeStep();
+ } else if (_phase == 3) {
+ build();
+ }
+ return _phase >= 4 || error != null;
+ }
+
+ // Codewords, size, padding, blocks.
+ private function prepare() as Void {
+ if (_data.size() == 0) {
+ error = "Data Matrix needs some text";
+ return;
+ }
+ var cw = codewords(_data);
+ for (var i = 0; i < SIZES.size() && _size.size() == 0; i++) {
+ var s = SIZES[i] as Array<Number>;
+ if (cw.size() <= s[3]) {
+ _size = s;
+ }
+ }
+ if (_size.size() == 0) {
+ error = "too long for Data Matrix (" + cw.size() + " codewords, at most 1558)";
+ return;
+ }
+ var capacity = _size[3];
+ if (cw.size() < capacity) {
+ cw.add(PAD);
+ while (cw.size() < capacity) {
+ cw.add(pad253(cw.size() + 1));
+ }
+ }
+ _cw = cw;
+ var blocks = _size[5];
+ for (var b = 0; b < blocks; b++) {
+ var block = [] as Array<Number>;
+ for (var k = b; k < capacity; k += blocks) {
+ block.add(cw[k]);
+ }
+ _blocks.add(block);
+ }
+ _ecTotal = new [_size[4]] as Array<Number>;
+ startBlock();
+ _phase = 1;
+ }
+
+ private function startBlock() as Void {
+ var n = _size[4] / _size[5];
+ _rem = new [n] as Array<Number>;
+ for (var k = 0; k < n; k++) {
+ _rem[k] = 0;
+ }
+ _at = 0;
+ }
+
+ // Reed-Solomon, RS_SYMBOLS_PER_STEP data symbols per step.
+ private function eccStep() as Void {
+ var f = theField();
+ var data = _blocks[_block];
+ var n = _rem.size();
+ var g = f.generator(n);
+ var end = _at + RS_SYMBOLS_PER_STEP < data.size() ? _at + RS_SYMBOLS_PER_STEP : data.size();
+ for (var i = _at; i < end; i++) {
+ var factor = data[i] ^ _rem[0];
+ for (var k = 0; k < n - 1; k++) {
+ _rem[k] = _rem[k + 1] ^ f.multiply(g[k], factor);
+ }
+ _rem[n - 1] = f.multiply(g[n - 1], factor);
+ }
+ _at = end;
+ if (_at < data.size()) {
+ return;
+ }
+ // Interleave this block's EC codewords.
+ var blocks = _size[5];
+ for (var k = 0; k < n; k++) {
+ _ecTotal[_block + k * blocks] = _rem[k];
+ }
+ _block++;
+ if (_block < blocks) {
+ startBlock();
+ return;
+ }
+ _cw.addAll(_ecTotal);
+ _blocks = [] as Array<Array<Number>>;
+ _nrow = _size[1] * _size[2];
+ _ncol = _nrow;
+ _place = new [_nrow * _ncol] as Array<Number>;
+ for (var k = 0; k < _place.size(); k++) {
+ _place[k] = 0;
+ }
+ _phase = 2;
+ }
+
+ // ISO 16022 annex F placement, SWEEPS_PER_STEP diagonal sweeps per step.
+ private function placeStep() as Void {
+ var nrow = _nrow;
+ var ncol = _ncol;
+ for (var s = 0; s < SWEEPS_PER_STEP && (_row < nrow || _col < ncol); s++) {
+ if (_row == nrow && _col == 0) {
+ corner1();
+ }
+ if (_row == nrow - 2 && _col == 0 && ncol % 4 != 0) {
+ corner2();
+ }
+ if (_row == nrow - 2 && _col == 0 && ncol % 8 == 4) {
+ corner3();
+ }
+ if (_row == nrow + 4 && _col == 2 && ncol % 8 == 0) {
+ corner4();
+ }
+ do {
+ if (_row < nrow && _col >= 0 && _place[_row * ncol + _col] == 0) {
+ utah(_row, _col);
+ }
+ _row -= 2;
+ _col += 2;
+ } while (_row >= 0 && _col < ncol);
+ _row += 1;
+ _col += 3;
+ do {
+ if (_row >= 0 && _col < ncol && _place[_row * ncol + _col] == 0) {
+ utah(_row, _col);
+ }
+ _row += 2;
+ _col -= 2;
+ } while (_row < nrow && _col >= 0);
+ _row += 3;
+ _col += 1;
+ }
+ if (_row < nrow || _col < ncol) {
+ return;
+ }
+ if (_place[nrow * ncol - 1] == 0) {
+ _place[nrow * ncol - 1] = -1;
+ _place[nrow * ncol - ncol - 2] = -1;
+ }
+ _phase = 3;
+ }
+
+ private function setModule(row as Number, col as Number, bit as Number) as Void {
+ var r = row;
+ var c = col;
+ if (r < 0) {
+ r += _nrow;
+ c += 4 - ((_nrow + 4) % 8);
+ }
+ if (c < 0) {
+ c += _ncol;
+ r += 4 - ((_ncol + 4) % 8);
+ }
+ _place[r * _ncol + c] = 10 * _chr + bit;
+ }
+
+ private function utah(row as Number, col as Number) as Void {
+ setModule(row - 2, col - 2, 1);
+ setModule(row - 2, col - 1, 2);
+ setModule(row - 1, col - 2, 3);
+ setModule(row - 1, col - 1, 4);
+ setModule(row - 1, col, 5);
+ setModule(row, col - 2, 6);
+ setModule(row, col - 1, 7);
+ setModule(row, col, 8);
+ _chr++;
+ }
+
+ private function corner1() as Void {
+ setModule(_nrow - 1, 0, 1);
+ setModule(_nrow - 1, 1, 2);
+ setModule(_nrow - 1, 2, 3);
+ setModule(0, _ncol - 2, 4);
+ setModule(0, _ncol - 1, 5);
+ setModule(1, _ncol - 1, 6);
+ setModule(2, _ncol - 1, 7);
+ setModule(3, _ncol - 1, 8);
+ _chr++;
+ }
+
+ private function corner2() as Void {
+ setModule(_nrow - 3, 0, 1);
+ setModule(_nrow - 2, 0, 2);
+ setModule(_nrow - 1, 0, 3);
+ setModule(0, _ncol - 4, 4);
+ setModule(0, _ncol - 3, 5);
+ setModule(0, _ncol - 2, 6);
+ setModule(0, _ncol - 1, 7);
+ setModule(1, _ncol - 1, 8);
+ _chr++;
+ }
+
+ private function corner3() as Void {
+ setModule(_nrow - 3, 0, 1);
+ setModule(_nrow - 2, 0, 2);
+ setModule(_nrow - 1, 0, 3);
+ setModule(0, _ncol - 2, 4);
+ setModule(0, _ncol - 1, 5);
+ setModule(1, _ncol - 1, 6);
+ setModule(2, _ncol - 1, 7);
+ setModule(3, _ncol - 1, 8);
+ _chr++;
+ }
+
+ private function corner4() as Void {
+ setModule(_nrow - 1, 0, 1);
+ setModule(_nrow - 1, _ncol - 1, 2);
+ setModule(0, _ncol - 3, 3);
+ setModule(0, _ncol - 2, 4);
+ setModule(0, _ncol - 1, 5);
+ setModule(1, _ncol - 3, 6);
+ setModule(1, _ncol - 2, 7);
+ setModule(1, _ncol - 1, 8);
+ _chr++;
+ }
+
+ // The symbol: data modules mapped into the regions, plus each
+ // region's finder (solid left and bottom) and clock (top, right).
+ private function build() as Void {
+ var size = _size[0];
+ var region = _size[1];
+ var box = region + 2;
+ var m = new [size * size]b;
+ for (var y = 0; y < size; y++) {
+ var ry = y % box;
+ for (var x = 0; x < size; x++) {
+ var rx = x % box;
+ var dark = 0;
+ if (rx == 0 || ry == box - 1) {
+ dark = 1;
+ } else if (ry == 0) {
+ dark = rx % 2 == 0 ? 1 : 0;
+ } else if (rx == box - 1) {
+ dark = ry % 2 == 1 ? 1 : 0;
+ } else {
+ var mr = (y / box) * region + ry - 1;
+ var mc = (x / box) * region + rx - 1;
+ var v = _place[mr * _ncol + mc];
+ if (v == -1) {
+ dark = 1;
+ } else if (v > 0) {
+ var cw = _cw[v / 10 - 1];
+ dark = (cw >> (8 - v % 10)) & 1;
+ }
+ }
+ m[y * size + x] = dark;
+ }
+ }
+ _place = [] as Array<Number>;
+ _result = new Codes.Matrix(size, size, QUIET, m);
+ _phase = 4;
+ }
+ }
+}
diff --git a/source/codes/Pdf417.mc b/source/codes/Pdf417.mc
new file mode 100644
index 0000000..b17b5df
--- /dev/null
+++ b/source/codes/Pdf417.mc
@@ -0,0 +1,428 @@
+// Author: cirakg & claude ai
+// Summary: PDF417 encoder (ISO/IEC 15438): text, byte and numeric compaction as pdf417gen does it, columns fitted to the screen.
+//
+// Steps:
+// 1. Compaction, mirroring pdf417gen (the test reference): the bytes are
+// split into runs of digits, text-compactable characters and other
+// bytes; digit runs under 13 next to text become text; equal neighbours
+// merge. Text uses the four submodes (switch codes, pairs of values,
+// 29 as padding), numeric packs 44 digits per base-900 group, byte
+// packs 6 bytes into 5 codewords.
+// 2. Error-correction level: the option's fixed level, or automatic
+// (ISO 15438 annex E: 2 up to 40 data codewords, then 3, 4, 5).
+// 3. Columns and module size: the largest module size, then the fewest
+// columns, for which the symbol (with a 2-module quiet zone and rows 3
+// modules high) fits the screen (Codes.fits).
+// 4. Padding (900), length descriptor, Reed-Solomon mod 929 in steps.
+// 5. Rows: start, left row indicator, data, right row indicator, stop,
+// each codeword drawn from its cluster's 17-module pattern.
+// The 2,787 patterns, EC factors and text table are JSON resources
+// generated from pdf417gen (tools/gen_pdf417_tables.py), loaded only while
+// encoding.
+
+import Toybox.Lang;
+import Toybox.Math;
+import Toybox.WatchUi;
+
+module Pdf417 {
+
+ const START = 0x1fea8;
+ const STOP = 0x3fa29;
+ const PAD = 900;
+ const TEXT_LATCH = 900;
+ const BYTE_LATCH = 901;
+ const BYTE_LATCH_6 = 924;
+ const NUMERIC_LATCH = 902;
+ const ROW_HEIGHT = 3;
+ const QUIET = 2;
+ const MAX_CODEWORDS = 928;
+
+ const KIND_TEXT = 0;
+ const KIND_BYTE = 1;
+ const KIND_NUMERIC = 2;
+
+ // Text submodes and the switch codes between them [from][to].
+ const UPPER = 0;
+ const LOWER = 1;
+ const MIXED = 2;
+ const PUNCT = 3;
+ const SWITCHES = [
+ [[], [27], [28], [28, 25]],
+ [[28, 28], [], [28], [28, 25]],
+ [[28], [27], [], [25]],
+ [[29], [29, 27], [29, 28], []]
+ ];
+ const PREFERENCE = [1, 0, 2, 3]; // lower, upper, mixed, punctuation
+ const EC_STEP_WORDS = 20;
+
+ // One of the generated table resources ("v" holds the values).
+ function table(id as ResourceId) as Array {
+ return (WatchUi.loadResource(id) as Dictionary)["v"] as Array;
+ }
+
+ // Data codewords (without length descriptor and padding).
+ function dataCodewords(data as ByteArray, text as Array<Array<Number>>) as Array<Number> {
+ // Runs of one kind: [kind, start, end) triples.
+ var runs = [] as Array<Number>;
+ for (var i = 0; i < data.size(); i++) {
+ var kind = kindOf(data[i], text);
+ var n = runs.size();
+ if (n > 0 && runs[n - 3] == kind) {
+ runs[n - 1] = i + 1;
+ } else {
+ runs.add(kind);
+ runs.add(i);
+ runs.add(i + 1);
+ }
+ }
+ // Short digit runs next to text become text.
+ for (var r = 0; r < runs.size(); r += 3) {
+ if (runs[r] == KIND_NUMERIC && runs[r + 2] - runs[r + 1] < 13
+ && ((r > 0 && runs[r - 3] == KIND_TEXT) || (r + 3 < runs.size() && runs[r + 3] == KIND_TEXT))) {
+ runs[r] = -KIND_TEXT - 10; // marked; resolved below so later checks see the original kinds
+ }
+ }
+ for (var r = 0; r < runs.size(); r += 3) {
+ if (runs[r] == -KIND_TEXT - 10) {
+ runs[r] = KIND_TEXT;
+ }
+ }
+ // Merge equal neighbours.
+ var merged = [] as Array<Number>;
+ for (var r = 0; r < runs.size(); r += 3) {
+ var n = merged.size();
+ if (n > 0 && merged[n - 3] == runs[r]) {
+ merged[n - 1] = runs[r + 2];
+ } else {
+ merged.add(runs[r]);
+ merged.add(runs[r + 1]);
+ merged.add(runs[r + 2]);
+ }
+ }
+ var out = [] as Array<Number>;
+ for (var r = 0; r < merged.size(); r += 3) {
+ var kind = merged[r];
+ var start = merged[r + 1];
+ var end = merged[r + 2];
+ if (r > 0 || kind != KIND_TEXT) {
+ out.add(kind == KIND_TEXT ? TEXT_LATCH : (kind == KIND_NUMERIC ? NUMERIC_LATCH
+ : ((end - start) % 6 == 0 ? BYTE_LATCH_6 : BYTE_LATCH)));
+ }
+ if (kind == KIND_TEXT) {
+ compactText(data, start, end, text, out);
+ } else if (kind == KIND_NUMERIC) {
+ compactNumeric(data, start, end, out);
+ } else {
+ compactBytes(data, start, end, out);
+ }
+ }
+ return out;
+ }
+
+ function kindOf(b as Number, text as Array<Array<Number>>) as Number {
+ if (b >= 0x30 && b <= 0x39) {
+ return KIND_NUMERIC;
+ }
+ if (b < 127) {
+ for (var m = 0; m < 4; m++) {
+ if ((text[m][b] as Number) >= 0) {
+ return KIND_TEXT;
+ }
+ }
+ }
+ return KIND_BYTE;
+ }
+
+ function compactText(data as ByteArray, start as Number, end as Number, text as Array<Array<Number>>, out as Array<Number>) as Void {
+ var interim = [] as Array<Number>;
+ var submode = UPPER;
+ for (var i = start; i < end; i++) {
+ var c = data[i];
+ if ((text[submode][c] as Number) < 0) {
+ var next = submode;
+ for (var p = 0; p < 4; p++) {
+ var m = PREFERENCE[p] as Number;
+ if ((text[m][c] as Number) >= 0) {
+ next = m;
+ break;
+ }
+ }
+ interim.addAll((SWITCHES[submode] as Array)[next] as Array<Number>);
+ submode = next;
+ }
+ interim.add(text[submode][c] as Number);
+ }
+ for (var i = 0; i < interim.size(); i += 2) {
+ out.add(30 * interim[i] + (i + 1 < interim.size() ? interim[i + 1] : 29));
+ }
+ }
+
+ // 44 digits per group: the value of "1" followed by the digits, in base 900.
+ function compactNumeric(data as ByteArray, start as Number, end as Number, out as Array<Number>) as Void {
+ for (var g = start; g < end; g += 44) {
+ var stop = g + 44 < end ? g + 44 : end;
+ var decimal = [1] as Array<Number>;
+ for (var i = g; i < stop; i++) {
+ decimal.add(data[i] - 0x30);
+ }
+ var base900 = [] as Array<Number>;
+ while (decimal.size() > 0) {
+ // Divide the decimal number by 900, keeping the quotient.
+ var quotient = [] as Array<Number>;
+ var rem = 0;
+ for (var k = 0; k < decimal.size(); k++) {
+ var cur = rem * 10 + decimal[k];
+ var q = cur / 900;
+ rem = cur % 900;
+ if (quotient.size() > 0 || q > 0) {
+ quotient.add(q);
+ }
+ }
+ base900.add(rem);
+ decimal = quotient;
+ }
+ for (var k = base900.size() - 1; k >= 0; k--) {
+ out.add(base900[k]);
+ }
+ }
+ }
+
+ // 6 bytes per group as 5 base-900 codewords; a shorter last group as is.
+ function compactBytes(data as ByteArray, start as Number, end as Number, out as Array<Number>) as Void {
+ for (var g = start; g < end; g += 6) {
+ if (g + 6 > end) {
+ for (var i = g; i < end; i++) {
+ out.add(data[i]);
+ }
+ return;
+ }
+ var value = 0l;
+ for (var i = g; i < g + 6; i++) {
+ value = value * 256 + data[i];
+ }
+ var digits = new [5] as Array<Number>;
+ for (var k = 4; k >= 0; k--) {
+ digits[k] = (value % 900).toNumber();
+ value = value / 900;
+ }
+ out.addAll(digits);
+ }
+ }
+
+ // ISO 15438 annex E recommended level for the data codewords.
+ function autoLevel(dataCount as Number) as Number {
+ if (dataCount <= 40) {
+ return 2;
+ }
+ if (dataCount <= 160) {
+ return 3;
+ }
+ return dataCount <= 320 ? 4 : 5;
+ }
+
+ // The option's level: Low 2, Medium automatic, Quartile 5, High 6.
+ function levelFor(option as Number, dataCount as Number) as Number {
+ return [2, autoLevel(dataCount), 5, 6][option] as Number;
+ }
+
+ function leftIndicator(row as Number, rows as Number, cols as Number, level as Number) as Number {
+ var t = row % 3;
+ var x = t == 0 ? (rows - 1) / 3 : (t == 1 ? level * 3 + (rows - 1) % 3 : cols - 1);
+ return 30 * (row / 3) + x;
+ }
+
+ function rightIndicator(row as Number, rows as Number, cols as Number, level as Number) as Number {
+ var t = row % 3;
+ var x = t == 0 ? cols - 1 : (t == 1 ? (rows - 1) / 3 : level * 3 + (rows - 1) % 3);
+ return 30 * (row / 3) + x;
+ }
+
+ // Encode synchronously with given columns and level (tests).
+ function encode(text as String, cols as Number, level as Number) as Codes.Matrix or Codes.CodeError {
+ var job = new Pdf417Job(text, 0, 0, 0, false);
+ job.force(cols, level);
+ Jobs.runToCompletion(job, 1);
+ var err = job.error;
+ return err != null ? new Codes.CodeError(err) : job.matrix() as Codes.Matrix;
+ }
+
+ class Pdf417Job extends Codes.MatrixJob {
+
+ var columns as Number = 0;
+ var level as Number = -1;
+ var rows as Number = 0;
+
+ private var _data as ByteArray;
+ private var _option as Number;
+ private var _screenW as Number;
+ private var _screenH as Number;
+ private var _round as Boolean;
+ private var _phase as Number = 0;
+ private var _words as Array<Number> = [] as Array<Number>;
+ private var _ec as Array<Number> = [] as Array<Number>;
+ private var _factors as Array<Number> = [] as Array<Number>;
+ private var _codes as Array<Array<Number>> = [] as Array<Array<Number>>;
+ private var _at as Number = 0;
+ private var _modules as ByteArray = []b;
+ private var _result as Codes.Matrix? = null;
+
+ // option: the EC option (0-3). The screen size decides the columns.
+ function initialize(text as String, option as Number, screenW as Number, screenH as Number, round as Boolean) {
+ MatrixJob.initialize();
+ _data = Bytes.fromUtf8(text);
+ _option = option;
+ _screenW = screenW;
+ _screenH = screenH;
+ _round = round;
+ }
+
+ // Tests: fixed columns and level instead of the screen and option.
+ function force(cols as Number, ecLevel as Number) as Void {
+ columns = cols;
+ level = ecLevel;
+ }
+
+ function percent() as Number {
+ return _phase == 1 && _words.size() > 0 ? 20 + 60 * _at / _words.size() : _phase * 30;
+ }
+
+ function matrix() as Codes.Matrix? {
+ return _result;
+ }
+
+ function step(budget as Number) as Boolean {
+ if (_phase == 0) {
+ prepare();
+ } else if (_phase == 1) {
+ ecStep();
+ } else if (_phase == 2) {
+ rowsStep();
+ }
+ return _phase >= 3 || error != null;
+ }
+
+ private function prepare() as Void {
+ if (_data.size() == 0) {
+ error = "PDF417 needs some text";
+ return;
+ }
+ var data = dataCodewords(_data, table(Rez.JsonData.Pdf417Text) as Array<Array<Number>>);
+ if (level < 0) {
+ level = levelFor(_option, data.size());
+ }
+ var ecCount = 1 << (level + 1);
+ if (columns == 0 && !chooseLayout(data.size() + ecCount + 1)) {
+ error = "too large for this screen as PDF417 (" + data.size() + " codewords)";
+ return;
+ }
+ var total = data.size() + ecCount + 1;
+ var padding = total % columns == 0 ? 0 : columns - total % columns;
+ var length = data.size() + padding + 1;
+ rows = (total + padding) / columns;
+ if (length > MAX_CODEWORDS || rows > 90) {
+ error = "too long for PDF417 (" + data.size() + " codewords)";
+ return;
+ }
+ if (rows < 3) {
+ error = "PDF417 needs at least 3 rows; use fewer columns";
+ return;
+ }
+ _words = [length] as Array<Number>;
+ _words.addAll(data);
+ for (var i = 0; i < padding; i++) {
+ _words.add(PAD);
+ }
+ _factors = (table(Rez.JsonData.Pdf417Ec) as Array<Array<Number>>)[level];
+ _codes = [table(Rez.JsonData.Pdf417Codes0) as Array<Number>, table(Rez.JsonData.Pdf417Codes1) as Array<Number>,
+ table(Rez.JsonData.Pdf417Codes2) as Array<Number>];
+ _ec = new [ecCount] as Array<Number>;
+ for (var i = 0; i < ecCount; i++) {
+ _ec[i] = 0;
+ }
+ _at = 0;
+ _phase = 1;
+ }
+
+ // The largest module size, then the fewest columns, that fit.
+ private function chooseLayout(codewords as Number) as Boolean {
+ for (var px = 6; px >= 1; px--) {
+ for (var cols = 1; cols <= 30; cols++) {
+ var r = (codewords + cols - 1) / cols;
+ if (r < 3 || r > 90) {
+ continue;
+ }
+ var w = 17 * (cols + 4) + 1 + 2 * QUIET;
+ var h = r * ROW_HEIGHT + 2 * QUIET;
+ if (Codes.fits(w, h, px, _screenW, _screenH, _round)) {
+ columns = cols;
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
+ // Reed-Solomon mod 929 (pdf417gen's form), EC_STEP_WORDS words per step.
+ private function ecStep() as Void {
+ var n = _ec.size();
+ var f = _factors;
+ var end = _at + EC_STEP_WORDS < _words.size() ? _at + EC_STEP_WORDS : _words.size();
+ for (var i = _at; i < end; i++) {
+ var temp = (_words[i] + _ec[n - 1]) % 929;
+ for (var x = n - 1; x >= 0; x--) {
+ var word = x > 0 ? _ec[x - 1] : 0;
+ _ec[x] = (word + 929 - (temp * f[x]) % 929) % 929;
+ }
+ }
+ _at = end;
+ if (_at < _words.size()) {
+ return;
+ }
+ for (var x = n - 1; x >= 0; x--) {
+ var v = _ec[x];
+ _words.add(v > 0 ? 929 - v : 0);
+ }
+ _ec = [] as Array<Number>;
+ var width = 17 * (columns + 4) + 1;
+ _modules = new [width * rows]b;
+ _at = 0;
+ _phase = 2;
+ }
+
+ // A few rows per step: start, indicators, data, stop.
+ private function rowsStep() as Void {
+ var width = 17 * (columns + 4) + 1;
+ for (var k = 0; k < 4 && _at < rows; k++) {
+ var row = _at;
+ var cluster = _codes[row % 3];
+ var x = put(row, 0, START, 17, width);
+ x = put(row, x, cluster[leftIndicator(row, rows, columns, level)] as Number, 17, width);
+ for (var c = 0; c < columns; c++) {
+ x = put(row, x, cluster[_words[row * columns + c]] as Number, 17, width);
+ }
+ x = put(row, x, cluster[rightIndicator(row, rows, columns, level)] as Number, 17, width);
+ put(row, x, STOP, 18, width);
+ _at++;
+ }
+ if (_at < rows) {
+ return;
+ }
+ _codes = [] as Array<Array<Number>>;
+ _words = [] as Array<Number>;
+ var result = new Codes.Matrix(width, rows, QUIET, _modules);
+ result.rowHeight = ROW_HEIGHT;
+ _result = result;
+ _phase = 3;
+ }
+
+ // Draw count bits of pattern (most significant first) at column x of
+ // row `row`; returns the next column.
+ private function put(row as Number, x as Number, pattern as Number, count as Number, width as Number) as Number {
+ var at = row * width + x;
+ for (var i = 0; i < count; i++) {
+ _modules[at + i] = (pattern >> (count - 1 - i)) & 1;
+ }
+ return x + count;
+ }
+ }
+}
diff --git a/source/codes/Qr.mc b/source/codes/Qr.mc
index 8af0f5a..1fa69da 100644
--- a/source/codes/Qr.mc
+++ b/source/codes/Qr.mc
@@ -207,9 +207,8 @@ module Qr {
return err != null ? new Codes.CodeError(err) : job.result as MatrixCode;
}
- class QrJob extends Jobs.Job {
+ class QrJob extends Codes.MatrixJob {
- var error as String? = null;
var result as MatrixCode? = null;
private var _data as ByteArray;
@@ -237,7 +236,7 @@ module Qr {
// ec: 0 L, 1 M, 2 Q, 3 H. forceVersion 0 = smallest that fits;
// forceMask -1 = best by penalty.
function initialize(text as String, ec as Number, forceVersion as Number, forceMask as Number) {
- Job.initialize();
+ MatrixJob.initialize();
_data = Bytes.fromUtf8(text);
_ec = ec;
_forceMask = forceMask;
@@ -249,6 +248,12 @@ module Qr {
return _phase * 100 / 6;
}
+ // The code as a generic matrix with QR's 4-module quiet zone.
+ function matrix() as Codes.Matrix? {
+ var r = result;
+ return r != null ? new Codes.Matrix(r.size, r.size, 4, r.modules) : null;
+ }
+
function step(budget as Number) as Boolean {
if (_phase == 0) {
buildCodewords();