diff options
Diffstat (limited to 'source/codes')
| -rw-r--r-- | source/codes/Codes.mc | 118 | ||||
| -rw-r--r-- | source/codes/DataMatrix.mc | 398 | ||||
| -rw-r--r-- | source/codes/Pdf417.mc | 428 | ||||
| -rw-r--r-- | source/codes/Qr.mc | 11 |
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(); |
