Skip to content

Commit 355bb5e

Browse files
committed
azteccode: Avoid O(n^2) sequence copying in encoder
1 parent 3de0d6a commit 355bb5e

1 file changed

Lines changed: 100 additions & 98 deletions

File tree

src/azteccode.ps.src

Lines changed: 100 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -475,37 +475,52 @@ begin
475475
/e //azteccode.e def
476476

477477
%
478-
% Each char adds 1, latches add up to 3
478+
% Codeword sequences are held as persistent chains of segments. Each
479+
% chain head is null or a node [parent_node, segment_array]; the final
480+
% sequence is materialised once by walking the chain. For the B-mode
481+
% run-length check we keep a counter rather than rescan the chain.
479482
%
480-
/maxseqlen msglen 4 mul def
481-
482-
/seq0 [ 6 { maxseqlen { array } stopped {
483-
pop /bwipp.azteccodeInputTooLarge (The input data exceeds the implementation limits) //raiseerror exec
484-
} if } repeat ] def
485-
/seq1 [ 6 { maxseqlen { array } stopped {
486-
pop /bwipp.azteccodeInputTooLarge (The input data exceeds the implementation limits) //raiseerror exec
487-
} if } repeat ] def
488-
/len0 6 array def 0 1 5 { len0 exch 0 put } for
489-
/len1 6 array def
490-
491-
/curseq seq0 def /curseqlen len0 def
492-
/nxtseq seq1 def /nxtseqlen len1 def
483+
/curseqhd [ //azteccode.states length { null } repeat ] def
484+
/nxtseqhd [ //azteccode.states length { null } repeat ] def
485+
/curseqlen [ //azteccode.states length { 0 } repeat ] def
486+
/nxtseqlen [ //azteccode.states length { 0 } repeat ] def
487+
/curseqbb 0 def /nxtseqbb 0 def
493488

494489
% U L M P D B
495490
/curlen [ 0 e e e e e ] def
496491

497-
/backto //azteccode.u def % U
492+
/backto //azteccode.u def
498493
/lastchar () def
499494

500-
% Derive the optimal sequences ending in each state
495+
%
496+
% Flatten a chain of [parent, segment] cons cells of total length L
497+
% into a fresh array. The chain's deepest node holds the prefix, so
498+
% we walk head-to-root and write each segment into the tail of the
499+
% output, advancing pos backwards.
500+
%
501+
/flatten { % hd len -> array
502+
503+
dup array 3 1 roll % pos = len; out = array(len)
504+
505+
{ % out hd pos
506+
1 index null eq { exit } if % done when hd is null
507+
1 index 1 get % ... seg = hd[1]
508+
exch 1 index length sub % ... pos -= length(seg)
509+
3 index 1 index 3 index putinterval % out[pos..] = seg
510+
exch pop % drop seg
511+
exch 0 get exch % hd = hd[0]
512+
} loop
513+
514+
pop pop % return out
515+
516+
} def
517+
501518
msg {
502519

503520
/char exch def
504521

505-
%
506-
% Check for optimisations in the current sequences by latching from x to y
507-
%
508-
{ % loop
522+
% Phase 1: relax curlen[*] via the static latch graph
523+
{
509524
/imp false def
510525
//azteccode.states {
511526
/x exch def
@@ -515,38 +530,39 @@ begin
515530
/cost curlen x get //azteccode.latlen x get y get add def
516531
cost curlen y get lt {
517532
curlen y cost put
518-
% Copy curseq[x] to curseq[y] and append latseq codes
519-
/srclen curseqlen x get def
520-
srclen 0 gt {
521-
curseq y get 0 curseq x get 0 srclen getinterval putinterval
522-
} if
523-
//azteccode.latseq x get y get {
524-
curseq y get srclen 3 -1 roll put
525-
/srclen srclen 1 add def
526-
} forall
527-
curseqlen y srclen put
528-
y //azteccode.b eq { % Set backto to previous state
533+
/latyseg //azteccode.latseq x get y get def
534+
curseqhd y
535+
latyseg length 0 eq
536+
{ curseqhd x get }
537+
{ [ curseqhd x get latyseg ] }
538+
ifelse
539+
put
540+
curseqlen y curseqlen x get latyseg length add put
541+
y //azteccode.b eq {
542+
% Recompute B-chain byte-count: latseg may contain sb
543+
/nb 0 def
544+
latyseg { //azteccode.sb eq {0} {nb 1 add} ifelse /nb exch def } forall
545+
/curseqbb nb def
529546
/backto x //azteccode.p eq x //azteccode.d eq or {//azteccode.u} {x} ifelse def
530547
} if
531548
/imp true def
532549
} if
533550
} if
534551
} forall
535552
} forall
536-
imp not {exit} if % Repeat unless no improvement
553+
imp not {exit} if
537554
} loop
538555

539-
%
540-
% Determine optimal next sequences for each valid encoding
541-
%
542556
% U L M P D B
543557
/nxtlen [ e e e e e e ] def
544-
0 1 5 { nxtseqlen exch 0 put } for
558+
//azteccode.states { nxtseqhd exch null put } forall
559+
//azteccode.states { nxtseqlen exch 0 put } forall
560+
/nxtseqbb 0 def
545561

546562
//azteccode.states {
547563
/x exch def
548564

549-
{ % loop for common exit
565+
{ % loop for common exit
550566

551567
%
552568
% Skip states that cannot encode character
@@ -557,39 +573,30 @@ begin
557573
x //azteccode.p ne {exit} if % Only P can encode FNC1 and ECI
558574
} ifelse
559575

560-
%
561-
% Extend directly: copy curseq[x] + char to nxtseq[x]
562-
%
576+
% Phase 2: nxtseq[x] := curseq[x] + [char]
563577
/cost curlen x get x char charsize add def
564578
cost nxtlen x get lt {
565579
nxtlen x cost put
566-
/srclen curseqlen x get def
567-
srclen 0 gt {
568-
nxtseq x get 0 curseq x get 0 srclen getinterval putinterval
569-
} if
570-
nxtseq x get srclen char put
571-
nxtseqlen x srclen 1 add put
580+
nxtseqhd x [ curseqhd x get [ char ] ] put
581+
nxtseqlen x curseqlen x get 1 add put
582+
x //azteccode.b eq { /nxtseqbb curseqbb 1 add def } if
572583
} if
573584

574-
%
575-
% Optimise for direct shifts from y to x
576-
%
577585
x //azteccode.b eq {exit} if % B is treated as a latch
586+
587+
% Phase 3: shift y -> x for one char (y != x, y != B; stays in y)
578588
//azteccode.states {
579589
/y exch def
580590
y //azteccode.b eq {exit} if
581591
x y ne {
582592
/cost curlen y get //azteccode.shftlen y get x get add x char charsize add def
583593
cost nxtlen y get lt {
584594
nxtlen y cost put
585-
% Copy curseq[y] + shift + char to nxtseq[y]
586-
/srclen curseqlen y get def
587-
srclen 0 gt {
588-
nxtseq y get 0 curseq y get 0 srclen getinterval putinterval
589-
} if
590-
nxtseq y get srclen x //azteccode.u eq {//azteccode.su} {//azteccode.sp} ifelse put
591-
nxtseq y get srclen 1 add char put
592-
nxtseqlen y srclen 2 add put
595+
nxtseqhd y [
596+
curseqhd y get
597+
[ x //azteccode.u eq {//azteccode.su} {//azteccode.sp} ifelse char ]
598+
] put
599+
nxtseqlen y curseqlen y get 2 add put
593600
} if
594601
} if
595602
} forall
@@ -600,7 +607,8 @@ begin
600607
} forall
601608

602609
%
603-
% Optimise using P compression
610+
% Phase 4: P compression. Falls back to materialising the candidate
611+
% chain and rebuilding as a single segment.
604612
%
605613
lastchar () ne char 0 ge and {
606614
/pchars 2 string dup 0 lastchar put dup 1 char put def
@@ -618,84 +626,74 @@ begin
618626
} ifelse
619627
/curseqilen curseqlen i get def
620628
inP curlen i get nxtlen i get lt and {
629+
/curseqi_arr curseqhd i get curseqilen flatten def
621630
/lastld false def
622631
/lastsp false def
623632
/lastidx -1 def
624633
curseqilen 1 sub -1 0 { % Search backwards for lastchar
625634
/idx exch def
626-
/ch curseq i get idx get def
635+
/ch curseqi_arr idx get def
627636
lastidx -1 eq {
628637
ch lastchar eq {
629638
/lastidx idx def
630639
idx 0 gt {
631-
curseq i get idx 1 sub get //azteccode.sp eq { /lastsp true def } if % Preceded by P/S
640+
curseqi_arr idx 1 sub get //azteccode.sp eq { /lastsp true def } if
632641
} if
633642
} if
634643
} { % Found lastchar, check latch
635-
ch //azteccode.sb eq { % In B/S, do nothing
644+
ch //azteccode.sb eq {
636645
/lastidx -1 def
637646
exit
638647
} if
639-
ch 0 lt ch //azteccode.ld ge and { % If have latch
648+
ch 0 lt ch //azteccode.ld ge and {
640649
i //azteccode.p eq {
641-
ch //azteccode.ld eq { /lastld true def } if % Set flag if D/L for adjustment below
650+
ch //azteccode.ld eq { /lastld true def } if
642651
} {
643-
ch //azteccode.lp ne { /inP lastsp def } if % If not P/L only in P if have P/S
652+
ch //azteccode.lp ne { /inP lastsp def } if
644653
} ifelse
645654
exit
646655
} if
647656
} ifelse
648657
} for
649658
inP lastidx 0 ge and {
650659
nxtlen i curlen i get put
651-
lastidx curseqilen 1 sub lt { % If lastchar not at end of sequence
660+
/newseg curseqilen array def
661+
lastidx curseqilen 1 sub lt {
652662
i //azteccode.p eq {
653-
lastld { nxtlen i nxtlen i get 1 add put } if % Adjust count if D/L
654-
% Copy curseq[i], move lastchar to end and replace with pcomp
655-
/srclen curseqilen def
656-
nxtseq i get 0 curseq i get 0 lastidx getinterval putinterval
657-
nxtseq i get lastidx curseq i get lastidx 1 add srclen lastidx sub 1 sub getinterval putinterval
658-
nxtseq i get srclen 1 sub //azteccode.pcomp pchars get put
659-
nxtseqlen i srclen put
663+
lastld { nxtlen i 2 copy get 1 add put } if
664+
% move lastchar to end and replace with pcomp
665+
newseg 0 curseqi_arr 0 lastidx getinterval putinterval
666+
newseg lastidx curseqi_arr lastidx 1 add curseqilen lastidx sub 1 sub getinterval putinterval
667+
newseg curseqilen 1 sub //azteccode.pcomp pchars get put
660668
} {
661-
% Copy curseq[i] and replace lastchar in situ
662-
curseqilen 0 gt {
663-
nxtseq i get 0 curseq i get 0 curseqilen getinterval putinterval
664-
} if
665-
nxtseq i get lastidx //azteccode.pcomp pchars get put
666-
nxtseqlen i curseqilen put
669+
% replace lastchar in situ
670+
newseg 0 curseqi_arr putinterval
671+
newseg lastidx //azteccode.pcomp pchars get put
667672
} ifelse
668673
} {
669-
% Copy curseq[i] except last element, append pcomp
670-
/srclen curseqilen 1 sub def
671-
srclen 0 gt {
672-
nxtseq i get 0 curseq i get 0 srclen getinterval putinterval
674+
% drop last element, append pcomp
675+
curseqilen 1 gt {
676+
newseg 0 curseqi_arr 0 curseqilen 1 sub getinterval putinterval
673677
} if
674-
nxtseq i get srclen //azteccode.pcomp pchars get put
675-
nxtseqlen i srclen 1 add put
678+
newseg curseqilen 1 sub //azteccode.pcomp pchars get put
676679
} ifelse
680+
nxtseqhd i [ null newseg ] put
681+
nxtseqlen i curseqilen put
677682
} if
678683
} if
679684
} forall
680685
} if
681686
} if
682687

