postnet.ss
;;; @Package     postnet
;;; @Subtitle    USPS POSTNET Barcode Encoding in Scheme
;;; @HomePage    http://www.neilvandyke.org/postnet-scheme/
;;; @Author      Neil Van Dyke
;;; @Version     0.3
;;; @Date        2009-03-03
;;; @PLaneT      neil/postnet:1:0

;; $Id: postnet.ss,v 1.15 2009/03/03 12:51:12 neilpair Exp $

;;; @legal
;;; Copyright @copyright{} 2004--2009 Neil Van Dyke.  This program is Free
;;; Software; you can redistribute it and/or modify it under the terms of the
;;; GNU Lesser General Public License as published by the Free Software
;;; Foundation; either version 3 of the License (LGPL 3), or (at your option)
;;; any later version.  This program is distributed in the hope that it will be
;;; useful, but without any warranty; without even the implied warranty of
;;; merchantability or fitness for a particular purpose.  See
;;; @indicateurl{http://www.gnu.org/licenses/} for details.  For other licenses
;;; and consulting, please contact the author.
;;; @end legal

#lang scheme/base

;;; @section Introduction

;;; This @b{postnet} Scheme library can be used to generate the United States
;;; Postal Service POSTNET barcode binary pattern, as described in Chapter 4 of
;;; USPS Publication 25, ``Designing Letter and Reply Mail,'' dated 2003-06.
;;; This library supports ZIP, ZIP+4, and DBPC codes.
;;;
;;; The generated binary representation is a Scheme list of boolean values.
;;; Separate code must be written to render this representation to a particular
;;; printing devices.

;;; @section Encoding

;;; There are several procedures for encoding POSTNET from various input
;;; representations.  It is suspected that most applications will encode from a
;;; string representation, using @code{string->postnet}.

;;; @defproc digit-list->postnet digits
;;;
;;; Yields a POSTNET encoding of a list of digit numbers.  For example:
;;;
;;; @lisp
;;; (digit-list->postnet '(1 2 3 4 5))
;;; @result{}
;;; (#t #f #f #f #t #t #f #f #t #f #t #f #f #t #t #f
;;;  #f #t #f #f #t #f #t #f #t #f #f #t #f #t #f #t)
;;; @end lisp

(define digit-list->postnet
  (let ((digit->postnet-bars
         (let ((postnet-digit-bars-vector
                '#((#t #t #f #f #f)
                   (#f #f #f #t #t)
                   (#f #f #t #f #t)
                   (#f #f #t #t #f)
                   (#f #t #f #f #t)
                   (#f #t #f #t #f)
                   (#f #t #t #f #f)
                   (#t #f #f #f #t)
                   (#t #f #f #t #f)
                   (#t #f #t #f #f))))
           (lambda (digit)
             (if (and (integer? digit) (<= 0 digit 9))
                 (vector-ref postnet-digit-bars-vector digit)
                 '())))))
    (lambda (digits)
      (let ((sum 0))
        (cons #t
              (let loop ((digits digits))
                (if (null? digits)
                    (append (digit->postnet-bars (- 10 (modulo sum 10)))
                            (list #t))
                    (let* ((d    (car digits))
                           (bars (digit->postnet-bars d)))
                      (if (null? bars)
                          (loop (cdr digits))
                          (begin (set! sum (+ sum d))
                                 (append bars (loop (cdr digits)))))))))))))

;;; @defproc char-list->postnet chars
;;;
;;; Yields a POSTNET encoding of a list of characters.  For example:
;;;
;;; @lisp
;;; (char-list->postnet '(#\1 #\2 #\3 #\4 #\5))
;;; @result{}
;;; (#t #f #f #f #t #t #f #f #t #f #t #f #f #t #t #f
;;;  #f #t #f #f #t #f #t #f #t #f #f #t #f #t #f #t)
;;; @end lisp

(define (char-list->postnet chars)
  (digit-list->postnet (map (lambda (c)
                              (- (char->integer c) 48))
                            chars)))

;;; @defproc string->postnet str
;;;
;;; Yields a POSTNET encoding of a string.  For example:
;;;
;;; @lisp
;;; (string->postnet "12345-6789 01")
;;; @result{}
;;; (#t #f #f #f #t #t #f #f #t #f #t #f #f #t #t #f
;;;  #f #t #f #f #t #f #t #f #t #f #f #t #t #f #f #t
;;;  #f #f #f #t #t #f #f #t #f #t #f #t #f #f #t #t
;;;  #f #f #f #f #f #f #t #t #f #t #f #f #t #t)
;;; @end lisp

(define (string->postnet str)
  (char-list->postnet (string->list str)))

;;; @section Debugging

;;; Although this package does not render POSTNET for OCR use, for debugging
;;; purposes, the @code{postnet->debug-string} procedure can be used to
;;; approximate the barcode visually, using ASCII characters.

;;; @defproc postnet->debug-string postnet
;;;
;;; Yields a string visual approximation of a POSTNET encoding.  For example:
;;;
;;; @lisp
;;; (postnet->debug-string (string->postnet "12345"))
;;; @result{} "|...||..|.|..||..|..|.|.|..|.|.|"
;;; @end lisp

(define (postnet->debug-string postnet)
  (apply string (map (lambda (bar) (if bar #\| #\.)) postnet)))

;;; @unnumberedsec History

;;; @table @asis
;;;
;;; @item Version 0.3 --- 2009-03-03 -- PLaneT @code{(1 0)}
;;; Licensed changed to LGPL 3.  Converted to author's new Scheme
;;; administration system.  Previous version was erroneously in planet as
;;; package @code{postnet-planet.plt}.
;;;
;;; @item Version 0.2 --- 2005-07-12
;;; Added Testeez tests.  Minor documentation changes.
;;;
;;; @item Version 0.1 --- 2004-04-14
;;; Wrote as a diversion over dinner.
;;;
;;; @end table

(provide
 char-list->postnet
 digit-list->postnet
 postnet->debug-string
 string->postnet)