add support for DataMatrix; version --> 3.5.52
authorrobin
Thu, 01 Oct 2020 18:28:19 +0100
changeset 4608 25c3638c0330
parent 4607 3c4898979adc
child 4609 45bd9edde3c8
add support for DataMatrix; version --> 3.5.52
CHANGES.md
src/reportlab/__init__.py
src/reportlab/graphics/barcode/__init__.py
src/reportlab/graphics/barcode/common.py
src/reportlab/graphics/barcode/dmtx.py
src/reportlab/graphics/barcode/test.py
src/reportlab/lib/validators.py
tests/test_graphics_barcode.py
--- a/CHANGES.md	Thu Sep 24 14:07:02 2020 +0100
+++ b/CHANGES.md	Thu Oct 01 18:28:19 2020 +0100
@@ -11,6 +11,10 @@
 The contributors lists are in no order and apologies to those accidentally not
 mentioned. If we missed you, please let us know!
 
+RELEASE 3.5.52	 01/10/2020
+---------------------------
+	* add support for DataMatrix barcode
+
 RELEASE 3.5.51	 24/09/2020
 ---------------------------
 	* fix malloc(0) issue in \_rl_accel.c \_fp_str thanks to Hans-Peter Jansen <hpj@urpla.net> @ openSUSE
--- a/src/reportlab/__init__.py	Thu Sep 24 14:07:02 2020 +0100
+++ b/src/reportlab/__init__.py	Thu Oct 01 18:28:19 2020 +0100
@@ -1,9 +1,9 @@
 #Copyright ReportLab Europe Ltd. 2000-2018
 #see license.txt for license details
 __doc__="""The Reportlab PDF generation library."""
-Version = "3.5.51"
+Version = "3.5.52"
 __version__=Version
-__date__='20200924'
+__date__='20201001'
 
 import sys, os
 
--- a/src/reportlab/graphics/barcode/__init__.py	Thu Sep 24 14:07:02 2020 +0100
+++ b/src/reportlab/graphics/barcode/__init__.py	Thu Oct 01 18:28:19 2020 +0100
@@ -69,6 +69,10 @@
                 BarcodeECC200DataMatrix,
                 ):
         registerWidget(widget)
+        from reportlab.graphics.barcode import dmtx
+        if dmtx.pylibdmtx:
+            registerWidget(dmtx.DataMatrixWidget)
+
 _reset()
 from reportlab.rl_config import register_reset
 register_reset(_reset)
--- a/src/reportlab/graphics/barcode/common.py	Thu Sep 24 14:07:02 2020 +0100
+++ b/src/reportlab/graphics/barcode/common.py	Thu Oct 01 18:28:19 2020 +0100
@@ -177,6 +177,12 @@
         getattr(canv,func)(x,y,text)
         canv.restoreState()
 
