postnet.scm
;;; @Package     postnet.scm
;;; @Subtitle    USPS POSTNET Barcode Encoding in Scheme
;;; @HomePage    http://www.neilvandyke.org/postnet-scm/
;;; @Author      Neil W. Van Dyke
;;; @AuthorEmail neil@@neilvandyke.org
;;; @Version     0.2
;;; @Date        2005-07-12

;; $Id: postnet.scm,v 1.10 2005/07/12 16:27:15 neil Exp $

;;; @legal
;;; Copyright @copyright{} 2004 - 2005 Neil W. 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 2.1 of the License, 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/copyleft/lesser.html} for details.  For
;;; other license options and consulting, contact the author.
;;; @end legal

(define-syntax %postnet:testeez
  (syntax-rules () ((_ X ...)
                    ;; (testeez X ...)
                    (error "Tests disabled.")
                    )))

;;; @section Introduction

;;; This @code{postnet.scm} Scheme library can be used to generate the United
;;; States Postal Service POSTNET barcode binary pattern, as described in
;;; Chapter 4 of @uref{http://pe.usps.gov/text/Pub25/Pub25.htm, USPS
;;; Publication 25@comma{} ``Designing Letter and Reply Mail@comma{}''} 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)))

;;; @section Tests

;;; The @code{postnet.scm} test suite can be enabled by editing the source code
;;; file and loading @uref{http://www.neilvandyke.org/testeez/, Testeez}.

(define (%postnet:test)
  (%postnet:testeez
   "postnet.scm"

   (test/equal ""
               (digit-list->postnet '(1 2 3 4 5))
               '(#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))

   (test/equal ""
               (char-list->postnet '(#\1 #\2 #\3 #\4 #\5))
               '(#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))

   (test/equal ""
               (string->postnet "12345-6789 01")
               '(#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))

   (test/equal ""
               (postnet->debug-string (string->postnet "12345"))
               "|...||..|.|..||..|..|.|.|..|.|.|")

   ))

;;; @unnumberedsec History

;;; @table @asis
;;;
;;; @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