@@ -83,3 +83,129 @@ function randomizePad(padValue: number, position: number): number {
8383 const result = padValue + pseudoRandom ;
8484 return result <= 254 ? result : result - 254 ;
8585}
86+
87+ // C40 character set values
88+ // Set 0 (basic): space=3, 0-9=4-13, A-Z=14-39
89+ // Set 1 (shift 1): control chars 0-31
90+ // Set 2 (shift 2): !"#$%&'()*+,-./:;<=>?@[\]^_
91+ // Set 3 (shift 3): `a-z{|}~DEL
92+ function c40Value ( ch : number ) : { set : number ; value : number } {
93+ if ( ch === 32 ) return { set : 0 , value : 3 } ; // space
94+ if ( ch >= 48 && ch <= 57 ) return { set : 0 , value : ch - 48 + 4 } ; // 0-9
95+ if ( ch >= 65 && ch <= 90 ) return { set : 0 , value : ch - 65 + 14 } ; // A-Z
96+ if ( ch >= 0 && ch <= 31 ) return { set : 1 , value : ch } ; // control
97+ if ( ch >= 33 && ch <= 47 ) return { set : 2 , value : ch - 33 } ; // !"#$%&'()*+,-./
98+ if ( ch >= 58 && ch <= 64 ) return { set : 2 , value : ch - 58 + 15 } ; // :;<=>?@
99+ if ( ch >= 91 && ch <= 95 ) return { set : 2 , value : ch - 91 + 22 } ; // [\]^_
100+ if ( ch >= 96 && ch <= 127 ) return { set : 3 , value : ch - 96 } ; // `a-z{|}~
101+ return { set : - 1 , value : 0 } ; // not C40 encodable
102+ }
103+
104+ // Text mode: same as C40 but swaps upper/lowercase
105+ function textValue ( ch : number ) : { set : number ; value : number } {
106+ if ( ch === 32 ) return { set : 0 , value : 3 } ;
107+ if ( ch >= 48 && ch <= 57 ) return { set : 0 , value : ch - 48 + 4 } ;
108+ if ( ch >= 97 && ch <= 122 ) return { set : 0 , value : ch - 97 + 14 } ; // a-z in basic set
109+ if ( ch >= 0 && ch <= 31 ) return { set : 1 , value : ch } ;
110+ if ( ch >= 33 && ch <= 47 ) return { set : 2 , value : ch - 33 } ;
111+ if ( ch >= 58 && ch <= 64 ) return { set : 2 , value : ch - 58 + 15 } ;
112+ if ( ch >= 91 && ch <= 95 ) return { set : 2 , value : ch - 91 + 22 } ;
113+ if ( ch === 96 ) return { set : 3 , value : 0 } ; // backtick
114+ if ( ch >= 65 && ch <= 90 ) return { set : 3 , value : ch - 65 + 1 } ; // A-Z in shift 3
115+ if ( ch >= 123 && ch <= 127 ) return { set : 3 , value : ch - 123 + 27 } ;
116+ return { set : - 1 , value : 0 } ;
117+ }
118+
119+ /**
120+ * Encode text using C40 mode (efficient for uppercase + digits)
121+ * 3 characters → 2 codewords
122+ * Latch: codeword 230
123+ */
124+ export function encodeC40 ( text : string ) : number [ ] {
125+ return encodeC40Text ( text , 230 , c40Value ) ;
126+ }
127+
128+ /**
129+ * Encode text using Text mode (efficient for lowercase + digits)
130+ * 3 characters → 2 codewords
131+ * Latch: codeword 239
132+ */
133+ export function encodeTextMode ( text : string ) : number [ ] {
134+ return encodeC40Text ( text , 239 , textValue ) ;
135+ }
136+
137+ function encodeC40Text (
138+ text : string ,
139+ latchCW : number ,
140+ valueFn : ( ch : number ) => { set : number ; value : number } ,
141+ ) : number [ ] {
142+ const codewords : number [ ] = [ latchCW ] ; // Latch to C40/Text
143+ const values : number [ ] = [ ] ;
144+
145+ for ( let i = 0 ; i < text . length ; i ++ ) {
146+ const ch = text . charCodeAt ( i ) ;
147+ const { set, value } = valueFn ( ch ) ;
148+ if ( set === - 1 ) {
149+ // Fall back to ASCII for this character — unlatch
150+ // For simplicity, encode rest as ASCII
151+ codewords . push ( 254 ) ; // Unlatch to ASCII
152+ const remaining = text . substring ( i ) ;
153+ codewords . push ( ...encodeASCII ( remaining ) ) ;
154+ return codewords ;
155+ }
156+ if ( set > 0 ) {
157+ values . push ( set - 1 ) ; // Shift indicator (0=shift1, 1=shift2, 2=shift3)
158+ values . push ( value ) ;
159+ } else {
160+ values . push ( value ) ;
161+ }
162+ }
163+
164+ // Pack triplets into codeword pairs
165+ let i = 0 ;
166+ while ( i + 2 < values . length ) {
167+ const v = values [ i ] ! * 1600 + values [ i + 1 ] ! * 40 + values [ i + 2 ] ! + 1 ;
168+ codewords . push ( Math . floor ( v / 256 ) ) ;
169+ codewords . push ( v % 256 ) ;
170+ i += 3 ;
171+ }
172+
173+ // Handle remaining 1 or 2 values — unlatch to ASCII
174+ if ( i < values . length ) {
175+ codewords . push ( 254 ) ; // Unlatch
176+ }
177+
178+ return codewords ;
179+ }
180+
181+ /**
182+ * Auto-select best encoding mode for the given text
183+ * Returns the most efficient encoding
184+ */
185+ export function encodeAuto ( text : string ) : number [ ] {
186+ // Count uppercase vs lowercase to decide
187+ let upper = 0 ;
188+ let lower = 0 ;
189+ let digits = 0 ;
190+ for ( const ch of text ) {
191+ const c = ch . charCodeAt ( 0 ) ;
192+ if ( c >= 65 && c <= 90 ) upper ++ ;
193+ else if ( c >= 97 && c <= 122 ) lower ++ ;
194+ else if ( c >= 48 && c <= 57 ) digits ++ ;
195+ }
196+
197+ // C40 is best for uppercase-heavy, Text for lowercase-heavy
198+ const asciiCW = encodeASCII ( text ) ;
199+
200+ if ( upper + digits > text . length * 0.6 ) {
201+ const c40CW = encodeC40 ( text ) ;
202+ if ( c40CW . length < asciiCW . length ) return c40CW ;
203+ }
204+
205+ if ( lower + digits > text . length * 0.6 ) {
206+ const textCW = encodeTextMode ( text ) ;
207+ if ( textCW . length < asciiCW . length ) return textCW ;
208+ }
209+
210+ return asciiCW ;
211+ }
0 commit comments