diff options
Diffstat (limited to 'source/codes/Pdf417.mc')
| -rw-r--r-- | source/codes/Pdf417.mc | 286 |
1 files changed, 214 insertions, 72 deletions
diff --git a/source/codes/Pdf417.mc b/source/codes/Pdf417.mc index b17b5df..a294aa7 100644 --- a/source/codes/Pdf417.mc +++ b/source/codes/Pdf417.mc @@ -53,7 +53,12 @@ module Pdf417 { [[29], [29, 27], [29, 28], []] ]; const PREFERENCE = [1, 0, 2, 3]; // lower, upper, mixed, punctuation - const EC_STEP_WORDS = 20; + // Work per step: each was checked in the simulator by running two + // steps per event on the Forerunner 255S (120,000 bytecodes per event) + // with the largest payload its screen takes. + const COMPACT_PER_STEP = 150; // bytes (or runs) compacted + const EC_WORK_PER_STEP = 300; // data codewords x EC codewords + const ROWS_PER_STEP = 2; // One of the generated table resources ("v" holds the values). function table(id as ResourceId) as Array { @@ -62,61 +67,179 @@ module Pdf417 { // 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; + var compactor = new Compactor(data, text); + while (!compactor.step(data.size() + 1)) { + } + return compactor.out; + } + + // dataCodewords() in steps of about `budget` bytes (or runs): the runs + // of one kind, then the merged runs (short digit runs next to text + // become text, equal neighbours join), then the compaction of each run. + class Compactor { + var out as Array<Number> = [] as Array<Number>; + + private var _data as ByteArray; + private var _text as Array<Array<Number>>; + private var _stage as Number = 0; + private var _i as Number = 0; + // [kind, start, end) triples, before and after merging. + private var _runs as Array<Number> = [] as Array<Number>; + private var _merged as Array<Number> = [] as Array<Number>; + // Compaction: the position in the current run (-1 before its latch), + // the text submode and a text value waiting for its pair. + private var _pos as Number = -1; + private var _submode as Number = UPPER; + private var _half as Number = -1; + + function initialize(data as ByteArray, text as Array<Array<Number>>) { + _data = data; + _text = text; + } + + // True when out is complete. + function step(budget as Number) as Boolean { + if (_stage == 0) { + findRuns(budget); + } else if (_stage == 1) { + mergeRuns(budget); } else { - runs.add(kind); - runs.add(i); - runs.add(i + 1); + compact(budget); } + return _stage == 3; } - // 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 + + private function findRuns(budget as Number) as Void { + var data = _data; + var runs = _runs; + var stop = _i + budget < data.size() ? _i + budget : data.size(); + for (var i = _i; i < stop; 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); + } + } + _i = stop; + if (_i >= data.size()) { + _i = 0; + _stage = 1; } } - for (var r = 0; r < runs.size(); r += 3) { - if (runs[r] == -KIND_TEXT - 10) { - runs[r] = KIND_TEXT; + + // Short digit runs next to text become text (judged by the original + // neighbours), and equal neighbours merge, in one pass. + private function mergeRuns(budget as Number) as Void { + var runs = _runs; + var merged = _merged; + var stop = _i + 3 * budget < runs.size() ? _i + 3 * budget : runs.size(); + for (var r = _i; r < stop; r += 3) { + var kind = runs[r]; + if (kind == 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))) { + kind = KIND_TEXT; + } + var n = merged.size(); + if (n > 0 && merged[n - 3] == kind) { + merged[n - 1] = runs[r + 2]; + } else { + merged.add(kind); + merged.add(runs[r + 1]); + merged.add(runs[r + 2]); + } + } + _i = stop; + if (_i >= runs.size()) { + _runs = [] as Array<Number>; + _i = 0; + _stage = 2; } } - // 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]); + + private function compact(budget as Number) as Void { + var left = budget; + var merged = _merged; + while (left > 0 && _i < merged.size()) { + var kind = merged[_i]; + var start = merged[_i + 1]; + var end = merged[_i + 2]; + if (_pos < 0) { + if (_i > 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))); + } + _pos = start; + _submode = UPPER; + _half = -1; + } + var stop = 0; + if (kind == KIND_TEXT) { + stop = _pos + left < end ? _pos + left : end; + compactText(_pos, stop); + left -= stop - _pos; + } else if (kind == KIND_NUMERIC) { + stop = _pos + 44 < end ? _pos + 44 : end; + compactNumeric(_data, _pos, stop, out); + left -= 4 * (stop - _pos); // long division: costly per digit + } else { + var groups = left / 6 > 0 ? left / 6 : 1; + stop = _pos + 6 * groups < end ? _pos + 6 * groups : end; + compactBytes(_data, _pos, stop, out); + left -= stop - _pos; + } + _pos = stop; + if (_pos >= end) { + if (kind == KIND_TEXT && _half >= 0) { + out.add(30 * _half + 29); + } + _pos = -1; + _i += 3; + } + } + if (_i >= merged.size()) { + _merged = [] as Array<Number>; + _stage = 3; + } + } + + // Text values of data[from, to) in the current submode, with switch + // codes; values are paired into codewords as they come. + private function compactText(from as Number, to as Number) as Void { + var text = _text; + var data = _data; + for (var i = from; i < to; 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; + } + } + var codes = (SWITCHES[_submode] as Array)[next] as Array<Number>; + for (var k = 0; k < codes.size(); k++) { + textValue(codes[k]); + } + _submode = next; + } + textValue(text[_submode][c] as Number); } } - 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); + + private function textValue(v as Number) as Void { + if (_half < 0) { + _half = v; } else { - compactBytes(data, start, end, out); + out.add(30 * _half + v); + _half = -1; } } - return out; } function kindOf(b as Number, text as Array<Array<Number>>) as Number { @@ -133,30 +256,6 @@ module Pdf417 { 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) { @@ -261,6 +360,8 @@ module Pdf417 { 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 _compactor as Compactor? = null; + private var _dataWords as Array<Number>? = null; private var _codes as Array<Array<Number>> = [] as Array<Array<Number>>; private var _at as Number = 0; private var _modules as ByteArray = []b; @@ -301,12 +402,34 @@ module Pdf417 { return _phase >= 3 || error != null; } + // Compaction in steps (after a quick check that the text could fit + // the screen at all), then level, layout and padding, and the + // tables in a step of their own. 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>>); + var data = _dataWords; + if (data == null) { + var compactor = _compactor; + if (compactor == null) { + // At best 44 digits take 15 codewords. + var least = _data.size() * 15 / 44; + if (least > MAX_CODEWORDS || (columns == 0 && least > screenCapacity())) { + error = "too large for this screen as PDF417 (at least " + least + " codewords)"; + return; + } + compactor = new Compactor(_data, table(Rez.JsonData.Pdf417Text) as Array<Array<Number>>); + _compactor = compactor; + } + if (!compactor.step(COMPACT_PER_STEP)) { + return; + } + _compactor = null; + _dataWords = compactor.out; + return; + } if (level < 0) { level = levelFor(_option, data.size()); } @@ -329,6 +452,7 @@ module Pdf417 { } _words = [length] as Array<Number>; _words.addAll(data); + _dataWords = null; for (var i = 0; i < padding; i++) { _words.add(PAD); } @@ -343,6 +467,22 @@ module Pdf417 { _phase = 1; } + // The most codewords any layout can hold on this screen (1 px per + // module, up to 90 rows). + private function screenCapacity() as Number { + var best = 0; + for (var cols = 1; cols <= 30; cols++) { + var w = 17 * (cols + 4) + 1 + 2 * QUIET; + for (var r = 90; r >= 3; r -= 3) { + if (Codes.fits(w, r * ROW_HEIGHT + 2 * QUIET, 1, _screenW, _screenH, _round)) { + best = cols * r > best ? cols * r : best; + break; + } + } + } + return best; + } + // 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--) { @@ -362,11 +502,13 @@ module Pdf417 { return false; } - // Reed-Solomon mod 929 (pdf417gen's form), EC_STEP_WORDS words per step. + // Reed-Solomon mod 929 (pdf417gen's form), EC_WORK_PER_STEP data + // codewords times EC codewords 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(); + var words = EC_WORK_PER_STEP / n > 0 ? EC_WORK_PER_STEP / n : 1; + var end = _at + words < _words.size() ? _at + 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--) { @@ -392,7 +534,7 @@ module Pdf417 { // 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++) { + for (var k = 0; k < ROWS_PER_STEP && _at < rows; k++) { var row = _at; var cluster = _codes[row % 3]; var x = put(row, 0, START, 17, width); |