+    def _checkVal(self, name, v, allowed):
+        if v not in allowed:
+            raise ValueError('%s attribute %s is invalid %r\nnot in allowed %r' % (
+                self.__class__.__name__, name, v, allowed))
+        return v
+
 class MultiWidthBarcode(Barcode):
     """Base for variable-bar-width codes like Code93 and Code128"""
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/reportlab/graphics/barcode/dmtx.py	Thu Oct 01 18:28:19 2020 +0100
@@ -0,0 +1,273 @@
+try:
+    from pylibdmtx import pylibdmtx
+except ImportError:
+    pylibdmtx = None
+    __all__ = ()
+else:
+    __all__=('DataMatrix',)
+
+from reportlab.graphics.barcode.common import Barcode
+from reportlab.lib.utils import asBytes
+from reportlab.platypus.paraparser import _num as paraparser_num
+from reportlab.graphics.widgetbase import Widget
+from reportlab.lib.validators import isColor, isString, isColorOrNone, isNumber, isBoxAnchor
+from reportlab.lib.attrmap import AttrMap, AttrMapValue
+from reportlab.lib.colors import toColor
+from reportlab.graphics.shapes import Group, Rect
+
+def _numConv(x):
+    return x if isinstance(x,(int,float)) else paraparser_num(x)
+
+class _DMTXCheck(object):
+    @classmethod
+    def pylibdmtx_check(cls):
+        if not pylibdmtx:
+            raise ValueError('The %s class requires package pylibdmtx' % cls.__name__)
+
+class DataMatrix(Barcode,_DMTXCheck):
+    def __init__(self, value='', **kwds):
+        self.pylibdmtx_check()
+        self._recalc = True
+        self.value = value
+        self.cellSize = kwds.pop('cellSize','5x5')
+        self.size = kwds.pop('size','SquareAuto')
+        self.encoding = kwds.pop('encoding','Ascii')
+        self.anchor = kwds.pop('anchor','sw')
+        self.color = kwds.pop('color',(0,0,0))
+        self.bgColor = kwds.pop('bgColor',None)
+        self.x = kwds.pop('x',0)
+        self.y = kwds.pop('y',0)
+        self.border = kwds.pop('border',5)
+
+    @property
+    def value(self):
+        return self._value
+
+    @value.setter
+    def value(self,v):
+        self._value = asBytes(v)
+        self._recalc = True
+
+    @property
+    def size(self):
+        return self._size
+
+    @size.setter
+    def size(self,v):
+        self._size = self._checkVal('size', v, pylibdmtx.ENCODING_SIZE_NAMES)
+        self._recalc = True
+
+    @property
+    def border(self):
+        return self._border
+
+    @border.setter
+    def border(self,v):
+        self._border = _numConv(v)
+        self._recalc = True
+
+    @property
+    def x(self):
+        return self._x
+
+    @x.setter
+    def x(self,v):
+        self._x = _numConv(v)
+        self._recalc = True
+
+    @property
+    def y(self):
+        return self._y
+
+    @y.setter
+    def y(self,v):
+        self._y = _numConv(v)
+        self._recalc = True
+
+    @property
+    def cellSize(self):
+        return self._cellSize
+
+    @size.setter
+    def cellSize(self,v):
+        self._cellSize = v
+        self._recalc = True
+
+    @property
+    def encoding(self):
+        return self._encoding
+
+    @encoding.setter
+    def encoding(self,v):
+        self._encoding = self._checkVal('encoding', v, pylibdmtx.ENCODING_SCHEME_NAMES)
+        self._recalc = True
+
+    @property
+    def anchor(self):
+        return self._anchor
+
+    @anchor.setter
+    def anchor(self,v):
+        self._anchor = self._checkVal('anchor', v, ('n','ne','e','se','s','sw','w','nw','c'))
+        self._recalc = True
+
+    def recalc(self):
+        if not self._recalc: return
+        data = self._value
+        size = self._size
+        encoding = self._encoding
+        e = pylibdmtx.encode(data, size=size, scheme=encoding)
+        iW = e.width
+        iH = e.height
+        p = e.pixels
+        iCellSize = 5
+        bpp = 3 #bytes per pixel
+        rowLen = iW*bpp
+        cellLen = iCellSize*bpp
+        assert len(p)//rowLen == iH
+        matrix = list(filter(None,
+                            (''.join(
+                                (('x' if p[j:j+bpp] != b'\xff\xff\xff' else ' ')
+                                for j in range(i,i+rowLen,cellLen))).strip()
+                            for i in range(0,iH*rowLen,rowLen*iCellSize))))
+        self._nRows = len(matrix)
+        self._nCols = len(matrix[-1])
+        self._matrix = '\n'.join(matrix)
+
+        cellWidth = self._cellSize
+        if cellWidth:
+            cellWidth = cellWidth.split('x')
+            if len(cellWidth)>2:
+                raise ValueError('cellSize needs to be distance x distance not %r' % self._cellSize)
+            elif len(cellWidth)==2:
+                cellWidth, cellHeight = cellWidth
+            else:
+                cellWidth = cellHeight = cellWidth[0]
+            cellWidth = _numConv(cellWidth)
+            cellHeight = _numConv(cellHeight)
+        else:
+            cellWidth = cellHeight = iCellSize
+        self._cellWidth = cellWidth
+        self._cellHeight = cellHeight
+        self._recalc = False
+        self._bord = max(self.border,cellWidth,cellHeight)
+        self._width = cellWidth*self._nCols + 2*self._bord
+        self._height = cellHeight*self._nRows + 2*self._bord
+
+    @property
+    def matrix(self):
+        self.recalc()
+        return self._matrix
+
+    @property
+    def width(self):
+        self.recalc()
+        return self._width
+
+    @property
+    def height(self):
+        self.recalc()
+        return self._height
+
+    @property
+    def cellWidth(self):
+        self.recalc()
+        return self._cellWidth
+
+    @property
+    def cellHeight(self):
+        self.recalc()
+        return self._cellHeight
+
+    def draw(self):
+        self.recalc()
+        canv = self.canv
+        w = self.width
+        h = self.height
+        x = self.x
+        y = self.y
+        b = self._bord
+
+        anchor = self.anchor
+        if anchor in ('nw','n','ne'):
+            y -= h
+        elif anchor in ('c','e','w'):
+            y -= h//2
+        if anchor in ('ne','e','se'):
+            x -= w
+        elif anchor in ('n','c','s'):
+            x -= w//2
+
+        canv.saveState()
+        if self.bgColor:
+            canv.setFillColor(toColor(self.bgColor))
+            canv.rect(x, y-h, w, h, fill=1, stroke=0)
+        canv.setFillColor(toColor(self.color))
+        canv.setStrokeColor(None)
+
+        cellWidth = self.cellWidth
+        cellHeight = self.cellHeight
+        yr = y - b - cellHeight
+        x += b
+        for row in self.matrix.split('\n'):
+            xr = x 
+            for c in row:
+                if c=='x':
+                    canv.rect(xr, yr, cellWidth, cellHeight, fill=1, stroke=0)
+                xr += cellWidth
+            yr -= cellHeight
+        canv.restoreState()
+    
+
+class DataMatrixWidget(Widget,_DMTXCheck):
+    codeName = "DataMatrix"
+    _attrMap = AttrMap(
+        BASE = Widget,
+        value = AttrMapValue(isString, desc='Datamatrix data'),
+        x = AttrMapValue(isNumber, desc='x-coord'),
+        y = AttrMapValue(isNumber, desc='y-coord'),
+        color = AttrMapValue(isColor, desc='foreground color'),
+        bgColor = AttrMapValue(isColorOrNone, desc='background color'),
+        encoding = AttrMapValue(isString, desc='encoding'),
+        size = AttrMapValue(isString, desc='size'),
+        cellSize = AttrMapValue(isString, desc='cellSize'),
+        anchor = AttrMapValue(isBoxAnchor, desc='anchor pooint for x,y'),
+        )
+
+    _defaults = dict(
+        x = ('0',_numConv),
+        y = ('0',_numConv),
+        color = ('black',toColor),
+        bgColor = (None,lambda _: toColor(_) if _ is not None else _),
+        encoding = ('Ascii',None),
+        size = ('SquareAuto',None),
+        cellSize = ('5x5',None),
+        anchor = ('sw', None),
+        )
+    def __init__(self,value='Hello Cruel World!', **kwds):
+        self.pylibdmtx_check()
+        self.value = value
+        for k,(d,c) in self._defaults.items():
+            v = kwds.pop(k,d)
+            if c: v = c(v)
+            setattr(self,k,v)
+
+    def rect(self, x, y, w, h, fill=1, stroke=0):
+        self._gadd(Rect(x,y,w,h,strokeColor=None,fillColor=self._fillColor))
+
+    def saveState(self,*args,**kwds):
+        pass
+
+    restoreState = setStrokeColor = saveState
+
+    def setFillColor(self,c):
+        self._fillColor = c
+
+    def draw(self):
+        m = DataMatrix(value=self.value,**{k: getattr(self,k) for k in self._defaults})
+        m.canv = self
+        m.y += m.height
+        g = Group()
+        self._gadd = g.add
+        m.draw()
+        return g
--- a/src/reportlab/graphics/barcode/test.py	Thu Sep 24 14:07:02 2020 +0100
+++ b/src/reportlab/graphics/barcode/test.py	Thu Oct 01 18:28:19 2020 +0100
@@ -10,6 +10,7 @@
 from reportlab.graphics.barcode.usps import *
 from reportlab.graphics.barcode.usps4s import USPS_4State
 from reportlab.graphics.barcode.qr import QrCodeWidget
