Author: reinhard
Date: 2009-10-06 09:49:09 -0500 (Tue, 06 Oct 2009)
New Revision: 289
Added:
printing/
printing/__init__.py
printing/barcodes/
printing/barcodes/Base.py
printing/barcodes/README
printing/barcodes/__init__.py
printing/barcodes/codabar.py
printing/barcodes/code39.py
printing/barcodes/interleaved2of5.py
printing/barcodes/postnet.py
printing/barcodes/standard2of5.py
printing/pdftable/
printing/pdftable/README
printing/pdftable/__init__.py
printing/pdftable/pdftable.py
printing/pdftable/sample.py
Log:
Moved printing library to gnue-contrib since it's not used in any GNUe Tool.
Added: printing/__init__.py
===================================================================
Added: printing/barcodes/Base.py
===================================================================
--- printing/barcodes/Base.py (rev 0)
+++ printing/barcodes/Base.py 2009-10-06 14:49:09 UTC (rev 289)
@@ -0,0 +1,257 @@
+#
+# This file is part of GNU Enterprise.
+#
+# GNU Enterprise is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2, or (at your option) any later version.
+#
+# GNU Enterprise 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 the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with program; see the file COPYING. If not,
+# write to the Free Software Foundation, Inc., 59 Temple Place
+# - Suite 330, Boston, MA 02111-1307, USA.
+#
+# Copyright 2004-2009 Free Software Foundation
+#
+# FILE:
+# barcodes/Base.py
+#
+# DESCRIPTION:
+"""
+"""
+#
+import sys, os, string
+from reportlab.lib import colors
+from reportlab.graphics.shapes import *
+
+class InvalidBarcode(StandardError):
+ pass
+
+
+class Barcode:
+ mapping = {}
+ chars = []
+ validLengths = []
+ lineWidth = 1.44 # points (.02")
+ lineHeight = 18 # (.125")
+ spacing = ''
+ start = ''
+ stop = ''
+ defaultIncludeText = True
+
+ encodingMap = {
+ # Stroke?, X Multiplier, Y Multiplier
+ '0': (False, 1, 1),
+ '1': (True, 1, 1),
+ }
+
+
+ def checkdigit(self, value):
+ """
+ Returns the checkdigit encoding for the given value
+ """
+ return ''
+
+ def calculateLineHeight(self, width):
+ return self.lineHeight
+
+ def _buildBinary(self, value):
+ """
+ Returns a string of 0s (no line) and 1s (line).
+ Note that, depending on the barcode type,
+ a single bar could be multiple
+ """
+
+ if self.validLengths and len(value) not in self.validLengths:
+ raise InvalidBarcode, 'Barcode is not a valid length: Should be one of %s' % self.validLengths
+
+ value = str(value)
+ rv = self.start + self.spacing
+
+ first = True
+ for ch in value + self.checkdigit(value):
+ if first:
+ first = False
+ else:
+ rv += self.spacing
+ try:
+ rv += self.mapping[ch]
+ except KeyError:
+ raise InvalidBarcode, 'Barcode cannot contain "%s" character.' % ch
+
+ return rv + self.spacing + self.stop
+
+
+ ###
+ ###
+ ###
+ def generate(self, value, stream=None,
+ format='eps', includeText=None, textSize=7, dpi=300):
+ """
+ Generates the requested bar code either via a stream or as the
+ requested object type.
+
+ @param value: The string to convert to a barcode
+ @param stream: Optional argument of file name as a string, or any
+ open file style object.
+ @param format: The format in which the output should be generated.
+ Valid file formats include pdf, eps, svg and
+ will require the stream argument be provided.
+ Valid object formats include
+ rldrawing (ReportLab Drawing object will be returned,
+ No stream argument is required).
+ @param includeText: Boolean. If true then human readable text will
+ be printed centered under the barcode.
+ @param textSize: The point size of the human readable text.
+ @param dpi: The dots per inch at which the bitmap should be generated.
+
+ @return: None or a format dependent object. Valid return values::
+ eps : None
+ pdf : None
+ svg : None
+ rl : ReportLab Drawing
+ @rtype: misc
+ """
+
+ assert (format in ('rl','pil') or stream is not None)
+
+ d = self._generateDrawing(value, includeText, textSize, dpi)
+
+ #
+ # Process formats that return value instead of write to a file
+ #
+ if format == 'rl':
+ return d
+
+ #
+ # A stream is required for the remaining formats
+ #
+ if not hasattr(stream, 'write'):
+ closeFile = True
+ stream = open(stream,'w')
+ else:
+ closeFile = False
+
+ if format == 'pdf':
+ from reportlab.graphics import renderPDF
+ renderPDF.drawToFile(d, stream, 'GNUe')
+ elif format == 'eps':
+ from reportlab.graphics import renderPS
+ renderPS.drawToFile(d, stream)
+ elif format == 'svg':
+ from reportlab.graphics import renderSVG
+ renderSVG.drawToFile(d, stream)
+ elif format in ('png','tiff'):
+ from reportlab.graphics import renderPM
+ renderPM.drawToFile(d, stream,format.upper(), dpi=dpi)
+ elif format in ('pil',):
+ from reportlab.graphics import renderPM
+ return renderPM.drawToPIL(d, dpi=dpi)
+
+
+#
+# This code *should* be replaced with calls to renderPM
+# but that appears broken in the .debs
+#
+ ##
+ ## Raster-based output using PIL
+ ##
+# elif format in ('png','tiff','ppm','xbm'):
+# lineWidth = int(lineWidth * dpi/72+.5) # 300dpi
+# lineHeight = int(lineHeight * dpi/72+.5) # 300dpi
+# # Special case for PostNet
+# lineHeight2 = int(lineHeight * .45+.5)
+#
+# # Create a new monochrome image with a white backgint
+# image = Image.new('1',(int(len(code)*lineWidth+.5),
+# int(lineHeight+.5)), 1)
+# draw = ImageDraw.Draw(image)
+# offs = 0
+# for ch in code:
+# if ch == '1':
+# draw.rectangle((offs,0,offs+lineWidth-1,lineHeight),
+# outline=0, fill=0)
+# # Special case for PostNet
+# elif ch == '2':
+# draw.rectangle((offs,0,offs+lineWidth-1,lineHeight2),
+# outline=0, fill=0)
+# offs += lineWidth
+#
+# image.save(stream, format)
+
+ if closeFile:
+ stream.close()
+
+ def _generateDrawing(self, value, includeText=None, textSize=7, dpi=300):
+ """
+ Generates a ReportLab Drawing object used by the renderers in generate()
+
+ @param value: The string to convert to a barcode
+ @param includeText: Boolean. If true then human readable text will
+ be printed centered under the barcode.
+
+ @param textSize: The point size of the human readable text.
+ @param dpi: The dots per inch at which the bitmap should be generated.
+
+ @return: ReportLab Drawing
+ @rtype: misc
+ """
+
+ if includeText is None:
+ includeText = self.defaultIncludeText
+ code = self._buildBinary(value)
+ lineWidth = self.lineWidth
+ try:
+ spaceWidth = self.spaceWidth
+ except:
+ spaceWidth = lineWidth
+
+ width = 0
+ for ch in code:
+ stroke, xmul, ymul = self.encodingMap[ch]
+ if stroke:
+ width += lineWidth*xmul
+ else:
+ width += spaceWidth*xmul
+
+ lineHeight = self.calculateLineHeight(width)
+
+ d = Drawing(width+1,lineHeight+(includeText and ( textSize+2 ) or 1 ))
+
+ if includeText:
+ y = textSize + 1
+ else:
+ y = 0
+
+ # Draw each bar
+ x = 0
+ for ch in code:
+ stroke, xmul, ymul = self.encodingMap[ch]
+ dx = lineWidth*xmul
+ dy = lineHeight*ymul
+ if stroke:
+ # So we won't cut off half the first bar...
+ if not x:
+ x = dx/2.0
+
+ d.add(Line(x+dx/2, y, x+dx/2, y+dy, strokeColor=colors.black,strokeWidth=dx))
+
+ x += dx
+ else:
+ x += spaceWidth*xmul
+
+ # Draw the text
+ if includeText:
+ d.add(String(x/2.0, textSize/2.0, value, fontSize=textSize, fontName="Courier",fillColor=colors.black,textAnchor="middle"))
+
+ return d
+
+ # Line height is .15 * barcode width, but at least .25"
+ # This is used by Code39, Interleaved 2 of 5, etc
+ def _calculate15(self, width):
+ return max(18, .15 * width)
Added: printing/barcodes/README
===================================================================
--- printing/barcodes/README (rev 0)
+++ printing/barcodes/README 2009-10-06 14:49:09 UTC (rev 289)
@@ -0,0 +1,33 @@
+Usage:
+
+ from gnue.common.printing.barcodes import Code39
+
+ # You can pass either a file object, or a string containing a file name:
+
+ # File Object:
+ myfile = open('foo.eps','w')
+ Code39().generate('123443',myfile,'eps')
+ myfile.close()
+
+ # or string...
+ Code39().generate('123456','foo2.eps','eps')
+
+ # or get a report lab object
+ object = Code39().generate('GNUE',format='rl')
+ from reportlab.graphics import renderPDF
+ renderPDF.drawToFile(object, "output.pdf","GNUe Common Barcode Demo")
+
+It has strong support for "eps". It somewhat supports
+"png", "tiff", and "xbm" via Python Imaging Library.
+
+Test the outputs with your scanners before using in mass-production.
+We try to stay within specs in our code, but printers and readers vary.
+
+Specs:
+ * http://www.adams1.com/pub/russadam/info.html#Specs
+ General line width specs, min sizes
+ (also has link to an online barcode generator to compare against)
+
+ * http://barcodeisland.com/symbolgy.phtml
+ A lot of encodings for barcodes, but no spacing specs
+
Added: printing/barcodes/__init__.py
===================================================================
--- printing/barcodes/__init__.py (rev 0)
+++ printing/barcodes/__init__.py 2009-10-06 14:49:09 UTC (rev 289)
@@ -0,0 +1,32 @@
+#
+# This file is part of GNU Enterprise.
+#
+# GNU Enterprise is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2, or (at your option) any later version.
+#
+# GNU Enterprise 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 the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with program; see the file COPYING. If not,
+# write to the Free Software Foundation, Inc., 59 Temple Place
+# - Suite 330, Boston, MA 02111-1307, USA.
+#
+# Copyright 2004-2009 Free Software Foundation
+#
+# FILE:
+# barcodes/__init__.py
+#
+# DESCRIPTION:
+"""
+"""
+#
+from codabar import *
+from code39 import *
+from interleaved2of5 import *
+from postnet import *
+from standard2of5 import *
Added: printing/barcodes/codabar.py
===================================================================
--- printing/barcodes/codabar.py (rev 0)
+++ printing/barcodes/codabar.py 2009-10-06 14:49:09 UTC (rev 289)
@@ -0,0 +1,77 @@
+#
+# This file is part of GNU Enterprise.
+#
+# GNU Enterprise is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2, or (at your option) any later version.
+#
+# GNU Enterprise 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 the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with program; see the file COPYING. If not,
+# write to the Free Software Foundation, Inc., 59 Temple Place
+# - Suite 330, Boston, MA 02111-1307, USA.
+#
+# Copyright 2004-2009 Free Software Foundation
+#
+# FILE:
+# barcodes/codabar.py
+#
+# DESCRIPTION:
+"""
+Implements the Codabar barcode spec
+"""
+#
+from Base import Barcode
+
+class Codabar(Barcode):
+ chars = '0123456789-$:/.+'
+ mapping = {
+ '0': 'NnNnNwWn',
+ '1': 'NnNnWwNn',
+ '2': 'NnNwNnWn',
+ '3': 'WwNnNnNn',
+ '4': 'NnWnNwNn',
+ '5': 'WnNnNwNn',
+ '6': 'NwNnNnWn',
+ '7': 'NwNnWnNn',
+ '8': 'NwWnNnNn',
+ '9': 'WnNwNnNn',
+ '-': 'NnNwWnNn',
+ '$': 'NnWwNnNn',
+ ':': 'WnNnWnWn',
+ '/': 'WnWnNnWn',
+ '.': 'WnWnWnNn',
+ '+': 'NnWwWwWn'
+ }
+
+ start = 'NwNwNnWn'
+ stop = 'nnNwWwN'
+
+ lineWidth = .72 # points (1.0mil)
+ lineHeight = 18 # 1/4"
+
+ encodingMap = {
+ # Stroke?, X Multiplier, Y Multiplier
+ 'n': (False, 1, 1), # Narrow Spaces
+ 'w': (False, 2.5, 1), # Wide Spaces
+ 'N': (True, 1, 1), # Narrow bars
+ 'W': (True, 2.5, 1) # Wide bars
+ }
+
+if __name__ == '__main__':
+
+ codabar = Codabar()
+
+ def test(value, format, file):
+ f = open(file,'wb')
+ codabar.generate(value,f, format)
+ f.close()
+
+# test('0123456789-$:/.+','png','test1.png')
+# test('+./:$-9876543210','tiff','test1.tif')
+ test('0123456789-$:/.+','eps','codabar-1.eps')
Added: printing/barcodes/code39.py
===================================================================
--- printing/barcodes/code39.py (rev 0)
+++ printing/barcodes/code39.py 2009-10-06 14:49:09 UTC (rev 289)
@@ -0,0 +1,134 @@
+#
+# This file is part of GNU Enterprise.
+#
+# GNU Enterprise is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2, or (at your option) any later version.
+#
+# GNU Enterprise 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 the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with program; see the file COPYING. If not,
+# write to the Free Software Foundation, Inc., 59 Temple Place
+# - Suite 330, Boston, MA 02W1-1307, USA.
+#
+# Copyright 2w4 Free Software Foundation
+#
+# FILE:
+# barcodes/code39.py
+#
+# DESCRIPTION:
+"""
+Implements the Code 39 barcode spec
+"""
+#
+
+from Base import Barcode
+
+class Code39(Barcode):
+ """
+ Code 39 without a check digit
+ """
+ chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ-. $/+%'
+ mapping = {
+ '0': 'NnNwWnWnNn',
+ '1': 'WnNwNnNnWn',
+ '2': 'NnWwNnNnWn',
+ '3': 'WnWwNnNnNn',
+ '4': 'NnNwWnNnWn',
+ '5': 'WnNwWnNnNn',
+ '6': 'NnWwWnNnNn',
+ '7': 'NnNwNnWnWn',
+ '8': 'WnNwNnWnNn',
+ '9': 'NnWwNnWnNn',
+ 'A': 'WnNnNwNnWn',
+ 'B': 'NnWnNwNnWn',
+ 'C': 'WnWnNwNnNn',
+ 'D': 'NnNnWwNnWn',
+ 'E': 'WnNnWwNnNn',
+ 'F': 'NnWnWwNnNn',
+ 'G': 'NnNnNwWnWn',
+ 'H': 'WnNnNwWnNn',
+ 'I': 'NnWnNwWnNn',
+ 'J': 'NnNnWwWnNn',
+ 'K': 'WnNnNnNwWn',
+ 'L': 'NnWnNnNwWn',
+ 'M': 'WnWnNnNwNn',
+ 'N': 'NnNnWnNwWn',
+ 'O': 'WnNnWnNwNn',
+ 'P': 'NnWnWnNwNn',
+ 'Q': 'NnNnNnWwWn',
+ 'R': 'WnNnNnWwNn',
+ 'S': 'NnWnNnWwNn',
+ 'T': 'NnNnWnWwNn',
+ 'U': 'WwNnNnNnWn',
+ 'V': 'NwWnNnNnWn',
+ 'W': 'WwWnNnNnNn',
+ 'X': 'NwNnWnNnWn',
+ 'Y': 'WwNnWnNnNn',
+ 'Z': 'NwWnWnNnNn',
+ '-': 'NwNnNnWnWn',
+ '.': 'WwNnNnWnNn',
+ ' ': 'NwWnNnWnNn',
+ '$': 'NwNwNnNwNn',
+ '/': 'NwNwNnNwNn',
+ '+': 'NwNnNwNwNn',
+ '%': 'NnNwNwNwNn',
+ }
+
+ start= 'NwNnWnWnNn'
+ stop = 'NwNnWnWnN'
+
+ lineWidth = .6 # points (1.0mil)
+ lineHeight = 18 # Actually dependent on the width
+
+ encodingMap = {
+ # Stroke?, X Multiplier, Y Multiplier
+ 'n': (False, 1, 1), # Narrow Spaces
+ 'w': (False, 2.1, 1), # Wide Spaces
+ 'N': (True, 1, 1), # Narrow bars
+ 'W': (True, 2.1, 1) # Wide bars
+ }
+
+ calculateLineHeight = Barcode._calculate15
+
+
+######################################
+##
+##
+class Code39CheckDigit(Code39):
+ """
+ Code 39 with a Mod 43 check digit
+ """
+
+ # Calculate a Mod-43 check digit
+ def checkdigit(self, value):
+ v = 0
+ for ch in value:
+ v += self.chars.index(ch)
+ return self.chars[divmod(chv,43)[1]]
+
+
+if __name__ == '__main__':
+
+ code39 = Code39()
+
+ def test(value, format, file):
+ f = open(file,'wb')
+ code39.generate(value,f, format)
+ f.close()
+
+ import sys
+ if len(sys.argv[:1]):
+ test (sys.argv[1],'eps','code39-test.eps')
+ else:
+# test('0123456789ABCDEF','png','test1.png')
+# test('0123456789ABCDEF','tiff','test1.tif')
+ test('0123456789ABCDEF','eps','code39-1.eps')
+ test('GHIJKLMNOPQRSTUV','eps','code39-2.eps')
+ test('WXYZ-. $/+%','eps','code39-3.eps')
+ test('AR04F123','eps','code39-4.eps')
Added: printing/barcodes/interleaved2of5.py
===================================================================
--- printing/barcodes/interleaved2of5.py (rev 0)
+++ printing/barcodes/interleaved2of5.py 2009-10-06 14:49:09 UTC (rev 289)
@@ -0,0 +1,108 @@
+#
+# This file is part of GNU Enterprise.
+#
+# GNU Enterprise is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2, or (at your option) any later version.
+#
+# GNU Enterprise 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 the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with program; see the file COPYING. If not,
+# write to the Free Software Foundation, Inc., 59 Temple Place
+# - Suite 330, Boston, MA 02111-1307, USA.
+#
+# Copyright 2004-2009 Free Software Foundation
+#
+# FILE:
+# barcodes/code39.py
+#
+# DESCRIPTION:
+"""
+Implements the Interleaved 2 of 5 barcode spec
+"""
+#
+
+from Base import Barcode
+
+class Interleaved2of5(Barcode):
+ chars = '0123456789'
+
+ # Interleaved works differently than the others
+ mapping = {
+ '0': 'NNWWN',
+ '1': 'WNNNW',
+ '2': 'NWNNW',
+ '3': 'WWNNN',
+ '4': 'NNWNW',
+ '5': 'WNWNN',
+ '6': 'NWWNN',
+ '7': 'NNNWW',
+ '8': 'WNNWN',
+ '9': 'NWNWN'
+ }
+
+ lineWidth = .6 # points (7.5mil)
+ lineHeight = 18 # Actually dependent on the width
+
+ start = 'NnNn'
+ stop = 'WnN'
+
+ encodingMap = {
+ # Stroke?, X Multiplier, Y Multiplier
+ 'n': (False, 1, 1), # Narrow Spaces
+ 'w': (False, 2.5, 1), # Wide Spaces
+ 'N': (True, 1, 1), # Narrow bars
+ 'W': (True, 2.5, 1) # Wide bars
+ }
+
+ calculateLineHeight = Barcode._calculate15
+
+ # Since this is interleaved, we do
+ # our own custom buildBinary.
+ def _buildBinary(self, value):
+ """
+ Returns a string of 0s (no line) and 1s (line).
+ Note that, depending on the barcode type,
+ a single bar could be multiple
+ """
+
+ value = str(value)
+ if len(value)/2.0 != int(len(value)/2.0):
+ raise InvalidBarcode, \
+ 'Interleaved 2of5 must have an even number of digits'
+
+ rv = self.start
+
+ even = False
+ for i in range(len(value)/2):
+ try:
+ bar = self.mapping[value[i]]
+ space = self.mapping[value[i+1]].lower()
+ except KeyError:
+ raise InvalidBarcode
+
+ for j in xrange(5):
+ rv += bar[j]
+ rv += space[j]
+
+ return rv + self.stop
+
+
+if __name__ == '__main__':
+
+ testbar = Interleaved2of5()
+
+ def test(value, format, file):
+ f = open(file,'wb')
+ testbar.generate(value,f, format)
+ f.close()
+
+# test('0123456789','png','test1.png')
+# test('9876543210','tiff','test1.tif')
+ test('0123456789','eps','i2of5-1.eps')
+ test('9876543210','eps','i2of5-1.eps')
Added: printing/barcodes/postnet.py
===================================================================
--- printing/barcodes/postnet.py (rev 0)
+++ printing/barcodes/postnet.py 2009-10-06 14:49:09 UTC (rev 289)
@@ -0,0 +1,97 @@
+#
+# This file is part of GNU Enterprise.
+#
+# GNU Enterprise is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2, or (at your option) any later version.
+#
+# GNU Enterprise 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 the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with program; see the file COPYING. If not,
+# write to the Free Software Foundation, Inc., 59 Temple Place
+# - Suite 330, Boston, MA 02111-1307, USA.
+#
+# Copyright 2004-2009 Free Software Foundation
+#
+# FILE:
+# barcodes/code39.py
+#
+# DESCRIPTION:
+"""
+Implements the USPS PostNet barcode spec
+"""
+#
+
+from Base import Barcode, InvalidBarcode
+
+class PostNet(Barcode):
+ """
+ PostNet
+ """
+ validLengths = [5,9,11]
+ chars = '0123456789'
+ mapping = {
+ # 1 = tall bar, 2=short bar, 0=space
+ '0': '1010202020',
+ '1': '2020201010',
+ '2': '2020102010',
+ '3': '2020101020',
+ '4': '2010202010',
+ '5': '2010201020',
+ '6': '2010102020',
+ '7': '1020202010',
+ '8': '1020201020',
+ '9': '1020102020'
+ }
+
+ start = '10'
+ stop = '1'
+
+ lineWidth = 1.44 # points
+ lineHeight = 8.5 # (.125")
+ spaceWidth = 1.9 #1.66
+
+ encodingMap = {
+ # Stroke?, X Multiplier, Y Multiplier
+ '0': (False, 1, 1), # Spaces
+ '1': (True, 1, 1), # Tall bars
+ '2': (True, 1, .45) # Short bars
+ }
+
+
+
+ defaultIncludeText = False
+
+ # Calculate a Mod-10 check digit
+ def checkdigit(self, value):
+ v = 0
+ for ch in value:
+ try:
+ v += int(ch)
+ except ValueError:
+ raise InvalidBarcode
+ cd = abs(10-divmod(v,10)[1])
+ if cd == 10:
+ cd = 0
+ return self.chars[cd]
+
+
+if __name__ == '__main__':
+
+ postnet = PostNet()
+
+ def test(value, format, file):
+ f = open(file,'wb')
+ postnet.generate(value,f, format)
+ f.close()
+
+# test('381072456','png','test1.png')
+# test('381172459','tiff','test1.tif')
+ test('383759907','eps','postnet-1.eps')
+ test('12345','eps','postnet-2.eps')
+ test('12345678901','eps','postnet-3.eps')
Added: printing/barcodes/standard2of5.py
===================================================================
--- printing/barcodes/standard2of5.py (rev 0)
+++ printing/barcodes/standard2of5.py 2009-10-06 14:49:09 UTC (rev 289)
@@ -0,0 +1,74 @@
+#
+# This file is part of GNU Enterprise.
+#
+# GNU Enterprise is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2, or (at your option) any later version.
+#
+# GNU Enterprise 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 the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with program; see the file COPYING. If not,
+# write to the Free Software Foundation, Inc., 59 Temple Place
+# - Suite 330, Boston, MA 02111-1307, USA.
+#
+# Copyright 2004-2009 Free Software Foundation
+#
+# FILE:
+# barcodes/code39.py
+#
+# DESCRIPTION:
+"""
+Implements the Standard 2 of 5 barcode spec
+"""
+#
+
+from Base import Barcode
+
+class Standard2of5(Barcode):
+ chars = '0123456789'
+ mapping = {
+ '0': 'N0N0W0W0N0',
+ '1': 'W0N0N0N0W0',
+ '2': 'N0W0N0N0W0',
+ '3': 'W0W0N0N0N0',
+ '4': 'N0N0W0N0W0',
+ '5': 'W0N0W0N0N0',
+ '6': 'N0W0W0N0N0',
+ '7': 'N0N0N0W0W0',
+ '8': 'W0N0N0W0N0',
+ '9': 'N0W0N0W0N0'
+ }
+
+ start = '0M0N0'
+ stop = 'M0N0M'
+
+ encodingMap = {
+ # Stroke?, X Multiplier, Y Multiplier
+ '0': (False, 1, 1), # Narrow Spaces
+ 'N': (True, 1, 1), # Narrow bars
+ 'M': (True, 2, 1), # Medium bars
+ 'W': (True, 3, 1) # Wide bars
+ }
+
+ # calculateLineHeight = Barcode._calculate15 # Unsure of this
+ lineWidth = 1 # points -- Unsure of this
+ lineHeight = 18 # Actually dependent on the width
+
+
+if __name__ == '__main__':
+
+ testbar = Standard2of5()
+
+ def test(value, format, file):
+ f = open(file,'wb')
+ testbar.generate(value,f, format)
+ f.close()
+
+# test('0123456789','png','test1.png')
+# test('9876543210','tiff','test1.tif')
+ test('0123456789','eps','s2of5-1.eps')
Added: printing/pdftable/README
===================================================================
--- printing/pdftable/README (rev 0)
+++ printing/pdftable/README 2009-10-06 14:49:09 UTC (rev 289)
@@ -0,0 +1 @@
+See sample.py for an example on how to use pdftable
Added: printing/pdftable/__init__.py
===================================================================
--- printing/pdftable/__init__.py (rev 0)
+++ printing/pdftable/__init__.py 2009-10-06 14:49:09 UTC (rev 289)
@@ -0,0 +1,28 @@
+#
+# This file is part of GNU Enterprise.
+#
+# GNU Enterprise is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2, or (at your option) any later version.
+#
+# GNU Enterprise 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 the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with program; see the file COPYING. If not,
+# write to the Free Software Foundation, Inc., 59 Temple Place
+# - Suite 330, Boston, MA 02111-1307, USA.
+#
+# Copyright 2004-2009 Free Software Foundation
+#
+# FILE:
+# pdftable/__init__.py
+#
+# DESCRIPTION:
+"""
+"""
+#
+from pdftable import *
Added: printing/pdftable/pdftable.py
===================================================================
--- printing/pdftable/pdftable.py (rev 0)
+++ printing/pdftable/pdftable.py 2009-10-06 14:49:09 UTC (rev 289)
@@ -0,0 +1,789 @@
+#
+# This file is part of GNU Enterprise.
+#
+# GNU Enterprise is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2, or (at your option) any later version.
+#
+# GNU Enterprise 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 the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with program; see the file COPYING. If not,
+# write to the Free Software Foundation, Inc., 59 Temple Place
+# - Suite 330, Boston, MA 02111-1307, USA.
+#
+# Copyright 2004-2009 Free Software Foundation
+#
+# FILE:
+# pdftable/pdftable.py
+#
+# DESCRIPTION:
+"""
+A class that creates multisection tabular pdf reports.
+"""
+#
+
+# ============================================================================
+# Imports
+# ============================================================================
+# ----------------------------------------------------------------------------
+# Python standard modules
+# ----------------------------------------------------------------------------
+import time
+import sys
+
+# ----------------------------------------------------------------------------
+# Reportlab modules
+# ----------------------------------------------------------------------------
+from reportlab.lib import colors
+from reportlab.lib.units import inch
+from reportlab.lib.pagesizes import letter, landscape, portrait
+from reportlab.pdfgen import canvas
+from reportlab.pdfbase.pdfmetrics import getFont
+
+# ============================================================================
+# Module constants
+# ============================================================================
+# ----------------------------------------------------------------------------
+# Text alignment constants
+# ----------------------------------------------------------------------------
+LEFT=0
+RIGHT=1
+CENTER=2
+
+# ----------------------------------------------------------------------------
+# Some sample font definitions
+# ----------------------------------------------------------------------------
+fontDefs = { # Font name Pt Size Vert Spacing
+ 'dataFont' : ('Times-Roman', 10, 11),
+ 'subtotalFont' : ('Times-Bold', 10, 12),
+ 'tableHeaderFont' : ('Times-Bold', 10, 12),
+
+ 'titleFont' : ('Helvetica-Bold', 14, 14),
+ 'title2Font' : ('Helvetica', 12, 14),
+ 'title3Font' : ('Helvetica-Oblique',12, 14),
+ 'repeatTitleFont' : ('Helvetica-Oblique', 9, 10),
+ 'footerFont' : ('Times-Roman', 9, 10),
+
+ 'subtitleFont' : ('Times-Bold', 12, 13),
+ 'subtitleLabelFont' : ('Times-Roman', 12, 13),
+ 'subtitleContinueFont' : ('Times-Italic', 10, 13),
+}
+
+# ----------------------------------------------------------------------------
+# Sample Color settings
+#
+# TODO: This really needs handled in a more flexible mannor.
+# ----------------------------------------------------------------------------
+
+
+# ----------------------------------------------------------------------------
+# Size constants
+#
+# TODO: This really needs handled in a more flexible mannor.
+# ----------------------------------------------------------------------------
+
+lineWidth = .25
+
+# NOTE: I tried not to tie internal logic to the font/color/tracking
+# values that follow, so *theoretically* you can adjust them to
+# your liking.
+leftmargin = .75*inch
+topmargin = .75*inch
+
+# This is nothing but voodoo guesses...
+# Greatly depends on pointsize of self.fontDefs['dataFont']
+# TODO: This should probably be computed based on width of a "0"
+# TODO: and the width of the report. But, eh, this works for now...
+maxColsForPortraitNonscaled = 100 # Number of columns before we start scaling down
+maxColsForPortraitScaled = 120 # Number of columns before we switch to landscape
+maxColsForLandscapeNonscaled = 140 # Number of columns before landscape + scaling
+
+# ============================================================================
+# Class definition
+# ============================================================================
+class pdftable:
+ fontDefs = fontDefs
+ # ==========================================================================
+ # Creation/closure functions
+ # ==========================================================================
+
+ # --------------------------------------------------------------------------
+ # Initialize
+ # --------------------------------------------------------------------------
+ def __init__(self, destination, parameterBox = ()):
+ """
+ A simple table based report generator.
+
+ The class provides the following features:
+ - Border support for columns, rows.
+ - Automatic adjustments of the data when it is unable to fit on one page.
+ - Multiple sections of a report handled automatically.
+
+ @param destination: A python file handle used for output
+ @param parameterBox: Unused?
+ """
+ self._titleList = ['Your Report','Your Report']
+
+ self.destination = destination
+# self.parameterBox = parameterBox
+
+ self.highlightColor = colors.HexColor("0xffffcc")
+ self.headingColor = self.subtotalColor = colors.HexColor("0xe6e6ff")
+
+ self.highlightIndex = 0
+ self.init = 1
+ self._scalingComplete = 0
+ self.page = 0
+ self.continuing = 0
+
+ # Time of report session
+ self.timestamp = time.strftime("%m/%d/%Y %I:%M%p", time.localtime())
+
+ # Will be set by _beginData()
+ self.height = 0
+ self.width = 0
+ self.scale = 1.0
+
+ self._currentSectionType = "default"
+ self._currentSection = {}
+ self.definedSectionTypes = {}
+
+ self.columnGap = 6 # in pts, Amount of gap to leave between columns... doesn't need to be too much
+
+ # --------------------------------------------------------------------------
+ # Finish up the output
+ # --------------------------------------------------------------------------
+ def close(self):
+ """
+ Finalize the report.
+
+ Should be called after all data was sent to the report.
+ """
+ self.canvas.showPage()
+ self.canvas.save()
+
+
+ # ==========================================================================
+ # Functions that effect report wide settings
+ # ==========================================================================
+
+ # --------------------------------------------------------------------------
+ # Sets the title of the report
+ # --------------------------------------------------------------------------
+ def setFullTitle(self,titleList):
+ """
+ Sets the title of the report
+
+ @param titleList: A list containing tuples of the following format
+ (text, font defintion)
+ Font defintions are also tuples in the format
+ (Font name, Pt Size, Vert Spacing) such as...
+ ('Times-Roman', 10, 11)
+ """
+ self._titleList = titleList
+
+ # --------------------------------------------------------------------------
+ # Define a column on the report
+ # --------------------------------------------------------------------------
+ def addColumn(self, align, minSize, overflow="", highlight=None,
+ leftBorder=0, rightBorder=0, topBorder=0, bottomBorder=0,
+ sectionType="default"):
+ """
+ Defines a column on the record for the specified section
+
+ @param minSize: The minimum size in points of the column.
+ @param overflow: The text that should be printed if the contents of a column are too
+ large for the column width.
+ @param highlight: The color of background to use in this column.
+ @param leftBorder: The width of the left side border in points.
+ @param rightBorder: The width of the right side border in points.
+ @param topBorder: The width of the top side border in points.
+ @param bottomBorder: The width of the bottom side border in points.
+ @param sectionType: The name of the section to which this column should be added.
+ """
+ try:
+ secType = self.definedSectionTypes[sectionType]
+ except KeyError:
+ self.definedSectionTypes[sectionType] = {'columnSizes' :[],
+ 'columnAligns' :[],
+ 'columnOverflow' :[],
+ 'columnHighlight' :[],
+ 'columnCoords' :[],
+ 'columnLeftBorder' :[],
+ 'columnRightBorder':[],
+ 'columnTopBorder' :[],
+ 'columnBottomBorder':[],
+ 'headerList': [[]],
+ }
+ secType = self.definedSectionTypes[sectionType]
+
+ secType['columnSizes'].append(minSize)
+ secType['columnAligns'].append(align)
+ secType['columnOverflow'].append(overflow)
+ secType['columnHighlight'].append(highlight)
+ secType['columnLeftBorder'].append(leftBorder)
+ secType['columnRightBorder'].append(rightBorder)
+ secType['columnTopBorder'].append(topBorder)
+ secType['columnBottomBorder'].append(bottomBorder)
+
+ # ==========================================================================
+ # Section level functions
+ # ==========================================================================
+
+ # --------------------------------------------------------------------------
+ # Define a section header
+ # --------------------------------------------------------------------------
+ def addHeader(self, heading, align, startColumn, endColumn,
+ leftBorder=0, rightBorder=0, sectionType="default"):
+ """
+ Adds a column header to one or more columns
+
+ @param heading: The text to display
+ @param align: The alignment to apply to the header text
+ LEFT, RIGHT, CENTER are defined in this module
+ @param startColumn: The starting column number (starts at 0) for the header
+ @param endColumn: The ending column number for the header
+ @param leftBorder: The width of the left side border in points.
+ @param rightBorder: The width of the right side border in points.
+ @param sectionType: The name of the section to which this header should be added.
+ """
+ secType = self.definedSectionTypes[sectionType]
+ if endColumn > len(secType['columnSizes'])-1:
+ print "endColumn longer than defined columns"
+ sys.exit()
+
+ heading = {'text':heading,
+ 'align':align,
+ 'start':startColumn,
+ 'end':endColumn,
+ 'leftBorder':leftBorder,
+ 'rightBorder':rightBorder,
+ }
+ secType['headerList'][-1].append(heading)
+
+ # --------------------------------------------------------------------------
+ # Add a row to a header
+ # --------------------------------------------------------------------------
+ def addHeaderRow(self, sectionType="default"):
+ """
+ Adds a new row to the header. Subsequent calls to addHeader will now
+ apply to this new row.
+
+ @param sectionType: The name of the section to which this header row will be added.
+ """
+ secType = self.definedSectionTypes[sectionType]
+ secType['headerList'].append([])
+
+ # --------------------------------------------------------------------------
+ # Inform the writer to switch to a new section
+ # --------------------------------------------------------------------------
+ def startNewSection(self, subtitle, sectionType="default", newPage=1):
+ """
+ Begins a new report section.
+
+ @param subtitle: The subtitle to display above the section
+ @param sectionType: The name of the previous defined section to use.
+ @param newPage: If 0 then the new page will be supressed.
+ If 1 (the default) then a new page will be output prior
+ to starting the section.
+ """
+ if sectionType != self._currentSectionType:
+ self.init = 1
+ self._currentSection = self.definedSectionTypes[sectionType]
+ self._currentSectionType = sectionType
+ self.subtitle = subtitle
+ if newPage:
+ self.highlightIndex = 0
+ if self.init:
+ self.init = 0
+ self.beginData()
+ self.continued = 0
+ if newPage:
+ self.newPage()
+ else:
+ self.drawSectionHeading()
+ self.drawTableHeader()
+
+ # ==========================================================================
+ # Data functions
+ # ==========================================================================
+
+ # --------------------------------------------------------------------------
+ # Add data row
+ # --------------------------------------------------------------------------
+ def addRow(self, data, style="Data", displayBorders=True):
+ """
+ Adds a row of data to the current section
+
+ @param data: A list of strings containing the data to add to the current section
+ @param style: The format style to use to render the row.
+ These are currently hardcoded into this class and include
+ Data (default), Subtotal, Total
+ @param displayBorders: Boolean that controls if a borders will be drawn for this
+ row. Makes it easy to produce a "blank" row for spacing
+ output
+ """
+ canvas = self.canvas
+
+ if style == "Total":
+ self.y -= 4 * self.scale
+
+ if style in ("Subtotal","Total"):
+ font, size, tracking = self.fontDefs['subtotalFont']
+ fontWidth = self.subtotalFontWidth
+ else:
+ font, size, tracking = self.fontDefs['dataFont']
+ fontWidth = self.dataFontWidth
+
+ size = size * self.scale
+ tracking = tracking * self.scale
+
+ if self.y - tracking < self.miny:
+ self.newPage()
+
+ highlighted = 0
+ if style == "Data":
+ highlighted = divmod((self.highlightIndex),4)[1]>1
+ self.highlightIndex += 1
+ else:
+ self.highlightIndex = 0 # Reset highlighting after subtotals
+
+ boxy = self.y - (tracking-size)*1.5
+ boxh = tracking
+
+ if style in ("Subtotal","Total"):
+ self.drawHorizBorderedBox(leftmargin, boxy, self.width-leftmargin*2,
+ boxh, self.subtotalColor, lines=lineWidth * self.scale)
+ elif highlighted:
+ # If there is a bottom border we need to not draw over the top
+ # of the previous rows border. Currently minimum must be 1 point
+ adjustment = max([self._currentSection['columnBottomBorder'][0],1])
+ self.drawHorizBorderedBox(leftmargin, boxy, self.width-leftmargin*2,
+ boxh-adjustment, self.highlightColor, lines=0)
+
+ canvas.setFont(font, size)
+
+ i = 0
+ while i < len(data):
+ col = str(data[i])
+ # Find the hlx values (used for column highlight and borders)
+ if i > 0:
+ hlx1 = self._currentSection['columnCoords'][i-1][1]+self.columnGap/2.0
+ else:
+ hlx1 = leftmargin
+
+ if i < len(self._currentSection['columnCoords'])-1:
+ hlx2 = self._currentSection['columnCoords'][i+1][0]-self.columnGap/2.0
+ else:
+ hlx2 = self.width-leftmargin
+
+ # Column highlight support
+ highlightColumn = self._currentSection['columnHighlight'][i]
+ if highlightColumn and not style in ("Subtotal","Total"):
+ if self.highlightIndex==1: # We're on the first column (not 0 due to += above)
+ adjust = lineWidth#*self.scale
+ else:
+ adjust = 0
+ if highlighted:
+ color = colors.Blacker(self.highlightColor,.98)
+ else:
+ color = highlightColumn
+
+ self.drawHorizBorderedBox(hlx1, boxy - adjust,
+ hlx2-hlx1, boxh,
+ color, lines=0)
+
+ # Column border support
+ if displayBorders:
+ leftBorder = self._currentSection['columnLeftBorder'][i]
+ if leftBorder:
+ canvas.setLineWidth(leftBorder*self.scale)
+ canvas.line(hlx1, boxy, hlx1, boxy + boxh)
+
+ rightBorder =self._currentSection['columnRightBorder'][i]
+ if rightBorder:
+ canvas.setLineWidth(rightBorder*self.scale)
+ canvas.line(hlx2, boxy, hlx2, boxy + boxh)
+
+ topBorder =self._currentSection['columnTopBorder'][i]
+ if topBorder:
+ canvas.setLineWidth(topBorder*self.scale)
+ canvas.line(hlx1, boxy + boxh, hlx2, boxy + boxh)
+
+ bottomBorder = self._currentSection['columnBottomBorder'][i]
+ if bottomBorder:
+ canvas.setLineWidth(bottomBorder*self.scale)
+ canvas.line(hlx1, boxy, hlx2, boxy)
+
+ if col:
+ align= self._currentSection['columnAligns'][i]
+ x1, x2 = self._currentSection['columnCoords'][i]
+
+ # Clip text, if needed
+ restore = 0
+ if fontWidth(col, size) > x2-x1:
+ if self._currentSection['columnOverflow'][i]:
+ col = self._currentSection['columnOverflow'][i]
+ else:
+ restore = 1
+ canvas.saveState()
+ path = canvas.beginPath()
+ # Vertical is overkill, but only interested in horizontal
+ path.rect(x1,self.y-tracking, x2-x1, tracking*3)
+ canvas.clipPath(path, stroke=0, fill=0)
+
+ if align == LEFT:
+ canvas.drawString(x1,self.y,col)
+ elif align == RIGHT:
+ canvas.drawRightString(x2,self.y,col)
+ elif align == CENTER:
+ canvas.drawCentredString(x1+(x2-x1)/2.0,self.y,col)
+
+ # Restore from clipping
+ if restore:
+ canvas.restoreState()
+ i += 1
+
+ self.y -= tracking
+
+ def addLine(self, string, borders=None):
+ """
+ Adds a single line of text instead of a row to a table.
+
+ Usefull in reproducing a check register style printout.
+
+ @param string: The string to print
+ @param borders: A list containing the border widths to use on the line.
+ Sequence is in the order Top, Right, Bottom, Left (TRBL)
+ which I believe is how CSS does it
+ """
+ canvas = self.canvas
+
+ font, size, tracking = self.fontDefs['dataFont']
+ fontWidth = self.dataFontWidth
+
+ size = size * self.scale
+ tracking = tracking * self.scale
+
+ if self.y - tracking < self.miny:
+ self.newPage()
+
+ boxy = self.y - (tracking-size)*1.5
+ boxh = tracking
+
+ # Use the same highlighting as the pervious row printed
+ highlighted = divmod((self.highlightIndex-1),4)[1]>1 # Use the same highlighting
+ if highlighted:
+ # If there is a bottom border we need to not draw over the top
+ # of the previous rows border. Currently minimum must be 1 point
+ adjustment = max([self._currentSection['columnBottomBorder'][0],1])
+ self.drawHorizBorderedBox(leftmargin, boxy, self.width-leftmargin*2,
+ boxh-adjustment, self.highlightColor, lines=0)
+
+ # Place a border around the memo line
+ if borders:
+ topBorder, rightBorder, bottomBorder, leftBorder = borders
+
+ # Top
+ if topBorder:
+ canvas.setLineWidth(topBorder*self.scale)
+ canvas.line(leftmargin, boxy + boxh, self.width-leftmargin, boxy + boxh)
+
+ #Right
+ if rightBorder:
+ canvas.setLineWidth(rightBorder*self.scale)
+ canvas.line(self.width-leftmargin, boxy, self.width-leftmargin, boxy + boxh)
+
+ # Bottom
+ if bottomBorder:
+ canvas.setLineWidth(bottomBorder*self.scale)
+ canvas.line(leftmargin, boxy, self.width-leftmargin, boxy)
+ # Left
+ if leftBorder:
+ canvas.setLineWidth(leftBorder*self.scale)
+ canvas.line(leftmargin, boxy, leftmargin, boxy + boxh)
+
+ canvas.setFont(font, size)
+ hlx1 = leftmargin
+
+ if string:
+ # Clip text, if needed
+ restore = False
+ if fontWidth(string, size) > self.width-leftmargin*2:
+ restore = True
+ canvas.saveState()
+ path = canvas.beginPath()
+ # Vertical is overkill, but only interested in horizontal
+ path.rect(leftmargin,self.y-tracking, self.width-leftmargin*2, tracking*3)
+ canvas.clipPath(path, stroke=0, fill=0)
+
+ canvas.drawString(leftmargin,self.y,string)
+
+ # Restore from clipping
+ if restore:
+ canvas.restoreState()
+
+ self.y -= tracking
+
+
+ # ==========================================================================
+ # Private functions
+ # ==========================================================================
+
+ # --------------------------------------------------------------------------
+ # Begin a new page
+ # --------------------------------------------------------------------------
+ def newPage(self):
+ """
+ Private function that creates a new page.
+ """
+ if self.page:
+ self.canvas.showPage()
+
+ self.page += 1
+ self.y = self.height - topmargin
+
+ if self.page == 1:
+ self.drawLargeTitle()
+ else:
+ self.drawSmallTitle()
+
+ self.drawTableHeader()
+ self.footer()
+
+ self.continued = 1
+
+
+ # --------------------------------------------------------------------------
+ # Draw footer
+ # --------------------------------------------------------------------------
+ def footer(self):
+ """
+ Private function that creates the footer containing the time/page #
+ """
+ canvas = self.canvas
+ font, size, tracking = self.fontDefs['footerFont']
+ canvas.setFont(font, size)
+ canvas.drawString(leftmargin, topmargin, self.timestamp)
+ canvas.drawRightString(self.width - leftmargin, topmargin, "Page %s" % self.page)
+ self.miny = topmargin + tracking*2
+
+ # --------------------------------------------------------------------------
+ # Draw full (first-page) header (Title)
+ # --------------------------------------------------------------------------
+ def drawLargeTitle(self):
+ """
+ Private function that creates a full (first page) header on a new page.
+ """
+ canvas = self.canvas
+ self.y -= self.fontDefs['titleFont'][2]
+ for text, fontspec in self._titleList:
+ if text:
+ font, size, tracking = fontspec
+ canvas.setFont(font, size)
+ canvas.drawCentredString(self.width/2.0, self.y, text)
+ self.y -= tracking
+ self.drawSectionHeading()
+
+ # --------------------------------------------------------------------------
+ # Draw short (non-first-page) header (Title)
+ # --------------------------------------------------------------------------
+ def drawSmallTitle(self):
+ """
+ Private function that creates a short ( non first page) header on a new page.
+ """
+ canvas = self.canvas
+ font, size, tracking = self.fontDefs['repeatTitleFont']
+ self. y -= size
+ canvas.setFont(font, size)
+ canvas.drawString(leftmargin, self.y, self._titleList[0][0])
+ canvas.drawRightString(self.width - leftmargin, self.y, self._titleList[1][0])
+ self.y -= tracking
+ self.drawSectionHeading()
+
+ # --------------------------------------------------------------------------
+ # Draw the section header
+ # --------------------------------------------------------------------------
+ def drawSectionHeading(self):
+ """
+ Draws the text that preceeds the section's table.
+ """
+ canvas = self.canvas
+
+ if not self.subtitle:
+ return
+
+ self.y -= self.fontDefs['subtitleFont'][2]
+
+ font, size, tracking = self.fontDefs['subtitleLabelFont']
+
+ text = canvas.beginText(leftmargin, self.y)
+ for l in self.subtitle.split():
+ boldOff = 0
+ if l[0] == '*':
+ l = l[1:]
+ font, size, tracking = self.fontDefs['subtitleFont']
+
+ if l[-1] == '*':
+ boldOff = 1
+ l = l[:-1]
+
+ text.setFont(font, size)
+ text.textOut(l+ ' ')
+ if boldOff:
+ font, size, tracking = self.fontDefs['subtitleLabelFont']
+ text.textOut(' ')
+
+ if self.continued:
+ font2, size2, tracking2 = self.fontDefs['subtitleContinueFont']
+ text.setFont(font2, size2)
+ text.textOut("(Continued)")
+
+ canvas.drawText(text)
+ self.y -= tracking*2
+
+
+ # --------------------------------------------------------------------------
+ # Draw table header
+ # --------------------------------------------------------------------------
+ def drawTableHeader(self):
+ """
+ Generates a section's table header.
+ """
+ canvas = self.canvas
+
+ numRows = len(self._currentSection['headerList'])
+
+ font, size, tracking = self.fontDefs['tableHeaderFont']
+ size = size * self.scale
+ tracking = tracking * self.scale
+ canvas.setFont(font, size)
+
+ boxy = self.y + tracking - (tracking-size)/2.0
+ boxh = -tracking*numRows - (tracking-size)/2.0 - lineWidth*self.scale
+ self.drawHorizBorderedBox(leftmargin, boxy, self.width-leftmargin*2,
+ boxh, self.headingColor)
+
+ for list in self._currentSection['headerList']:
+ for header in list:
+ c1 = header['start']
+ c2 = header['end']
+ x1 = self._currentSection['columnCoords'][c1][0]
+ x2 = self._currentSection['columnCoords'][c2][1]
+ align = header['align']
+ text = header['text']
+
+ canvas.saveState()
+ path = canvas.beginPath()
+ # Vertical is overkill, but only interested in horizontal
+ path.rect(x1,self.y-tracking, x2-x1, tracking*3)
+ canvas.clipPath(path, stroke=0, fill=0)
+
+ if align == LEFT:
+ canvas.drawString(x1,self.y,text)
+ elif align == RIGHT:
+ canvas.drawRightString(x2,self.y,text)
+ elif align == CENTER:
+ canvas.drawCentredString(x1+(x2-x1)/2.0,self.y,text)
+ canvas.restoreState()
+
+ leftBorder = header['leftBorder']
+ if leftBorder:
+ canvas.setLineWidth(leftBorder*self.scale)
+ canvas.line(x1-self.columnGap/2.0, boxy, x1-self.columnGap/2.0, boxy + boxh)
+
+ rightBorder = header['rightBorder']
+ if rightBorder:
+ canvas.setLineWidth(rightBorder*self.scale)
+ canvas.line(x2+self.columnGap/2.0, boxy, x2+self.columnGap/2.0, boxy + boxh)
+ self.y -= tracking
+
+
+
+ # --------------------------------------------------------------------------
+ # Draws a box w/shading and a top/bottom border
+ # --------------------------------------------------------------------------
+ def drawHorizBorderedBox(self, x, y, w, h, color, lines=lineWidth):
+ canvas = self.canvas
+ canvas.saveState()
+ canvas.setFillColor(color)
+ canvas.rect(x,y,w,h, stroke=0,fill=1)
+ if lines:
+ canvas.setLineWidth(lines)
+ canvas.line(x,y,x+w,y)
+ canvas.line(x,y+h,x+w,y+h)
+ canvas.restoreState()
+
+ # --------------------------------------------------------------------------
+ # Initialize report section
+ # --------------------------------------------------------------------------
+ def beginData(self):
+ """
+ Prepares the class to begin drawing a section on the report. Figures out
+ the required orientation of the report as well as any scaling that is required
+ """
+ # Calculate column sizes
+ totalCols = 0
+ for cs in self._currentSection['columnSizes']:
+ totalCols += cs
+
+ # Figure out the page orientation/scaling
+ if not self._scalingComplete:
+ self._scalingComplete = 1
+ self.pageSize = letter
+ self.scale = 1.0
+ if totalCols < maxColsForPortraitNonscaled: # Guestimate of max # cols we can get on portrait
+ self.pageOrient = portrait
+ self.width, self.height = letter
+ elif totalCols < maxColsForPortraitScaled:
+ self.pageOrient = portrait
+ self.width, self.height = letter
+ self.scale = maxColsForPortraitNonscaled / float(totalCols)
+ elif totalCols < maxColsForLandscapeNonscaled:
+ self.pageOrient = landscape
+ self.height, self.width = letter
+ else:
+ self.pageOrient = landscape
+ self.height, self.width = letter
+ self.scale = maxColsForLandscapeNonscaled / float(totalCols)
+
+ if self.scale < 1:
+ print "Scaling to %.2f%%" % (self.scale*100)
+
+ # in pts, Amount of gap to leave between columns...
+ # doesn't need to be too much
+ self.columnGap = self.columnGap * self.scale
+
+ if isinstance(self.destination, canvas.Canvas):
+ self.canvas = self.destination
+ else:
+ self.canvas = canvas.Canvas(self.destination,
+ pagesize=self.pageOrient(self.pageSize))
+
+ font, size, leading = self.fontDefs['dataFont']
+ self.dataFontWidth = getFont(font).stringWidth
+
+ font, size, leading = self.fontDefs['subtotalFont']
+ self.subtotalFontWidth = getFont(font).stringWidth
+
+ # This is not scaled down according to self.scale...
+ # we'll instead scale the point sizes down when we do setFont
+ usableHorizSpace = (self.width - 2*leftmargin - self.columnGap*(len(self._currentSection['columnSizes'])))
+ x = leftmargin + self.columnGap/2.0
+ for i in range(len(self._currentSection['columnSizes'])):
+ colSize = (self._currentSection['columnSizes'][i] / float(totalCols) * usableHorizSpace)
+ self._currentSection['columnCoords'].append ( ( x, x+colSize ) )
+ x += colSize + self.columnGap
+
+ def setHighlightColorHex(self, hexColor):
+ self.highlightColor = colors.HexColor(hexColor)
+
+ def setFont(self, fontStyle, settings):
+ assert fontStyle in self.fontDefs.keys(), 'Invalid font style: %s'
+
+ self.fontDefs[fontStyle] = settings
Added: printing/pdftable/sample.py
===================================================================
--- printing/pdftable/sample.py (rev 0)
+++ printing/pdftable/sample.py 2009-10-06 14:49:09 UTC (rev 289)
@@ -0,0 +1,266 @@
+#!/usr/bin/env python
+#
+# This file is part of GNU Enterprise.
+#
+# GNU Enterprise is free software; you can redistribute it
+# and/or modify it under the terms of the GNU General Public
+# License as published by the Free Software Foundation; either
+# version 2, or (at your option) any later version.
+#
+# GNU Enterprise 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 the GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public
+# License along with program; see the file COPYING. If not,
+# write to the Free Software Foundation, Inc., 59 Temple Place
+# - Suite 330, Boston, MA 02111-1307, USA.
+#
+# Copyright 2004-2009 Free Software Foundation
+#
+# FILE:
+# pdftable/sample.py
+#
+# DESCRIPTION:
+"""
+A sample application showing how to use pdftable
+"""
+
+# GNUe Modules
+from gnue.common.apps.GClientApp import *
+from gnue.common.printing.pdftable import *
+
+#
+# Sample
+#
+# Primay class of this application
+#
+class Sample(GClientApp):
+ VERSION = "1.0.0"
+ COMMAND = "sample"
+ NAME = "sample"
+ USAGE = GClientApp.USAGE
+ SUMMARY = """
+Generates a sample report using gnue.common.printing.pdftable
+"""
+
+ def __init__(self):
+ GClientApp.__init__(self)
+
+ def run(self):
+ # ------------------------------------------------------------------------
+ # Create the output file
+ # ------------------------------------------------------------------------
+ f = open('output.pdf','w')
+ writer = pdftable(f)
+
+
+ writer.setFullTitle([("Quarterly Sales Report", writer.fontDefs['titleFont']),
+ ('2002 - 2004', writer.fontDefs['title2Font']),
+ ])
+
+ # ========================================================================
+ # Main section of the report
+ # ========================================================================
+
+ # ------------------------------------------------------------------------
+ # Define this sections columns
+ # ------------------------------------------------------------------------
+ writer.addColumn(CENTER,7)
+
+ # Loop through creating columns for each quarter
+ for q in range(4): # q isn't used
+ # 2002 count and %
+ writer.addColumn(RIGHT, 5, leftBorder=.25)
+ writer.addColumn(RIGHT, 7)
+ # 2003 count and %
+ writer.addColumn(RIGHT, 5,
+ highlight=colors.Whiter(writer.highlightColor,.3))
+ writer.addColumn(RIGHT, 7,
+ highlight=colors.Whiter(writer.highlightColor,.3))
+ # 2004 count and %
+ writer.addColumn(RIGHT, 5)
+ writer.addColumn(RIGHT, 7)
+
+ # ------------------------------------------------------------------------
+ # Populate this section's header
+ # ------------------------------------------------------------------------
+
+ # Quarter heading
+ writer.addHeader('Q1',CENTER,startColumn=1, endColumn=6)
+ writer.addHeader('Q2',CENTER,startColumn=7, endColumn=12, leftBorder=.25)
+ writer.addHeader('Q3',CENTER,startColumn=13,endColumn=18, leftBorder=.25, rightBorder=.25)
+ writer.addHeader('Q4',CENTER,startColumn=19,endColumn=24)
+
+ # Repeating year columns
+ writer.addHeaderRow()
+
+ for q in range(4):
+ offset=q*6 # Adjust for 3 years, 2 columns per year for each quarter
+ writer.addHeader("2002" , CENTER , 1+offset, 2+offset)
+ writer.addHeader("2003" , CENTER , 3+offset, 4+offset)
+ writer.addHeader("2004" , CENTER , 5+offset, 6+offset)
+
+ # The count and % columns
+ writer.addHeaderRow()
+
+ writer.addHeader("Model",CENTER,0,0, rightBorder=.25)
+ for q in range(4):
+ offset=q*6 # Adjust for 3 years, 2 columns per year for each quarter
+ print offset
+ writer.addHeader('#',CENTER, 1+offset,1+offset)
+ writer.addHeader('%',CENTER, 2+offset,2+offset)
+ writer.addHeader('#',CENTER, 3+offset,3+offset)
+ writer.addHeader('%',CENTER, 4+offset,4+offset)
+ writer.addHeader('#',CENTER, 5+offset,5+offset)
+ writer.addHeader('%',CENTER, 6+offset,6+offset)
+
+ # ========================================================================
+ # Second section of the report
+ # ========================================================================
+
+ # ------------------------------------------------------------------------
+ # Define this sections columns
+ # ------------------------------------------------------------------------
+ for counter in range(4):
+ writer.addColumn(LEFT, 5, sectionType="topSales")
+ writer.addColumn(LEFT, 15, sectionType="topSales")
+
+ # ------------------------------------------------------------------------
+ # Populate this section's header
+ # ------------------------------------------------------------------------
+ writer.addHeader('Q1',CENTER, 0, 1, rightBorder=.25, sectionType="topSales")
+ writer.addHeader('Q2',CENTER, 2, 3, rightBorder=.25, sectionType="topSales")
+ writer.addHeader('Q3',CENTER, 4, 5, rightBorder=.25, sectionType="topSales")
+ writer.addHeader('Q4',CENTER, 6, 7, sectionType="topSales")
+
+ writer.addHeaderRow(sectionType="topSales")
+ writer.addHeader('Year',LEFT,0,0,sectionType="topSales")
+ writer.addHeader('Salesperson',LEFT,1,1,sectionType="topSales")
+ writer.addHeader('Year',LEFT,2,2,sectionType="topSales")
+ writer.addHeader('Salesperson',LEFT,3,3,sectionType="topSales")
+ writer.addHeader('Year',LEFT,4,4,sectionType="topSales")
+ writer.addHeader('Salesperson',LEFT,5,5,sectionType="topSales")
+ writer.addHeader('Year',LEFT,6,6,sectionType="topSales")
+ writer.addHeader('Salesperson',LEFT,7,7,sectionType="topSales")
+
+
+ # ========================================================================
+ # Populate report with data
+ # ========================================================================
+
+ storeInfo = [{'location' :'Manhattan, KS',
+ 'modelLines' : 15, # Number of bogus models to create
+ },
+ {'location' :'Memphis, TN',
+ 'modelLines' : 65, # Number of bogus models to create
+ },
+ ]
+
+ for store in storeInfo:
+ # ----------------------------------------------------------------------
+ # Main section of the store's report
+ # ----------------------------------------------------------------------
+
+ # Start the section
+ writer.startNewSection("%s Store" % store['location'])
+
+ # Fill with data
+ for row in self._generateSalesData(store['modelLines']):
+ writer.addRow(row)
+# writer.addLine("Test Line", borders=[0,.25,.25,.25])
+
+ # We won't compute totals in this sample
+ writer.addRow(["Total 1"]+['0']*24, style='Subtotal')
+ writer.addRow(["Total 2"]+['0']*24, style='Total')
+
+ # ----------------------------------------------------------------------
+ # Secondard section
+ # ----------------------------------------------------------------------
+
+ # Start the new section, suppress the automatic new page at section start
+ writer.startNewSection("Top Sales Personel - Last 5 years",sectionType="topSales", newPage=0)
+
+ # Fill with data
+ for row in self._generateTopSalespersons(5):
+ writer.addRow(row)
+
+ writer.close()
+ f.close()
+
+
+ # ==========================================================================
+ # Functions to define bogus data for the sample
+ # ==========================================================================
+ def _generateSalesData(self,lines):
+ """
+ Simple function to generate random sales data for the sample report.
+
+ @param lines: The number of lines of model sales data to generate
+ """
+ import random
+ data = []
+ for count in range(lines):
+ row = []
+ # Build the model number
+ letterOffset, number = divmod(count,10)
+ model = "%s%s" % (chr(65+letterOffset),number)
+ row.append(model)
+
+ # Randomly create quarterly sales data for 3 years, set % to 0 for now
+ for c in range(12):
+ sales = random.randrange(1,100,1)
+ row.append("%s" % sales)
+ row.append("0") # not computing %s in the sample
+
+ # Add the row to the data being returned
+ data.append(row)
+
+ return data
+
+ def _generateTopSalespersons(self,years):
+ """
+ Simple function to generate random salesperson data for the sample report.
+
+ @param years: The number of lines of salesperson data to generate
+ """
+ import random
+ names=['Tom Barker','Linda Carter','Willy Sellut','Peter Smith','Chilly Willy','Betty Bip',
+ 'Mark Bently','Maynard Thomas','Sally Watson','Tammy Kingston','Shelly White',
+ 'Rose Parkson','Blair Thomas',
+ ]
+
+ data = []
+ for year in range(2004-years, 2004):
+ row1 = []
+ row2 = []
+ row3 = []
+ for q in range(4):
+ # Python 2.3 only :(
+ #people = random.sample(names,3)
+
+ people = {}
+ while len(people) < 3:
+ people[names[random.randint(1,len(names)-1)]] = ''
+ people = people.keys()
+
+ row1.append("%s" % year)
+ row1.append(people[0])
+ row2.append("%s" % year)
+ row2.append(people[1])
+ row3.append("%s" % year)
+ row3.append(people[2])
+ data.append(row1)
+ data.append(row2)
+ data.append(row3)
+ return data
+
+# ============================================================================
+# Run the sample report
+# ============================================================================
+if __name__ == '__main__':
+ Sample().run()
+
+
+
Property changes on: printing/pdftable/sample.py
___________________________________________________________________
Name: svn:executable
+ *
lmpx.com only provides a reader for public news (NNTP) servers. It is not
affiliated with the servers or forums shown here and is not responsible for
the content of articles, which is written by their respective authors.