683-
%
684-
% Account for binary extension for 32 or more bytes
685-
%
686-
nxtseqlen //azteccode.b get 0 gt {
687-
/numbytes 0 def
688-
0 1 nxtseqlen //azteccode.b get 1 sub {
689-
nxtseq //azteccode.b get exch get
690-
//azteccode.sb eq {0} {numbytes 1 add} ifelse /numbytes exch def
691-
} for
692-
numbytes 32 eq {nxtlen //azteccode.b nxtlen //azteccode.b get 11 add put} if
688+
% Phase 5: binary extension overhead (11-bit length field at 32 bytes)
689+
nxtseqbb 32 eq {
690+
nxtlen //azteccode.b 2 copy get 11 add put
693691
} if
694692

695693
/curlen nxtlen def
696-
% Swap cur and nxt buffers
697-
curseq nxtseq /curseq exch def /nxtseq exch def
698-
curseqlen nxtseqlen /curseqlen exch def /nxtseqlen exch def
694+
curseqhd nxtseqhd /curseqhd exch def /nxtseqhd exch def
695+
curseqlen nxtseqlen /curseqlen exch def /nxtseqlen exch def
696+
/curseqbb nxtseqbb def
699697
/lastchar char 0 ge {char} {()} ifelse def
700698

701699
} forall
@@ -704,13 +702,17 @@ begin
704702
% Select the optimal sequence
705703
%
706704
/minseq //azteccode.e def
705+
/bestst -1 def
707706
//azteccode.states {
708707
/i exch def
709708
curlen i get minseq lt {
710709
/minseq curlen i get def
711-
/seq curseq i get 0 curseqlen i get getinterval def
710+
/bestst i def
712711
} if
713712
} forall
713+
bestst -1 ne {
714+
/seq curseqhd bestst get curseqlen bestst get flatten def
715+
} if
714716

715717
%
716718
% Encoding functions

0 commit comments

Comments
 (0)