+from reportlab.graphics.barcode.dmtx import DataMatrixWidget, pylibdmtx
 
 
 from reportlab.platypus import Spacer, SimpleDocTemplate, Table, TableStyle, Preformatted, PageBreak
@@ -114,6 +115,11 @@
     storyAdd(Paragraph('QR', styleN))
     storyAdd(createBarcodeDrawing('QR', value='01234567094987654321',x=30,y=50))
 
+    def addCross(d,x,y,w=5,h=5, strokeColor='black', strokeWidth=0.5):
+        w *= 0.5
+        h *= 0.5
+        d.add(Line(x-w,y,x+w,y,strokeWidth=0.5,strokeColor=colors.blue))
+        d.add(Line(x, y-h, x, y+h,strokeWidth=0.5,strokeColor=colors.blue))
     storyAdd(Paragraph('QR in drawing at (0,0)', styleN))
     d = Drawing(100,100)
     d.add(Rect(0,0,100,100,strokeWidth=1,strokeColor=colors.red,fillColor=None))
@@ -123,8 +129,7 @@
     storyAdd(Paragraph('QR in drawing at (10,10)', styleN))
     d = Drawing(100,100)
     d.add(Rect(0,0,100,100,strokeWidth=1,strokeColor=colors.red,fillColor=None))
