summaryrefslogtreecommitdiff
path: root/source/codes/Pdf417.mc
diff options
context:
space:
mode:
Diffstat (limited to 'source/codes/Pdf417.mc')
-rw-r--r--source/codes/Pdf417.mc286
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);