Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimal encoding of Maxicode #279

Closed
wants to merge 15 commits into from
178 changes: 177 additions & 1 deletion 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,10 @@ begin
/sete charvals 4 get def
} ctxdef

/maxlen mode 5 eq {77} {mode 3 le {84} {93} ifelse} ifelse def

encoding (legacy) eq {

% Compute numeric runlengths
/nseq [ msglen 1 add {0} repeat ] def
msglen 1 sub -1 0 {
Expand Down Expand Up @@ -305,7 +313,7 @@ begin
{ % loop
% Exit when no characters remain latching back to A if necessary
i msglen eq {
cset (seta) ne cset (setb) ne and {
cset (seta) ne cset (setb) ne and out length maxlen lt and {
la cset load enc
/cset (seta) def
} if
Expand Down Expand Up @@ -457,6 +465,174 @@ 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_eci {c neg 1000000 sub dup 32 ge {dup 1024 lt {
dup -6 bitshift 32 or exch 63 and} {dup 32768 lt {
dup -12 bitshift 48 or exch
dup -6 bitshift 63 and exch 63 and} {
dup -18 bitshift 56 or exch
dup -12 bitshift 63 and exch
dup -6 bitshift 63 and exch 63 and} ifelse} ifelse} if} 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 {eci 1 ge } /intake 1 /output {out_eci} /sets 2#11111 /enc {27 enc_eci } >> % ECI 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 % Length returned 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 { % Determine if the operation accepts the input
/m n op /intake get sub 15 and def % Get index 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 encoding length
latch_length_s org get add % Add latch length
op /output get exec add % Add output length to yield resulting length
2 copy gt { % Compare lengths
exch % Keep the shorter length
path_op_0 state op /idx get put % Store operation in path
prior_code_set_0 state org put % Store prior set in path
} if pop % Pop off the longer (or equal) length
} if
} forall
} 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
0 max_int priority {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 padding is necessary and the code set does not have a pad code.
pad_code state get 0 eq j maxlen lt and {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