aboutsummaryrefslogtreecommitdiff
path: root/source/codes/Pdf417.mc
diff options
context:
space:
mode:
authorcirakg <cirakg@cirakg.xyz>2026-10-21 03:55:01 +0200
committercirakg <cirakg@cirakg.xyz>2026-10-21 03:55:01 +0200
commit88f1cac4fa530fc30fd6ef01970af11c7a959b8e (patch)
tree4e2e52b36a6faa2dd50d3b6b07fbfe46fb5dc48c /source/codes/Pdf417.mc
parenta69e23fb5266dd942d125239e7dcbe8a7c8ab956 (diff)
fix(codes): bound every 2D encoding step under the watchdog; draw large codes from runs
M6's step sizes were estimates, only exercised with small codes. With the largest inputs the app accepts, every 2D encoder tripped the watchdog on the Forerunner 255S (and the biggest steps were far over the 768 KB watches' limit), so a large code added from the phone would have stopped the app during settings processing at every start. - QR: resumable segmentation, Reed-Solomon, interleaving, function patterns and final mask; mask scoring on rows and columns packed 16 modules per word (same penalties, checked against the per-module rules). - Reed-Solomon: resumable field tables and generator, encoder shifting with native slice. - Data Matrix, Aztec, PDF417: resumable encodation, stuffing, compaction, placement and build; Data Matrix's grid as a byte array (90 KB -> 35 KB); PDF417 refuses text that can't fit the screen before compacting. - Cache format 2 with 16-bit sizes (wide PDF417 was cached wrong); packing in steps; codes drawn as runs, and from a bitmap painted in steps when a frame can't draw all runs. - The job runner does two steps per tick on 768 KB watches; gen_products --check verifies each product's watchdogCount fits that. - CodeView renews full brightness (AMOLED) or the backlight (MIP) for up to a minute from its single ticker; a tap restarts the minute. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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);