Skip to content

Commit

Permalink
Maxicode: New optimising encoder behind newencoder feature toggle
Browse files Browse the repository at this point in the history
  • Loading branch information
lyngklip authored and terryburton committed Nov 11, 2024
1 parent 03ceb50 commit 2a892a6
Show file tree
Hide file tree
Showing 2 changed files with 244 additions and 49 deletions.
184 changes: 184 additions & 0 deletions src/maxicode.ps.src
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ begin
/sam -1 def
/parse false def
/parsefnc false def
/newencoder false def

//processoptions exec /options exch def
/barcode exch def
Expand All @@ -78,6 +79,9 @@ begin
} if
} if

/encoding (legacy) def
newencoder {/encoding (new) def} if

/maxicode //loadctx exec

% Parse the input
Expand Down Expand Up @@ -274,6 +278,8 @@ begin
/sete charvals 4 get def
} ctxdef

encoding (legacy) eq {

% Compute numeric runlengths
/nseq [ msglen 1 add {0} repeat ] def
msglen 1 sub -1 0 {
Expand Down Expand Up @@ -457,6 +463,184 @@ begin
/encmsg out 0 j getinterval def
/padval cset load pad get def

} if

encoding (new) eq {

% Prior code set Later code set
% A B C D E
/latch_sequence [[[ ][63 ][58 ][58 ][58 ]] % A
[[63 ][ ][63 ][63 ][63 ]] % B
[[60 60][60 60][ ][60 60][60 60]] % C
[[61 61][61 61][61 61][ ][61 61]] % D
[[62 62][62 62][62 62][62 62][ ]]] def % E

/latch_length [[ 0 1 1 1 1 ] % A
[ 1 0 1 1 1 ] % B
[ 2 2 0 2 2 ] % C
[ 2 2 2 0 2 ] % D
[ 2 2 2 2 0 ]] def % E

/max_int 16#7FFFFFFF def % max int should make sure a state doesn't get picked

% The encoder needs 10 history rows.
% The circular history buffers are 16 long for convenience
/best_length [16 {[0 0 0 0 0]} repeat] def
/best_origin [16 {[0 0 0 0 0]} repeat] def

% Backtracking information
/prior_code_set [msglen {[5 {0} repeat]} repeat] def
/path_op [msglen {[5 {0} repeat]} repeat] def

% Length of Extended Channel Interpretation
/out_eci {c neg 1000000 sub dup 1024 lt { 32 lt {2} {3} ifelse}
{32768 lt {4} {5} ifelse} ifelse} def

% Operations that don't fit directly into the list below
/enc_eci1 {c neg 1000000 sub } def % Extended
/enc_eci2 {c neg 1000000 sub dup -6 bitshift 32 or exch 63 and} def % Channel
/enc_eci3 {c neg 1000000 sub dup -12 bitshift 48 or exch % Interpretation
dup -6 bitshift 63 and exch 63 and} def
/enc_eci4 {c neg 1000000 sub dup -18 bitshift 56 or exch
dup -12 bitshift 63 and exch
dup -6 bitshift 63 and exch 63 and} def
/enc_ns {0 msg n 9 getinterval {48 sub exch 10 mul add} forall % Numeric
dup -24 bitshift exch % Sequence
dup -18 bitshift 63 and exch
dup -12 bitshift 63 and exch
dup -6 bitshift 63 and exch 63 and} def
/enc_sha2 {seta c get seta msg n 1 add get get} def % Shift 2 A
/enc_sha3 {seta c get seta msg n 1 add get get
seta msg n 2 add get get} def % Shift 3 A

% Table of operations - operating table?
/op_tab [ % predicate applicable sets encoding
<< /can {2 eci eq } /intake 1 /output 2 /sets 2#11111 /enc {27 enc_eci1 } >> % ECI1 ABCDE
<< /can {3 eci eq } /intake 1 /output 3 /sets 2#11111 /enc {27 enc_eci2 } >> % ECI2 ABCDE
<< /can {4 eci eq } /intake 1 /output 4 /sets 2#11111 /enc {27 enc_eci3 } >> % ECI3 ABCDE
<< /can {5 eci eq } /intake 1 /output 5 /sets 2#11111 /enc {27 enc_eci4 } >> % ECI4 ABCDE
<< /can {digits 9 ge } /intake 9 /output 6 /sets 2#11111 /enc {31 enc_ns } >> % NS ABCDE
<< /can {seta c known} /intake 1 /output 1 /sets 2#00001 /enc { seta c get} >> % A A
<< /can {setb c known} /intake 1 /output 1 /sets 2#00010 /enc { setb c get} >> % B B
<< /can {setc c known} /intake 1 /output 1 /sets 2#00100 /enc { setc c get} >> % C C
<< /can {setd c known} /intake 1 /output 1 /sets 2#01000 /enc { setd c get} >> % D D
<< /can {sete c known} /intake 1 /output 1 /sets 2#10000 /enc { sete c get} >> % E E
<< /can {num_a 1 ge } /intake 1 /output 2 /sets 2#00010 /enc {59 seta c get} >> % SHA B
<< /can {num_a 2 ge } /intake 2 /output 3 /sets 2#00010 /enc {56 enc_sha2 } >> % SHA2 B
<< /can {num_a 3 ge } /intake 3 /output 4 /sets 2#00010 /enc {57 enc_sha3 } >> % SHA3 B
<< /can {setb c known} /intake 1 /output 2 /sets 2#00001 /enc {59 setb c get} >> % SHB A
<< /can {setc c known} /intake 1 /output 2 /sets 2#11011 /enc {60 setc c get} >> % SHC ABCDE
<< /can {setd c known} /intake 1 /output 2 /sets 2#10111 /enc {61 setd c get} >> % SHD ABCDE
<< /can {sete c known} /intake 1 /output 2 /sets 2#01111 /enc {62 sete c get} >> % SHE ABCDE
] def

% Add idx to each entry
0 op_tab {/idx 2 index put 1 add} forall pop

% Filter table of operations into lists of operations that apply in each code set
/code_set_operations [[1 2 4 8 16] {/n exch def [op_tab {dup /sets get n and 0 eq {pop} if} forall]} forall] def

% Get the shortest encoded length for the code set (state) and plot the path
/get_best_length {
/latch_length_s latch_length state get def % Get latch length row targetting the code set
max_int /op op_tab 0 get def /org 0 def % Values used if this is not a viable code set
code_set_operations state get { % Loop over operations that apply to this code set
/op_ exch def op_ /can get exec { % Execute predicate to see if the operation applies to input
/m n op_ /intake get sub 15 and def % Use intake to index back into circular history buffers
/org_ best_origin m get state get def % Get the best prior code set
best_length m get org_ get % Get the corresponding length of encoding
latch_length_s org_ get add % Add latch length
op_ /output get add % Add output length to yield resulting length
2 copy gt {
exch /op op_ def /org org_ def % Pick the shortest length and make a note of it
} if pop % Pop off the longer (or equal) length
} if
} forall

% Plot the path
prior_code_set_0 state org put
path_op_0 state op /idx get put
} def

% Unrolled loop to get the best prior code set using a row of
% best encoded lengths and a row of latch sequence lengths.
/get_best_origin {
/latch_length_s latch_length state get def
best_length_0 0 get latch_length_s 0 get add /orglen exch def 0
best_length_0 1 get latch_length_s 1 get add dup orglen lt {/orglen exch def pop 1} {pop} ifelse
best_length_0 2 get latch_length_s 2 get add dup orglen lt {/orglen exch def pop 2} {pop} ifelse
best_length_0 3 get latch_length_s 3 get add dup orglen lt {/orglen exch def pop 3} {pop} ifelse
best_length_0 4 get latch_length_s 4 get add dup orglen lt {/orglen exch def pop 4} {pop} ifelse
} def

/digits 0 def % Number of contiguous digits seen
/num_a 0 def % Number of contiguous characters seen that are encodable in code set A

% Make a table of best path options
0 1 msglen 1 sub {
/n exch def % Input index
/c msg n get def % Input character

% Keep tabs on digits, characters in code set a, and ECI type
/digits c 48 ge c 58 lt and {digits 1 add} {0} ifelse def
/num_a seta c known {num_a 1 add} {0} ifelse def
/eci c -1000000 le {out_eci } {0} ifelse def

% Get rows of interest
/path_op_0 path_op n get def
/prior_code_set_0 prior_code_set n get def
/best_length_0 best_length n 15 and get def
/best_origin_0 best_origin n 15 and get def

% Get best encoded lengths, then best prior code sets
0 1 4 {/state exch def best_length_0 state get_best_length put} for
0 1 4 {/state exch def best_origin_0 state get_best_origin put} for
} for

/n msglen def

% Get the best code set to end with. Code sets with a pad code are first pick
/priority [0 1 4 2 3] def
priority 0 get dup best_length_0 exch get
priority 1 4 getinterval {dup best_length_0 exch get dup 3 index lt {4 2 roll} if pop pop} forall
/j exch def
/state exch def

% End in a code set which has a pad code
/pad_code [33 33 0 0 28] def
/final_code_set pad_code state get 0 eq {0} {state} ifelse def

% Insert a latch to A if the code set does not have a pad code.
% There is something about this that doesn't seem right.
% To paraphrase the standard:
% If the message codewords exactly fill out all the message
% regions of the symbol then the latch to A would be unnecessary
% and would in fact overflow the buffer and cause an error.
pad_code state get 0 eq {j 1 add array dup j 58 put} {j array} ifelse
/padval pad_code final_code_set get def
/len j def

% Follow the best path back to the start of the message
{
n 0 le {exit} if
/pcs prior_code_set n 1 sub get state get def
/op_idx path_op n 1 sub get state get def
/op op_tab op_idx get def
/n n op /intake get sub def
/c msg n get def
/enc op /enc get def
/output [enc exec] def
/latch latch_sequence state get pcs get def
/len len latch length sub output length sub def
dup len latch 3 copy putinterval length add output putinterval
/state pcs def
} loop

% Name the result
/encmsg exch def

} if

% Prefix the encoded message with the structured append insert
/sami sam -1 ne { [ seta pad get sam 10 idiv 1 sub 8 mul sam 10 mod 1 sub add ] } { [] } ifelse def
/encmsg [ sami aload pop encmsg aload pop ] def
Expand Down
Loading

0 comments on commit 2a892a6

Please sign in to comment.