-    d.add(Line(7.5,10,12.5,10,strokeWidth=0.5,strokeColor=colors.blue))
-    d.add(Line(10,7.5, 10, 12.5,strokeWidth=0.5,strokeColor=colors.blue))
+    addCross(d,10,10)
     d.add(QrCodeWidget(value='01234567094987654321',x=10,y=10))
     storyAdd(d)
 
@@ -133,6 +138,28 @@
 
     storyAdd(Paragraph('Label Size', styleN))
     storyAdd(XBox((1.75)*inch, .5 * inch, '1/2x1-3/4"'))
+
+    if pylibdmtx:
+        storyAdd(PageBreak())
+        storyAdd(Paragraph('DataMatrix in drawing at (10,10)', styleN))
+        d = Drawing(100,100)
+        d.add(Rect(0,0,100,100,strokeWidth=1,strokeColor=colors.red,fillColor=None))
+        addCross(d,10,10)
+        d.add(DataMatrixWidget(value='1234567890',x=10,y=10))
+        storyAdd(d)
+        storyAdd(Paragraph('DataMatrix in drawing at (10,10)', styleN))
+        d = Drawing(100,100)
+        d.add(Rect(0,0,100,100,strokeWidth=1,strokeColor=colors.red,fillColor=None))
+        addCross(d,10,10)
+        d.add(DataMatrixWidget(value='1234567890',x=10,y=10,color='black',bgColor='lime'))
+        storyAdd(d)
+
+        storyAdd(Paragraph('DataMatrix in drawing at (90,90) anchor=ne', styleN))
+        d = Drawing(100,100)
+        d.add(Rect(0,0,100,100,strokeWidth=1,strokeColor=colors.red,fillColor=None))
+        addCross(d,90,90)
+        d.add(DataMatrixWidget(value='1234567890',x=90,y=90,color='darkblue',bgColor='yellow', anchor='ne'))
+        storyAdd(d)
     
 
     SimpleDocTemplate('out.pdf').build(story)
