Skip to content

Commit 4644e4a

Browse files
committed
micropdf417: Avoid O(n^2) sequence copying in text-mode encoder
1 parent 355bb5e commit 4644e4a

2 files changed

Lines changed: 64 additions & 53 deletions

File tree

CHANGES

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
XXXX-XX-XX
2+
3+
* The PDF417, MicroPDF417 and Aztec Code encoders were amended to avoid quadratic-time sequence copying in their encoding optimisers.
4+
5+
16
2026-05-29
27

38
* The GS1 Application Identifier element string parsing and validation was made more efficient.

src/micropdf417.ps.src

Lines changed: 59 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -890,32 +890,50 @@ begin
890890
} def
891891

892892
/enct {
893-
894893
/in exch def
895894

896895
%
897-
% Each char adds 1 codeword, latches add up to 2
896+
% Codeword sequences are held as persistent chains of segments.
897+
% Each chain head is null or a node [parent_node, segment_array];
898+
% the final sequence is materialised once by walking the chain.
898899
%
899-
/maxseqlen in length 3 mul 4 add def
900-
901-
/seq0 [ 4 { maxseqlen array } repeat ] def
902-
/seq1 [ 4 { maxseqlen array } repeat ] def
903-
/len0 4 array def len0 0 0 put len0 1 0 put len0 2 0 put len0 3 0 put
904-
/len1 4 array def
905-
906-
/curseq seq0 def /curseqlen len0 def
907-
/nxtseq seq1 def /nxtseqlen len1 def
900+
/curseqhd [ //micropdf417.textsubmodes length { null } repeat ] def
901+
/nxtseqhd [ //micropdf417.textsubmodes length { null } repeat ] def
902+
/curseqlen [ //micropdf417.textsubmodes length { 0 } repeat ] def
903+
/nxtseqlen [ //micropdf417.textsubmodes length { 0 } repeat ] def
908904

909905
/curlen [ //micropdf417.e //micropdf417.e //micropdf417.e //micropdf417.e ] def
910906
curlen submode 0 put
911907

912-
% Derive the optimal sequences ending in each submode
908+
%
909+
% Flatten a chain of [parent, segment] cons cells of total length L
910+
% into a fresh array. The chain's deepest node holds the prefix, so
911+
% we walk head-to-root and write each segment into the tail of the
912+
% output, advancing pos backwards.
913+
%
914+
/flatten { % hd len -> array
915+
916+
dup array 3 1 roll % pos = len; out = array(len)
917+
918+
{ % out hd pos
919+
1 index null eq { exit } if % done when hd is null
920+
1 index 1 get % ... seg = hd[1]
921+
exch 1 index length sub % ... pos -= length(seg)
922+
3 index 1 index 3 index putinterval % out[pos..] = seg
923+
exch pop % drop seg
924+
exch 0 get exch % hd = hd[0]
925+
} loop
926+
927+
pop pop % return out
928+
929+
} def
930+
913931
in {
914932

915933
/char exch def
916934

917-
% Check for optimisations in the current sequences by latching from x to y
918-
{ % loop
935+
% Phase 1: relax curlen[*] via the static latch graph
936+
{
919937
/imp false def
920938
//micropdf417.textsubmodes {
921939
/x exch def
@@ -924,28 +942,24 @@ begin
924942
/cost curlen x get //micropdf417.latlen x get y get add def
925943
cost curlen y get lt {
926944
curlen y cost put
927-
% Copy curseq[x] to curseq[y] and append latseq codes
928-
/srclen curseqlen x get def
929-
srclen 0 gt {
930-
curseq y get 0 curseq x get 0 srclen getinterval putinterval
931-
} if
932-
//micropdf417.latseq x get y get {
933-
curseq y get srclen 3 -1 roll put
934-
/srclen srclen 1 add def
935-
} forall
936-
curseqlen y srclen put
945+
/latyseg //micropdf417.latseq x get y get def
946+
curseqhd y
947+
latyseg length 0 eq
948+
{ curseqhd x get }
949+
{ [ curseqhd x get latyseg ] }
950+
ifelse
951+
put
952+
curseqlen y curseqlen x get latyseg length add put
937953
/imp true def
938954
} if
939955
} forall
940956
} forall
941-
imp not {exit} if % Repeat unless no improvement
957+
imp not {exit} if
942958
} loop
943959

944-
%
945-
% Determine optimal next sequences for each valid encoding
946-
%
947960
/nxtlen [ //micropdf417.e //micropdf417.e //micropdf417.e //micropdf417.e ] def
948-
nxtseqlen 0 0 put nxtseqlen 1 0 put nxtseqlen 2 0 put nxtseqlen 3 0 put
961+
//micropdf417.textsubmodes { nxtseqhd exch null put } forall
962+
//micropdf417.textsubmodes { nxtseqlen exch 0 put } forall
949963

950964
//micropdf417.textsubmodes {
951965
/x exch def
@@ -954,37 +968,26 @@ begin
954968

955969
charvals x get char known not {exit} if
956970

957-
%
958-
% Extend directly: copy curseq[x] + char to nxtseq[x]
959-
%
971+
% Phase 2: nxtseq[x] := curseq[x] + [char]
960972
/cost curlen x get 1 add def
961973
cost nxtlen x get lt {
962974
nxtlen x cost put
963-
/srclen curseqlen x get def
964-
srclen 0 gt {
965-
nxtseq x get 0 curseq x get 0 srclen getinterval putinterval
966-
} if
967-
nxtseq x get srclen char put
968-
nxtseqlen x srclen 1 add put
975+
nxtseqhd x [ curseqhd x get [ char ] ] put
976+
nxtseqlen x curseqlen x get 1 add put
969977
} if
970978

971-
%
972-
% Optimise for direct shifts from y to x
973-
%
979+
% Phase 3: shift y -> x for one char (y != x; stays in y)
974980
//micropdf417.textsubmodes {
975981
/y exch def
976982
x y ne {
977983
/cost curlen y get //micropdf417.shftlen y get x get add 1 add def
978984
cost nxtlen y get lt {
979985
nxtlen y cost put
980-
% Copy curseq[y] + shift + char to nxtseq[y]
981-
/srclen curseqlen y get def
982-
srclen 0 gt {
983-
nxtseq y get 0 curseq y get 0 srclen getinterval putinterval
984-
} if
985-
nxtseq y get srclen x //micropdf417.a eq {//micropdf417.as} {//micropdf417.ps} ifelse put
986-
nxtseq y get srclen 1 add char put
987-
nxtseqlen y srclen 2 add put
986+
nxtseqhd y [
987+
curseqhd y get
988+
[ x //micropdf417.a eq {//micropdf417.as} {//micropdf417.ps} ifelse char ]
989+
] put
990+
nxtseqlen y curseqlen y get 2 add put
988991
} if
989992
} if
990993
} forall
@@ -995,23 +998,26 @@ begin
995998
} forall
996999

9971000
/curlen nxtlen def
998-
% Swap cur and nxt buffers
999-
curseq nxtseq /curseq exch def /nxtseq exch def
1000-
curseqlen nxtseqlen /curseqlen exch def /nxtseqlen exch def
1001+
curseqhd nxtseqhd /curseqhd exch def /nxtseqhd exch def
1002+
curseqlen nxtseqlen /curseqlen exch def /nxtseqlen exch def
10011003

10021004
} forall
10031005

10041006
%
10051007
% Select the optimal sequence
10061008
%
10071009
/minseq //micropdf417.e def
1010+
/bestsm -1 def
10081011
//micropdf417.textsubmodes {
10091012
/k exch def
10101013
curlen k get minseq lt {
10111014
/minseq curlen k get def
1012-
/txtseq curseq k get 0 curseqlen k get getinterval def
1015+
/bestsm k def
10131016
} if
10141017
} forall
1018+
bestsm -1 ne {
1019+
/txtseq curseqhd bestsm get curseqlen bestsm get flatten def
1020+
} if
10151021

10161022
%
10171023
% Encode the sequence

0 commit comments

Comments
 (0)