@@ -195,59 +195,114 @@ export function encodeAustraliaPost(fcc: string, dpid: string): FourState[] {
195195 return bars ;
196196}
197197
198- // Japan Post 4-State barcode
199- const JP_TABLE : Record < string , FourState [ ] > = {
200- "0" : [ "F" , "F" , "T" ] ,
201- "1" : [ "D" , "A" , "F" ] ,
202- "2" : [ "D" , "F" , "A" ] ,
203- "3" : [ "A" , "D" , "F" ] ,
204- "4" : [ "F" , "D" , "A" ] ,
205- "5" : [ "A" , "F" , "D" ] ,
206- "6" : [ "F" , "A" , "D" ] ,
207- "7" : [ "D" , "D" , "A" ] ,
208- "8" : [ "D" , "A" , "D" ] ,
209- "9" : [ "A" , "D" , "D" ] ,
210- "-" : [ "F" , "T" , "F" ] ,
211- } ;
198+ // Japan Post 4-State barcode (Kasutama / JP4SCC)
199+ // KASUT_SET defines the order of characters for bar pattern lookup in JAPAN_TABLE
200+ // KASUT_SET: '1','2','3','4','5','6','7','8','9','0','-','a','b','c','d','e','f','g','h'
201+ // CH_KASUT_SET defines the order for check digit calculation (mod 19)
202+ // CH_KASUT_SET: '0','1','2','3','4','5','6','7','8','9','-','a','b','c','d','e','f','g','h'
203+ const KASUT_SET = "1234567890-abcdefgh" ;
204+ const CH_KASUT_SET = "0123456789-abcdefgh" ;
205+
206+ // JAPAN_TABLE[i] = bar pattern for KASUT_SET[i]
207+ const JAPAN_TABLE : FourState [ ] [ ] = [
208+ [ "F" , "F" , "T" ] , // '1'
209+ [ "F" , "D" , "A" ] , // '2'
210+ [ "D" , "F" , "A" ] , // '3'
211+ [ "F" , "A" , "D" ] , // '4'
212+ [ "F" , "T" , "F" ] , // '5'
213+ [ "D" , "A" , "F" ] , // '6'
214+ [ "A" , "F" , "D" ] , // '7'
215+ [ "A" , "D" , "F" ] , // '8'
216+ [ "T" , "F" , "F" ] , // '9'
217+ [ "F" , "T" , "T" ] , // '0'
218+ [ "T" , "F" , "T" ] , // '-'
219+ [ "D" , "A" , "T" ] , // 'a'
220+ [ "D" , "T" , "A" ] , // 'b'
221+ [ "A" , "D" , "T" ] , // 'c'
222+ [ "T" , "D" , "A" ] , // 'd' (also used for padding)
223+ [ "A" , "T" , "D" ] , // 'e'
224+ [ "T" , "A" , "D" ] , // 'f'
225+ [ "T" , "T" , "F" ] , // 'g'
226+ [ "F" , "F" , "F" ] , // 'h'
227+ ] ;
228+
229+ // Build lookup from character to bar pattern
230+ const JP_TABLE : Record < string , FourState [ ] > = { } ;
231+ for ( let i = 0 ; i < KASUT_SET . length ; i ++ ) {
232+ JP_TABLE [ KASUT_SET [ i ] ! ] = JAPAN_TABLE [ i ] ! ;
233+ }
234+
235+ /**
236+ * Convert an input character to its intermediate representation for Japan Post.
237+ * Digits and hyphens pass through; letters A-Z are expanded to two-character
238+ * sequences using internal characters a-h.
239+ */
240+ function jpExpandChar ( c : string ) : string {
241+ if ( ( c >= "0" && c <= "9" ) || c === "-" ) return c ;
242+ const code = c . charCodeAt ( 0 ) ;
243+ if ( code >= 65 && code <= 74 ) {
244+ // A-J → 'a' + digit
245+ return "a" + CH_KASUT_SET [ code - 65 ] ! ;
246+ }
247+ if ( code >= 75 && code <= 84 ) {
248+ // K-T → 'b' + digit
249+ return "b" + CH_KASUT_SET [ code - 75 ] ! ;
250+ }
251+ if ( code >= 85 && code <= 90 ) {
252+ // U-Z → 'c' + digit
253+ return "c" + CH_KASUT_SET [ code - 85 ] ! ;
254+ }
255+ throw new InvalidInputError ( `Invalid Japan Post character: ${ c } ` ) ;
256+ }
212257
213258/**
214259 * Encode Japan Post 4-State Customer barcode (JP4SCC / Kasutama)
215260 *
216261 * @param zipcode - 7-digit Japanese postal code
217- * @param address - Optional address digits ( up to 13 chars)
262+ * @param address - Optional address characters (digits, dash, A-Z; up to 13 chars)
218263 */
219264export function encodeJapanPost ( zipcode : string , address ?: string ) : FourState [ ] {
220265 const zip = zipcode . replace ( / - / g, "" ) ;
221266 if ( ! / ^ \d { 7 } $ / . test ( zip ) ) {
222267 throw new InvalidInputError ( "Japan Post zipcode must be 7 digits" ) ;
223268 }
224269
225- let data = zip ;
270+ // Build intermediate representation
271+ let inter = zip ; // zipcode is always digits
226272 if ( address ) {
227- const clean = address . replace ( / \s / g, "" ) ;
228- if ( ! / ^ [ \d - ] + $ / . test ( clean ) ) {
229- throw new InvalidInputError ( "Japan Post address only accepts digits and dash" ) ;
273+ const clean = address . toUpperCase ( ) . replace ( / \s / g, "" ) ;
274+ if ( ! / ^ [ \d A - Z - ] + $ / . test ( clean ) ) {
275+ throw new InvalidInputError ( "Japan Post address only accepts digits, dash, and A-Z" ) ;
276+ }
277+ for ( const ch of clean ) {
278+ inter += jpExpandChar ( ch ) ;
230279 }
231- data += clean ;
232280 }
233281
234- while ( data . length < 20 ) data += "-" ;
235- data = data . substring ( 0 , 20 ) ;
282+ // Pad to 20 characters with 'd' and truncate
283+ while ( inter . length < 20 ) inter += "d" ;
284+ inter = inter . substring ( 0 , 20 ) ;
236285
286+ // Check digit: sum of CH_KASUT_SET positions, mod 19
237287 let sum = 0 ;
238- for ( const ch of data ) {
239- sum += ch === "-" ? 10 : Number . parseInt ( ch , 10 ) ;
288+ for ( const ch of inter ) {
289+ const pos = CH_KASUT_SET . indexOf ( ch ) ;
290+ if ( pos === - 1 ) throw new InvalidInputError ( `Invalid Japan Post character: ${ ch } ` ) ;
291+ sum += pos ;
240292 }
241- data += ( ( 10 - ( sum % 10 ) ) % 10 ) . toString ( ) ;
293+ let check = 19 - ( sum % 19 ) ;
294+ if ( check === 19 ) check = 0 ;
295+ const checkChar = CH_KASUT_SET [ check ] ! ;
296+ inter += checkChar ;
242297
243298 const bars : FourState [ ] = [ "F" , "D" ] ; // Start
244299
245- for ( const ch of data ) {
300+ for ( const ch of inter ) {
246301 const pattern = JP_TABLE [ ch ] ;
247302 if ( ! pattern ) throw new InvalidInputError ( `Invalid Japan Post character: ${ ch } ` ) ;
248303 bars . push ( ...pattern ) ;
249304 }
250305
251- bars . push ( "F " , "A " ) ; // Stop
306+ bars . push ( "D " , "F " ) ; // Stop
252307 return bars ;
253308}
0 commit comments