--- a/src/reportlab/lib/validators.py	Thu Sep 24 14:07:02 2020 +0100
+++ b/src/reportlab/lib/validators.py	Thu Oct 01 18:28:19 2020 +0100
@@ -336,6 +336,7 @@
         self._dpLen = dpLen
         return self
 
+
 isAuto = Auto()
 isBoolean = _isBoolean()
 isString = _isString()
--- a/tests/test_graphics_barcode.py	Thu Sep 24 14:07:02 2020 +0100
+++ b/tests/test_graphics_barcode.py	Thu Oct 01 18:28:19 2020 +0100
@@ -5,7 +5,265 @@
 """
 from reportlab.lib.testutils import setOutDir,makeSuiteForClasses, outputfile, printLocation
 setOutDir(__name__)
-import unittest, os, sys, glob
+import unittest, os, sys, glob, time
+
+from reportlab import Version as __RL_Version__
+from reportlab.graphics.barcode.common import *
+from reportlab.graphics.barcode.code39 import *
+from reportlab.graphics.barcode.code93 import *
+from reportlab.graphics.barcode.code128 import *
+from reportlab.graphics.barcode.usps import *
+from reportlab.graphics.barcode.usps4s import USPS_4State
+from reportlab.graphics.barcode.qr import QrCodeWidget
+from reportlab.graphics.barcode.dmtx import DataMatrixWidget, pylibdmtx
+
+
+from reportlab.platypus import Spacer, SimpleDocTemplate, Table, TableStyle, Preformatted, PageBreak
+from reportlab.lib.units import inch, cm
+from reportlab.lib import colors
+
+from reportlab.pdfgen.canvas import Canvas
+from reportlab.lib.styles import getSampleStyleSheet
+from reportlab.platypus.paragraph import Paragraph
+from reportlab.platypus.frames import Frame
+from reportlab.platypus.flowables import XBox, KeepTogether
+from reportlab.graphics.shapes import Drawing, Rect, Line
+
+from reportlab.graphics.barcode import getCodes, getCodeNames, createBarcodeDrawing, createBarcodeImageInMemory
+def run(fileName):
+    styles = getSampleStyleSheet()
+    styleN = styles['Normal']
+    styleH = styles['Heading1']
+    story = []
+    storyAdd = story.append
+
+    #for codeNames in code
+    storyAdd(Paragraph('I2of5', styleN))
+    storyAdd(I2of5(1234, barWidth = inch*0.02, checksum=0))
+
+    storyAdd(Paragraph('MSI', styleN))
+    storyAdd(MSI(1234))
+
+    storyAdd(Paragraph('Codabar', styleN))
+    storyAdd(Codabar("A012345B", barWidth = inch*0.02))
+
+    storyAdd(Paragraph('Code 11', styleN))
+    storyAdd(Code11("01234545634563"))
+
+    storyAdd(Paragraph('Code 39', styleN))
+    storyAdd(Standard39("A012345B%R"))
+
+    storyAdd(Paragraph('Extended Code 39', styleN))
+    storyAdd(Extended39("A012345B}"))
+
+    storyAdd(Paragraph('Code93', styleN))
+    storyAdd(Standard93("CODE 93"))
+
+    storyAdd(Paragraph('Extended Code93', styleN))
+    storyAdd(Extended93("L@@K! Code 93 :-)")) #, barWidth=0.005 * inch))
+
+    storyAdd(Paragraph('Code 128', styleN))
+    storyAdd(Code128("AB-12345678"))
+
+    storyAdd(Paragraph('Code 128 Auto', styleN))
+    storyAdd(Code128Auto("AB-12345678"))
+
+    storyAdd(Paragraph('USPS FIM', styleN))
+    storyAdd(FIM("A"))
+
+    storyAdd(Paragraph('USPS POSTNET', styleN))
+    storyAdd(POSTNET('78247-1043'))
+
+    storyAdd(Paragraph('USPS 4 State', styleN))
+    storyAdd(USPS_4State('01234567094987654321','01234567891'))
+
+    from reportlab.graphics.barcode import createBarcodeDrawing
+
+    storyAdd(Paragraph('EAN13', styleN))
+    storyAdd(createBarcodeDrawing('EAN13', value='123456789012'))
+
+    storyAdd(Paragraph('EAN13 quiet=False', styleN))
+    storyAdd(createBarcodeDrawing('EAN13', value='123456789012', quiet=False))
+
+    storyAdd(Paragraph('EAN8', styleN))
+    storyAdd(createBarcodeDrawing('EAN8', value='1234567'))
+
+    storyAdd(PageBreak())
+
+    storyAdd(Paragraph('EAN5 price=True', styleN))
+    storyAdd(createBarcodeDrawing('EAN5', value='11299', price=True))
+
+    storyAdd(Paragraph('EAN5 price=True quiet=False', styleN))
+    storyAdd(createBarcodeDrawing('EAN5', value='11299', price=True, quiet=False))
+
+    storyAdd(Paragraph('EAN5 price=False', styleN))
+    storyAdd(createBarcodeDrawing('EAN5', value='11299', price=False))
+
+    storyAdd(Paragraph('ISBN alone', styleN))
+    storyAdd(createBarcodeDrawing('ISBN', value='9781565924796'))
+
+    storyAdd(Paragraph('ISBN  with ean5 price', styleN))
+    storyAdd(createBarcodeDrawing('ISBN', value='9781565924796',price='01299'))
+
+    storyAdd(Paragraph('ISBN  with ean5 price, quiet=False', styleN))
+    storyAdd(createBarcodeDrawing('ISBN', value='9781565924796',price='01299',quiet=False))
+
+    storyAdd(Paragraph('UPCA', styleN))
+    storyAdd(createBarcodeDrawing('UPCA', value='03600029145'))
+
+    storyAdd(Paragraph('USPS_4State', styleN))
+    storyAdd(createBarcodeDrawing('USPS_4State', value='01234567094987654321',routing='01234567891'))
+
+    storyAdd(Paragraph('QR', styleN))
+    storyAdd(createBarcodeDrawing('QR', value='01234567094987654321'))
+
+    storyAdd(Paragraph('QR', styleN))
+    storyAdd(createBarcodeDrawing('QR', value='01234567094987654321',x=30,y=50))
+
+    def addCross(d,x,y,w=5,h=5, strokeColor='black', strokeWidth=0.5):
+        w *= 0.5
+        h *= 0.5
+        d.add(Line(x-w,y,x+w,y,strokeWidth=0.5,strokeColor=colors.blue))
+        d.add(Line(x, y-h, x, y+h,strokeWidth=0.5,strokeColor=colors.blue))
+    storyAdd(Paragraph('QR in drawing at (0,0)', styleN))
+    d = Drawing(100,100)
+    d.add(Rect(0,0,100,100,strokeWidth=1,strokeColor=colors.red,fillColor=None))
+    d.add(QrCodeWidget(value='01234567094987654321'))
+    storyAdd(d)
+
+    storyAdd(Paragraph('QR in drawing at (10,10)', styleN))
+    d = Drawing(100,100)
+    d.add(Rect(0,0,100,100,strokeWidth=1,strokeColor=colors.red,fillColor=None))
+    addCross(d,10,10)
+    d.add(QrCodeWidget(value='01234567094987654321',x=10,y=10))
+    storyAdd(d)
+
+    storyAdd(Paragraph('Label Size', styleN))
+    storyAdd(XBox((2.0 + 5.0/8.0)*inch, 1 * inch, '1x2-5/8"'))
+
+    storyAdd(Paragraph('Label Size', styleN))
+    storyAdd(XBox((1.75)*inch, .5 * inch, '1/2x1-3/4"'))
+
+    if pylibdmtx:
+        storyAdd(PageBreak())
+        storyAdd(Paragraph('DataMatrix in drawing at (10,10)', styleN))
+        d = Drawing(100,100)
+        d.add(Rect(0,0,100,100,strokeWidth=1,strokeColor=colors.red,fillColor=None))
+        addCross(d,10,10)
+        d.add(DataMatrixWidget(value='1234567890',x=10,y=10))
+        storyAdd(d)
+        storyAdd(Paragraph('DataMatrix in drawing at (10,10)', styleN))
+        d = Drawing(100,100)
+        d.add(Rect(0,0,100,100,strokeWidth=1,strokeColor=colors.red,fillColor=None))
+        addCross(d,10,10)
+        d.add(DataMatrixWidget(value='1234567890',x=10,y=10,color='black',bgColor='lime'))
+        storyAdd(d)
+
+        storyAdd(Paragraph('DataMatrix in drawing at (90,90) anchor=ne', styleN))
+        d = Drawing(100,100)
+        d.add(Rect(0,0,100,100,strokeWidth=1,strokeColor=colors.red,fillColor=None))
+        addCross(d,90,90)
+        d.add(DataMatrixWidget(value='1234567890',x=90,y=90,color='darkblue',bgColor='yellow', anchor='ne'))
+        storyAdd(d)
+    
+
+    SimpleDocTemplate(fileName).build(story)
+
+def fullTest(fileName):
+    """Creates large-ish test document with a variety of parameters"""
+
+    story = []
+
+    styles = getSampleStyleSheet()
+    styleN = styles['Normal']
+    styleH = styles['Heading1']
+    styleH2 = styles['Heading2']
+    story = []
+
+    story.append(Paragraph('ReportLab %s Barcode Test Suite - full output' % __RL_Version__,styleH))
+    story.append(Paragraph('Generated at %s' % time.ctime(time.time()), styleN))
+
+    story.append(Paragraph('About this document', styleH2))
+    story.append(Paragraph('History and Status', styleH2))
+
+    story.append(Paragraph("""
+        This is the test suite and docoumentation for the ReportLab open source barcode API.
+        """, styleN))
+
+    story.append(Paragraph("""
+        Several years ago Ty Sarna contributed a barcode module to the ReportLab community.
+        Several of the codes were used by him in hiw work and to the best of our knowledge
+        this was correct.  These were written as flowable objects and were available in PDFs,
+        but not in our graphics framework.  However, we had no knowledge of barcodes ourselves
+        and did not advertise or extend the package.
+        """, styleN))
+
+    story.append(Paragraph("""
+        We "wrapped" the barcodes to be usable within our graphics framework; they are now available
+        as Drawing objects which can be rendered to EPS files or bitmaps.  For the last 2 years this
+        has been available in our Diagra and Report Markup Language products.  However, we did not
+        charge separately and use was on an "as is" basis.
+        """, styleN))
+
+    story.append(Paragraph("""
+        A major licensee of our technology has kindly agreed to part-fund proper productisation
+        of this code on an open source basis in Q1 2006.  This has involved addition of EAN codes
+        as well as a proper testing program.  Henceforth we intend to publicise the code more widely,
+        gather feedback, accept contributions of code and treat it as "supported".  
+        """, styleN))
+
+    story.append(Paragraph("""
+        This involved making available both downloads and testing resources.  This PDF document
+        is the output of the current test suite.  It contains codes you can scan (if you use a nice sharp
+        laser printer!), and will be extended over coming weeks to include usage examples and notes on
+        each barcode and how widely tested they are.  This is being done through documentation strings in
+        the barcode objects themselves so should always be up to date.
+        """, styleN))
+
+    story.append(Paragraph('Usage examples', styleH2))
+    story.append(Paragraph("""
+        To be completed
+        """, styleN))
+
+    story.append(Paragraph('The codes', styleH2))
+    story.append(Paragraph("""
+        Below we show a scannable code from each barcode, with and without human-readable text.
+        These are magnified about 2x from the natural size done by the original author to aid
+        inspection.  This will be expanded to include several test cases per code, and to add
+        explanations of checksums.  Be aware that (a) if you enter numeric codes which are too
+        short they may be prefixed for you (e.g. "123" for an 8-digit code becomes "00000123"),
+        and that the scanned results and readable text will generally include extra checksums
+        at the end.
+        """, styleN))
+
+    codeNames = getCodeNames()
+    from reportlab.lib.utils import flatten
+    width = [float(x[8:]) for x in sys.argv if x.startswith('--width=')]
+    height = [float(x[9:]) for x in sys.argv if x.startswith('--height=')]
+    isoScale = [int(x[11:]) for x in sys.argv if x.startswith('--isoscale=')]
+    options = {}
+    if width: options['width'] = width[0]
+    if height: options['height'] = height[0]
+    if isoScale: options['isoScale'] = isoScale[0]
+    scales = [x[8:].split(',') for x in sys.argv if x.startswith('--scale=')]
+    scales = list(map(float,scales and flatten(scales) or [1]))
+    scales = list(map(float,scales and flatten(scales) or [1]))
+    for scale in scales:
+        story.append(PageBreak())
+        story.append(Paragraph('Scale = %.1f'%scale, styleH2))
+        story.append(Spacer(36, 12))
+        for codeName in codeNames:
+            s = [Paragraph('Code: ' + codeName, styleH2)]
+            for hr in (0,1):
+                s.append(Spacer(36, 12))
+                dr = createBarcodeDrawing(codeName, humanReadable=hr,**options)
+                dr.renderScale = scale
+                s.append(dr)
+                s.append(Spacer(36, 12))
+            s.append(Paragraph('Barcode should say: ' + dr._bc.value, styleN))
+            story.append(KeepTogether(s))
+
+    SimpleDocTemplate(fileName).build(story)
 
 try:
     from reportlab.graphics import _renderPM
@@ -18,6 +276,7 @@
     @classmethod
     def setUpClass(cls):
         cls.outDir = outDir = outputfile('barcode-out')
+        cls.makeFn = lambda _, fn: os.path.join(outDir,fn)
         if not os.path.isdir(outDir):
             os.makedirs(outDir)
         for x in glob.glob(os.path.join(outDir,'*')):
@@ -70,6 +329,18 @@
                 if not klass.valid(c):
                     raise ValueError('%s.valid(%r) does not match' % (klass.__name__,c))
 
+    def createSample(self,name,memory):
+        f = open(self.makeFn(name),'wb')
+        f.write(memory)
+        f.close()
+    def test_graphics_barcode(self):
+        run(self.makeFn('graphics-barcode-out.pdf'))
+        fullTest(self.makeFn('graphics-barcode-full.pdf'))
+        self.createSample('test_cbcim.png',createBarcodeImageInMemory('EAN13', value='123456789012'))
+        self.createSample('test_cbcim.gif',createBarcodeImageInMemory('EAN8', value='1234567', format='gif'))
+        self.createSample('test_cbcim.pdf',createBarcodeImageInMemory('UPCA', value='03600029145',format='pdf', barHeight=40))
+        self.createSample('test_cbcim.tiff',createBarcodeImageInMemory('USPS_4State', value='01234567094987654321',routing='01234567891',format='tiff'))
+
 def makeSuite():
     return makeSuiteForClasses(BarcodeWidgetTestCase)