@@ -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