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 Code 128 with option "suppressc" #278

Merged
merged 5 commits into from
Nov 7, 2024
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
167 changes: 167 additions & 0 deletions src/code128.ps.src
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,8 @@ begin
/newencoder false def
/parse false def
/parsefnc false def
/suppressc false def % Suppress code set C
/unlatchextbeforec false def % Suppress extended ASCII with code set C
terryburton marked this conversation as resolved.
Show resolved Hide resolved

//processoptions exec /options exch def
/barcode exch def
Expand Down Expand Up @@ -396,6 +398,171 @@ begin

encoding (new) eq {

% Include pseudo characters for GS1-128 Composite linkage identifiers
/seta <<fn3 96 fn2 97 fn1 102 stp 106 lka 100 lkc 99>> def
/setb <<fn3 96 fn2 97 fn1 102 stp 106 lka 99 lkc 101>> def
/setc << fn1 102 stp 106 lka 101 lkc 100>> def

/text msglen string def
0 1 msglen 1 sub {
/i exch def
text i msg i get dup 0 lt { pop 32 } if put
} for

65 dict begin
lyngklip marked this conversation as resolved.
Show resolved Hide resolved
% Encoder states are named A0, B0, C0, A1, B1, and C1.
% A, B, and C are the Code 128 code sets.
% 0 is for ASCII and 1 is for extended ASCII range.
% The encoder relies on this order a lot, but it uses
% the "state_priority" list for prioritizing states.
% Strings are a convenient way of defining latch
% sequences. The strings map to Code 128 instructions.
% The (dddc) sequences are never inserted, but they
% are in the lists to provide the correct length:
% Prior state
% A0 B0 C0 A1 B1 C1
/latch_a0 [() (e) (e) (ee) (eee) (eee) ] def
/latch_b0 [(d) () (d) (ddd) (dd) (ddd) ] def
/latch_c0 [(c) (c) () (eec) (ddc) (dddc)] def
/latch_a1 [(ee) (eee) (eee) () (e) (e) ] def
/latch_b1 [(ddd) (dd) (ddd) (d) () (d) ] def
/latch_c1 [(eec) (ddc) (dddc) (c) (c) () ] def

/latch_length_a0 [latch_a0 {length} forall] def
/latch_length_a1 [latch_a1 {length} forall] def
/latch_length_b0 [latch_b0 {length} forall] def
/latch_length_b1 [latch_b1 {length} forall] def
/latch_length_c0 [latch_c0 {length} forall] def
/latch_length_c1 [latch_c1 {length} forall] def

% Backtracking needs a way of mapping states to sequences:
lyngklip marked this conversation as resolved.
Show resolved Hide resolved
/latch_sequence [latch_a0 latch_b0 latch_c0 latch_a1 latch_b1 latch_c1] def
/encode [{enc_a0} {enc_b0} {enc_c } {enc_a1} {enc_b1} {enc_c }] def
/start_code [103 104 105] def

/state_priority [1 0 2 4 3 5] def % Configure encoder to conform with existing tests
/start_state [0 1 2 0 1 2] def % Encoding starts in ASCII
/start_length [1 1 1 1 1 1] def % Room for start code

% A reverse priority list is handy for preprocessing latch lengths and final state:
/reverse_priority [[5 4 3 2 1 0] {state_priority exch get} forall] def

% Preprocessed latch lengths help satisfy the need for speed:
/prioritized_latch_length_a0 [reverse_priority {dup dup latch_length_a0 exch get exch} forall] def
/prioritized_latch_length_a1 [reverse_priority {dup dup latch_length_a1 exch get exch} forall] def
/prioritized_latch_length_b0 [reverse_priority {dup dup latch_length_b0 exch get exch} forall] def
/prioritized_latch_length_b1 [reverse_priority {dup dup latch_length_b1 exch get exch} forall] def
/prioritized_latch_length_c0 [reverse_priority {dup dup latch_length_c0 exch get exch} forall] def
/prioritized_latch_length_c1 [reverse_priority {dup dup latch_length_c1 exch get exch} forall] def

/max_int 2147483647 def % Make sure state doesn't get picked.
lyngklip marked this conversation as resolved.
Show resolved Hide resolved

% Predicates for ability to encode:
/can_a {c 0 ge {true} {seta c known} ifelse} def
/can_b {c 0 ge {true} {setb c known} ifelse} def
/can_c0 {num_digits 2 ge {true} {setc c known} ifelse} def
/can_c1 {num_digits 2 ge {true} {setc c known} ifelse} def

% Predicates overruled by options:
suppressc {/can_c0 {false} def} if
suppressc unlatchextbeforec or {/can_c1 {false} def} if

% Output length:
/out_a0 {1 c 0 ge {c 128 ge {1 add} if c 127 and 96 ge {1 add} if} if} def
/out_a1 {1 c 0 ge {c 128 lt {1 add} if c 127 and 96 ge {1 add} if} if} def
/out_b0 {1 c 0 ge {c 128 ge {1 add} if c 127 and 32 lt {1 add} if} if} def
/out_b1 {1 c 0 ge {c 128 lt {1 add} if c 127 and 32 lt {1 add} if} if} def

% Encode:
/map_ab {dup 32 lt {64 add} {32 sub} ifelse} def
/enc_a0 {[c 0 lt {seta c get} {c 128 ge {101} if c 127 and dup 96 ge {98 exch} if map_ab} ifelse]} def
/enc_a1 {[c 0 lt {seta c get} {c 128 lt {101} if c 127 and dup 96 ge {98 exch} if map_ab} ifelse]} def
/enc_b0 {[c 0 lt {setb c get} {c 128 ge {100} if c 127 and dup 32 lt {98 exch} if map_ab} ifelse]} def
/enc_b1 {[c 0 lt {setb c get} {c 128 lt {100} if c 127 and dup 32 lt {98 exch} if map_ab} ifelse]} def
/enc_c {[c 0 lt {setc c get} {msg n get 48 sub 10 mul msg n 1 add get 48 sub add } ifelse]} def

% Get best prior state based on a prior row of lengths and a row of latch
% lengths (unrolled and with preprocessed latch lengths on the stack):
/get_best_prior_state {
bln_0 exch get add /len exch def /o exch def
bln_0 exch get add dup len lt {/len exch def /o exch def} {pop pop} ifelse
bln_0 exch get add dup len lt {/len exch def /o exch def} {pop pop} ifelse
bln_0 exch get add dup len lt {/len exch def /o exch def} {pop pop} ifelse
bln_0 exch get add dup len lt {/len exch def /o exch def} {pop pop} ifelse
bln_0 exch get add len lt { /o exch def} { pop} ifelse
o
} def

% The encoder considers the current row and two rows back.
% The circular history buffer size is 4 for convenience.
% The names are short to keep the lines below reasonable:
/bln_0 start_length def /bln_1 start_length def /bln [4 {[0 0 0 0 0 0]} repeat] def % Best Length
lyngklip marked this conversation as resolved.
Show resolved Hide resolved
/bps_0 start_state def /bps_1 start_state def /bps [4 {[0 0 0 0 0 0]} repeat] def % Best Prior State

% Path for backtracking (could make path a string if it makes a difference):
terryburton marked this conversation as resolved.
Show resolved Hide resolved
/path [msg length {[0 0 0 0 0 0]} repeat] def

/make_tables {
/num_digits 0 def
0 1 msg length 1 sub {
/n exch def
/c msg n get def

% Keep a tab on digits:
/num_digits c 48 ge c 58 lt and {num_digits 1 add} {0} ifelse def

% Circular history buffer machinery:
/bln_2 bln_1 def /bln_1 bln_0 def /bln_0 bln n 3 and get def
/bps_2 bps_1 def /bps_1 bps_0 def /bps_0 bps n 3 and get def

% Pick history rows for code set c depending on digits:
/bps_c num_digits 2 ge {bps_2} {bps_1} ifelse def
/bln_c num_digits 2 ge {bln_2} {bln_1} ifelse def

% Use the best prior states and the prior best lengths to determine new best lengths and plot the path:
bln_0 0 can_a {/p bps_1 0 get def path n get 0 p put bln_1 p get latch_length_a0 p get add out_a0 add} {max_int} ifelse put
bln_0 3 can_a {/p bps_1 3 get def path n get 3 p put bln_1 p get latch_length_a1 p get add out_a1 add} {max_int} ifelse put
bln_0 1 can_b {/p bps_1 1 get def path n get 1 p put bln_1 p get latch_length_b0 p get add out_b0 add} {max_int} ifelse put
bln_0 4 can_b {/p bps_1 4 get def path n get 4 p put bln_1 p get latch_length_b1 p get add out_b1 add} {max_int} ifelse put
bln_0 2 can_c0 {/p bps_c 2 get def path n get 2 p put bln_c p get latch_length_c0 p get add 1 add} {max_int} ifelse put
bln_0 5 can_c1 {/p bps_c 5 get def path n get 5 p put bln_c p get latch_length_c1 p get add 1 add} {max_int} ifelse put

% Use the new best lengths to determine new best prior states:
bps_0 0 prioritized_latch_length_a0 aload pop get_best_prior_state put
bps_0 3 prioritized_latch_length_a1 aload pop get_best_prior_state put
bps_0 1 prioritized_latch_length_b0 aload pop get_best_prior_state put
bps_0 4 prioritized_latch_length_b1 aload pop get_best_prior_state put
bps_0 2 prioritized_latch_length_c0 aload pop get_best_prior_state put
bps_0 5 prioritized_latch_length_c1 aload pop get_best_prior_state put
} for
} def

/backtrack {
/n msg length def

% Get best final state and final length:
reverse_priority {dup bln_0 exch get dup} forall
pop /len exch def /state exch def
5 {len lt {/len exch def /state exch def} {pop pop} ifelse} repeat

len array % Put output array on the stack
{
n 0 le {exit} if
/prior_state path n 1 sub get state get def % Get prior state (following path)
/latch [latch_sequence state get prior_state get {} forall] def % Get latch sequence and convert from string to array
/n n state 2 eq state 5 eq or msg n 1 sub get 48 ge and {2} {1} ifelse sub def % Get intake and subtract it from the input index
/c msg n get def % Get current character (the encoding below needs it)
/enc encode state get exec def % Encode state
/len len latch length sub enc length sub def % Subtract latch length and output length from output index
dup len latch 3 copy putinterval length add enc putinterval % Copy latch and encoded entry to output
/state prior_state def % Prepare for next iteration
} loop
dup 0 start_code state get put % Add start code
} def

make_tables backtrack
end
lyngklip marked this conversation as resolved.
Show resolved Hide resolved
/cws exch def
} if % auto encoding
lyngklip marked this conversation as resolved.
Show resolved Hide resolved

% Derive checksum and place stop character
Expand Down
Binary file modified tests/ps_tests/code128.ps
Binary file not shown.