From ac5f8444449ad5cae6a7ca5f4c7e313c9caa9fdb Mon Sep 17 00:00:00 2001 From: pinky <> Date: Sun, 7 Jan 2007 23:34:45 +0000 Subject: [PATCH] RL2 bzr revid: pinky-2e44d4c158101b4eb29c9fb7cee91c9aaabfed06 --- bin/reportlab/lib/PyFontify.py | 155 ++++++ bin/reportlab/lib/__init__.py | 7 + bin/reportlab/lib/abag.py | 44 ++ bin/reportlab/lib/attrmap.py | 138 ++++++ bin/reportlab/lib/codecharts.py | 365 ++++++++++++++ bin/reportlab/lib/colors.py | 565 ++++++++++++++++++++++ bin/reportlab/lib/corp.py | 452 +++++++++++++++++ bin/reportlab/lib/enums.py | 11 + bin/reportlab/lib/extformat.py | 81 ++++ bin/reportlab/lib/fonts.py | 89 ++++ bin/reportlab/lib/formatters.py | 100 ++++ bin/reportlab/lib/logger.py | 61 +++ bin/reportlab/lib/normalDate.py | 605 +++++++++++++++++++++++ bin/reportlab/lib/pagesizes.py | 55 +++ bin/reportlab/lib/randomtext.py | 348 ++++++++++++++ bin/reportlab/lib/rltempfile.py | 29 ++ bin/reportlab/lib/rparsexml.py | 431 +++++++++++++++++ bin/reportlab/lib/sequencer.py | 284 +++++++++++ bin/reportlab/lib/set_ops.py | 38 ++ bin/reportlab/lib/styles.py | 257 ++++++++++ bin/reportlab/lib/textsplit.py | 210 ++++++++ bin/reportlab/lib/tocindex.py | 294 ++++++++++++ bin/reportlab/lib/units.py | 23 + bin/reportlab/lib/utils.py | 827 ++++++++++++++++++++++++++++++++ bin/reportlab/lib/validators.py | 324 +++++++++++++ bin/reportlab/lib/xmllib.py | 769 +++++++++++++++++++++++++++++ bin/reportlab/lib/yaml.py | 188 ++++++++ 27 files changed, 6750 insertions(+) create mode 100644 bin/reportlab/lib/PyFontify.py create mode 100644 bin/reportlab/lib/__init__.py create mode 100644 bin/reportlab/lib/abag.py create mode 100644 bin/reportlab/lib/attrmap.py create mode 100644 bin/reportlab/lib/codecharts.py create mode 100644 bin/reportlab/lib/colors.py create mode 100644 bin/reportlab/lib/corp.py create mode 100644 bin/reportlab/lib/enums.py create mode 100644 bin/reportlab/lib/extformat.py create mode 100644 bin/reportlab/lib/fonts.py create mode 100644 bin/reportlab/lib/formatters.py create mode 100644 bin/reportlab/lib/logger.py create mode 100644 bin/reportlab/lib/normalDate.py create mode 100644 bin/reportlab/lib/pagesizes.py create mode 100644 bin/reportlab/lib/randomtext.py create mode 100644 bin/reportlab/lib/rltempfile.py create mode 100644 bin/reportlab/lib/rparsexml.py create mode 100644 bin/reportlab/lib/sequencer.py create mode 100644 bin/reportlab/lib/set_ops.py create mode 100644 bin/reportlab/lib/styles.py create mode 100644 bin/reportlab/lib/textsplit.py create mode 100644 bin/reportlab/lib/tocindex.py create mode 100644 bin/reportlab/lib/units.py create mode 100644 bin/reportlab/lib/utils.py create mode 100644 bin/reportlab/lib/validators.py create mode 100644 bin/reportlab/lib/xmllib.py create mode 100644 bin/reportlab/lib/yaml.py diff --git a/bin/reportlab/lib/PyFontify.py b/bin/reportlab/lib/PyFontify.py new file mode 100644 index 00000000000..309f7740df9 --- /dev/null +++ b/bin/reportlab/lib/PyFontify.py @@ -0,0 +1,155 @@ +"""Module to analyze Python source code; for syntax coloring tools. + +Interface: + tags = fontify(pytext, searchfrom, searchto) + +The 'pytext' argument is a string containing Python source code. +The (optional) arguments 'searchfrom' and 'searchto' may contain a slice in pytext. +The returned value is a list of tuples, formatted like this: + [('keyword', 0, 6, None), ('keyword', 11, 17, None), ('comment', 23, 53, None), etc. ] +The tuple contents are always like this: + (tag, startindex, endindex, sublist) +tag is one of 'keyword', 'string', 'comment' or 'identifier' +sublist is not used, hence always None. +""" + +# Based on FontText.py by Mitchell S. Chapman, +# which was modified by Zachary Roadhouse, +# then un-Tk'd by Just van Rossum. +# Many thanks for regular expression debugging & authoring are due to: +# Tim (the-incredib-ly y'rs) Peters and Cristian Tismer +# So, who owns the copyright? ;-) How about this: +# Copyright 1996-2001: +# Mitchell S. Chapman, +# Zachary Roadhouse, +# Tim Peters, +# Just van Rossum + +__version__ = "0.4" + +import string +import re + +# First a little helper, since I don't like to repeat things. (Tismer speaking) +import string +def replace(where, what, with): + return string.join(string.split(where, what), with) + +# This list of keywords is taken from ref/node13.html of the +# Python 1.3 HTML documentation. ("access" is intentionally omitted.) +keywordsList = [ + "as", "assert", "exec", + "del", "from", "lambda", "return", + "and", "elif", "global", "not", "try", + "break", "else", "if", "or", "while", + "class", "except", "import", "pass", + "continue", "finally", "in", "print", + "def", "for", "is", "raise", "yield"] + +# Build up a regular expression which will match anything +# interesting, including multi-line triple-quoted strings. +commentPat = r"#[^\n]*" + +pat = r"q[^\\q\n]*(\\[\000-\377][^\\q\n]*)*q" +quotePat = replace(pat, "q", "'") + "|" + replace(pat, 'q', '"') + +# Way to go, Tim! +pat = r""" + qqq + [^\\q]* + ( + ( \\[\000-\377] + | q + ( \\[\000-\377] + | [^\q] + | q + ( \\[\000-\377] + | [^\\q] + ) + ) + ) + [^\\q]* + )* + qqq +""" +pat = string.join(string.split(pat), '') # get rid of whitespace +tripleQuotePat = replace(pat, "q", "'") + "|" + replace(pat, 'q', '"') + +# Build up a regular expression which matches all and only +# Python keywords. This will let us skip the uninteresting +# identifier references. +# nonKeyPat identifies characters which may legally precede +# a keyword pattern. +nonKeyPat = r"(^|[^a-zA-Z0-9_.\"'])" + +keyPat = nonKeyPat + "(" + string.join(keywordsList, "|") + ")" + nonKeyPat + +matchPat = commentPat + "|" + keyPat + "|" + tripleQuotePat + "|" + quotePat +matchRE = re.compile(matchPat) + +idKeyPat = "[ \t]*[A-Za-z_][A-Za-z_0-9.]*" # Ident w. leading whitespace. +idRE = re.compile(idKeyPat) + + +def fontify(pytext, searchfrom = 0, searchto = None): + if searchto is None: + searchto = len(pytext) + # Cache a few attributes for quicker reference. + search = matchRE.search + idSearch = idRE.search + + tags = [] + tags_append = tags.append + commentTag = 'comment' + stringTag = 'string' + keywordTag = 'keyword' + identifierTag = 'identifier' + + start = 0 + end = searchfrom + while 1: + m = search(pytext, end) + if m is None: + break # EXIT LOOP + start = m.start() + if start >= searchto: + break # EXIT LOOP + match = m.group(0) + end = start + len(match) + c = match[0] + if c not in "#'\"": + # Must have matched a keyword. + if start <> searchfrom: + # there's still a redundant char before and after it, strip! + match = match[1:-1] + start = start + 1 + else: + # this is the first keyword in the text. + # Only a space at the end. + match = match[:-1] + end = end - 1 + tags_append((keywordTag, start, end, None)) + # If this was a defining keyword, look ahead to the + # following identifier. + if match in ["def", "class"]: + m = idSearch(pytext, end) + if m is not None: + start = m.start() + if start == end: + match = m.group(0) + end = start + len(match) + tags_append((identifierTag, start, end, None)) + elif c == "#": + tags_append((commentTag, start, end, None)) + else: + tags_append((stringTag, start, end, None)) + return tags + + +def test(path): + f = open(path) + text = f.read() + f.close() + tags = fontify(text) + for tag, start, end, sublist in tags: + print tag, `text[start:end]` \ No newline at end of file diff --git a/bin/reportlab/lib/__init__.py b/bin/reportlab/lib/__init__.py new file mode 100644 index 00000000000..22d3f38c1b7 --- /dev/null +++ b/bin/reportlab/lib/__init__.py @@ -0,0 +1,7 @@ +#!/bin/env python +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/__init__.py +__version__=''' $Id: __init__.py 2385 2004-06-17 15:26:05Z rgbecker $ ''' +import os +RL_DEBUG = os.environ.has_key('RL_DEBUG') \ No newline at end of file diff --git a/bin/reportlab/lib/abag.py b/bin/reportlab/lib/abag.py new file mode 100644 index 00000000000..4c3c029a8d0 --- /dev/null +++ b/bin/reportlab/lib/abag.py @@ -0,0 +1,44 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/abag.py +__version__=''' $Id: abag.py 2385 2004-06-17 15:26:05Z rgbecker $ ''' + +class ABag: + """ + 'Attribute Bag' - a trivial BAG class for holding attributes. + + You may initialize with keyword arguments. + a = ABag(k0=v0,....,kx=vx,....) ==> getattr(a,'kx')==vx + + c = a.clone(ak0=av0,.....) copy with optional additional attributes. + """ + def __init__(self,**attr): + for k,v in attr.items(): + setattr(self,k,v) + + def clone(self,**attr): + n = apply(ABag,(),self.__dict__) + if attr != {}: apply(ABag.__init__,(n,),attr) + return n + + def __repr__(self): + import string + n = self.__class__.__name__ + L = [n+"("] + keys = self.__dict__.keys() + for k in keys: + v = getattr(self, k) + rk = repr(k) + rv = repr(v) + rk = " "+string.replace(rk, "\n", "\n ") + rv = " "+string.replace(rv, "\n", "\n ") + L.append(rk) + L.append(rv) + L.append(") #"+n) + return string.join(L, "\n") + +if __name__=="__main__": + AB = ABag(a=1, c="hello") + CD = AB.clone() + print AB + print CD \ No newline at end of file diff --git a/bin/reportlab/lib/attrmap.py b/bin/reportlab/lib/attrmap.py new file mode 100644 index 00000000000..2f8e89c13f8 --- /dev/null +++ b/bin/reportlab/lib/attrmap.py @@ -0,0 +1,138 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/attrmap.py +__version__=''' $Id: attrmap.py 2741 2005-12-07 21:52:33Z andy $ ''' +from UserDict import UserDict +from reportlab.lib.validators import isAnything, _SequenceTypes, DerivedValue +from reportlab import rl_config + +class CallableValue: + '''a class to allow callable initial values''' + def __init__(self,func,*args,**kw): + #assert iscallable(func) + self.func = func + self.args = args + self.kw = kw + + def __call__(self): + return apply(self.func,self.args,self.kw) + +class AttrMapValue: + '''Simple multi-value holder for attribute maps''' + def __init__(self,validate=None,desc=None,initial=None, **kw): + self.validate = validate or isAnything + self.desc = desc + self._initial = initial + for k,v in kw.items(): + setattr(self,k,v) + + def __getattr__(self,name): + #hack to allow callable initial values + if name=='initial': + if isinstance(self._initial,CallableValue): return self._initial() + return self._initial + elif name=='hidden': + return 0 + raise AttributeError, name + +class AttrMap(UserDict): + def __init__(self,BASE=None,UNWANTED=[],**kw): + data = {} + if BASE: + if isinstance(BASE,AttrMap): + data = BASE.data #they used BASECLASS._attrMap + else: + if type(BASE) not in (type(()),type([])): BASE = (BASE,) + for B in BASE: + if hasattr(B,'_attrMap'): + data.update(getattr(B._attrMap,'data',{})) + else: + raise ValueError, 'BASE=%s has wrong kind of value' % str(B) + + UserDict.__init__(self,data) + self.remove(UNWANTED) + self.data.update(kw) + + def update(self,kw): + if isinstance(kw,AttrMap): kw = kw.data + self.data.update(kw) + + def remove(self,unwanted): + for k in unwanted: + try: + del self[k] + except KeyError: + pass + + def clone(self,UNWANTED=[],**kw): + c = AttrMap(BASE=self,UNWANTED=UNWANTED) + c.update(kw) + return c + +def validateSetattr(obj,name,value): + '''validate setattr(obj,name,value)''' + if rl_config.shapeChecking: + map = obj._attrMap + if map and name[0]!= '_': + #we always allow the inherited values; they cannot + #be checked until draw time. + if isinstance(value, DerivedValue): + #let it through + pass + else: + try: + validate = map[name].validate + if not validate(value): + raise AttributeError, "Illegal assignment of '%s' to '%s' in class %s" % (value, name, obj.__class__.__name__) + except KeyError: + raise AttributeError, "Illegal attribute '%s' in class %s" % (name, obj.__class__.__name__) + obj.__dict__[name] = value + +def _privateAttrMap(obj,ret=0): + '''clone obj._attrMap if required''' + A = obj._attrMap + oA = getattr(obj.__class__,'_attrMap',None) + if ret: + if oA is A: + return A.clone(), oA + else: + return A, None + else: + if oA is A: + obj._attrMap = A.clone() + +def _findObjectAndAttr(src, P): + '''Locate the object src.P for P a string, return parent and name of attribute + ''' + P = string.split(P, '.') + if len(P) == 0: + return None, None + else: + for p in P[0:-1]: + src = getattr(src, p) + return src, P[-1] + +def hook__setattr__(obj): + if not hasattr(obj,'__attrproxy__'): + C = obj.__class__ + import new + obj.__class__=new.classobj(C.__name__,(C,)+C.__bases__, + {'__attrproxy__':[], + '__setattr__':lambda self,k,v,osa=getattr(obj,'__setattr__',None),hook=hook: hook(self,k,v,osa)}) + +def addProxyAttribute(src,name,validate=None,desc=None,initial=None,dst=None): + ''' + Add a proxy attribute 'name' to src with targets dst + ''' + #sanity + assert hasattr(src,'_attrMap'), 'src object has no _attrMap' + A, oA = _privateAttrMap(src,1) + if type(dst) not in _SequenceTypes: dst = dst, + D = [] + DV = [] + for d in dst: + if type(d) in _SequenceTypes: + d, e = d[0], d[1:] + obj, attr = _findObjectAndAttr(src,d) + if obj: + dA = getattr(obj,'_attrMap',None) diff --git a/bin/reportlab/lib/codecharts.py b/bin/reportlab/lib/codecharts.py new file mode 100644 index 00000000000..3d8630364b8 --- /dev/null +++ b/bin/reportlab/lib/codecharts.py @@ -0,0 +1,365 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/codecharts.py +#$Header $ +__version__=''' $Id ''' +__doc__="""Routines to print code page (character set) drawings. + +To be sure we can accurately represent characters in various encodings +and fonts, we need some routines to display all those characters. +These are defined herein. The idea is to include flowable, drawable +and graphic objects for single and multi-byte fonts. """ +import string +import codecs + +from reportlab.pdfgen.canvas import Canvas +from reportlab.platypus import Flowable +from reportlab.pdfbase import pdfmetrics, cidfonts +from reportlab.graphics.shapes import Drawing, Group, String, Circle, Rect +from reportlab.graphics.widgetbase import Widget +from reportlab.lib import colors + +adobe2codec = { + 'WinAnsiEncoding':'winansi', + 'MacRomanEncoding':'macroman', + 'MacExpert':'macexpert', + 'PDFDoc':'pdfdoc', + + } + +class CodeChartBase(Flowable): + """Basic bits of drawing furniture used by + single and multi-byte versions: ability to put letters + into boxes.""" + + def calcLayout(self): + "Work out x and y positions for drawing" + + + rows = self.codePoints * 1.0 / self.charsPerRow + if rows == int(rows): + self.rows = int(rows) + else: + self.rows = int(rows) + 1 + # size allows for a gray column of labels + self.width = self.boxSize * (1+self.charsPerRow) + self.height = self.boxSize * (1+self.rows) + + #handy lists + self.ylist = [] + for row in range(self.rows + 2): + self.ylist.append(row * self.boxSize) + self.xlist = [] + for col in range(self.charsPerRow + 2): + self.xlist.append(col * self.boxSize) + + def formatByte(self, byt): + if self.hex: + return '%02X' % byt + else: + return '%d' % byt + + def drawChars(self, charList): + """Fills boxes in order. None means skip a box. + Empty boxes at end get filled with gray""" + extraNeeded = (self.rows * self.charsPerRow - len(charList)) + for i in range(extraNeeded): + charList.append(None) + #charList.extend([None] * extraNeeded) + row = 0 + col = 0 + self.canv.setFont(self.fontName, self.boxSize * 0.75) + for ch in charList: # may be 2 bytes or 1 + if ch is None: + self.canv.setFillGray(0.9) + self.canv.rect((1+col) * self.boxSize, (self.rows - row - 1) * self.boxSize, + self.boxSize, self.boxSize, stroke=0, fill=1) + self.canv.setFillGray(0.0) + else: + try: + self.canv.drawCentredString( + (col+1.5) * self.boxSize, + (self.rows - row - 0.875) * self.boxSize, + ch, + ) + except: + self.canv.setFillGray(0.9) + self.canv.rect((1+col) * self.boxSize, (self.rows - row - 1) * self.boxSize, + self.boxSize, self.boxSize, stroke=0, fill=1) + self.canv.drawCentredString( + (col+1.5) * self.boxSize, + (self.rows - row - 0.875) * self.boxSize, + '?', + ) + self.canv.setFillGray(0.0) + col = col + 1 + if col == self.charsPerRow: + row = row + 1 + col = 0 + + def drawLabels(self, topLeft = ''): + """Writes little labels in the top row and first column""" + self.canv.setFillGray(0.8) + self.canv.rect(0, self.ylist[-2], self.width, self.boxSize, fill=1, stroke=0) + self.canv.rect(0, 0, self.boxSize, self.ylist[-2], fill=1, stroke=0) + self.canv.setFillGray(0.0) + + #label each row and column + self.canv.setFont('Helvetica-Oblique',0.375 * self.boxSize) + byt = 0 + for row in range(self.rows): + if self.rowLabels: + label = self.rowLabels[row] + else: # format start bytes as hex or decimal + label = self.formatByte(row * self.charsPerRow) + self.canv.drawCentredString(0.5 * self.boxSize, + (self.rows - row - 0.75) * self.boxSize, + label + ) + for col in range(self.charsPerRow): + self.canv.drawCentredString((col + 1.5) * self.boxSize, + (self.rows + 0.25) * self.boxSize, + self.formatByte(col) + ) + + if topLeft: + self.canv.setFont('Helvetica-BoldOblique',0.5 * self.boxSize) + self.canv.drawCentredString(0.5 * self.boxSize, + (self.rows + 0.25) * self.boxSize, + topLeft + ) + +class SingleByteEncodingChart(CodeChartBase): + def __init__(self, faceName='Helvetica', encodingName='WinAnsiEncoding', + charsPerRow=16, boxSize=14, hex=1): + self.codePoints = 256 + self.faceName = faceName + self.encodingName = encodingName + self.fontName = self.faceName + '-' + self.encodingName + self.charsPerRow = charsPerRow + self.boxSize = boxSize + self.hex = hex + self.rowLabels = None + pdfmetrics.registerFont(pdfmetrics.Font(self.fontName, + self.faceName, + self.encodingName) + ) + + self.calcLayout() + + + def draw(self): + self.drawLabels() + charList = [None] * 32 + map(chr, range(32, 256)) + + #we need to convert these to Unicode, since ReportLab + #2.0 can only draw in Unicode. + + encName = self.encodingName + #apply some common translations + encName = adobe2codec.get(encName, encName) + decoder = codecs.lookup(encName)[1] + def decodeFunc(txt): + if txt is None: + return None + else: + return decoder(txt, errors='replace')[0] + + charList = [decodeFunc(ch) for ch in charList] + + + + self.drawChars(charList) + self.canv.grid(self.xlist, self.ylist) + + +class KutenRowCodeChart(CodeChartBase): + """Formats one 'row' of the 94x94 space used in many Asian encodings.aliases + + These deliberately resemble the code charts in Ken Lunde's "Understanding + CJKV Information Processing", to enable manual checking. Due to the large + numbers of characters, we don't try to make one graphic with 10,000 characters, + but rather output a sequence of these.""" + #would be cleaner if both shared one base class whose job + #was to draw the boxes, but never mind... + def __init__(self, row, faceName, encodingName): + self.row = row + self.codePoints = 94 + self.boxSize = 18 + self.charsPerRow = 20 + self.rows = 5 + self.rowLabels = ['00','20','40','60','80'] + self.hex = 0 + self.faceName = faceName + self.encodingName = encodingName + + try: + # the dependent files might not be available + font = cidfonts.CIDFont(self.faceName, self.encodingName) + pdfmetrics.registerFont(font) + except: + # fall back to English and at least shwo we can draw the boxes + self.faceName = 'Helvetica' + self.encodingName = 'WinAnsiEncoding' + self.fontName = self.faceName + '-' + self.encodingName + self.calcLayout() + + def makeRow(self, row): + """Works out the character values for this kuten row""" + cells = [] + if string.find(self.encodingName, 'EUC') > -1: + # it is an EUC family encoding. + for col in range(1, 95): + ch = chr(row + 160) + chr(col+160) + cells.append(ch) +## elif string.find(self.encodingName, 'GB') > -1: +## # it is an EUC family encoding. +## for col in range(1, 95): +## ch = chr(row + 160) + chr(col+160) + else: + cells.append([None] * 94) + return cells + + def draw(self): + self.drawLabels(topLeft= 'R%d' % self.row) + + # work out which characters we need for the row + #assert string.find(self.encodingName, 'EUC') > -1, 'Only handles EUC encoding today, you gave me %s!' % self.encodingName + + # pad out by 1 to match Ken Lunde's tables + charList = [None] + self.makeRow(self.row) + self.drawChars(charList) + self.canv.grid(self.xlist, self.ylist) + + +class Big5CodeChart(CodeChartBase): + """Formats one 'row' of the 94x160 space used in Big 5 + + These deliberately resemble the code charts in Ken Lunde's "Understanding + CJKV Information Processing", to enable manual checking.""" + def __init__(self, row, faceName, encodingName): + self.row = row + self.codePoints = 160 + self.boxSize = 18 + self.charsPerRow = 16 + self.rows = 10 + self.hex = 1 + self.faceName = faceName + self.encodingName = encodingName + self.rowLabels = ['4','5','6','7','A','B','C','D','E','F'] + try: + # the dependent files might not be available + font = cidfonts.CIDFont(self.faceName, self.encodingName) + pdfmetrics.registerFont(font) + except: + # fall back to English and at least shwo we can draw the boxes + self.faceName = 'Helvetica' + self.encodingName = 'WinAnsiEncoding' + self.fontName = self.faceName + '-' + self.encodingName + self.calcLayout() + + def makeRow(self, row): + """Works out the character values for this Big5 row. + Rows start at 0xA1""" + cells = [] + if string.find(self.encodingName, 'B5') > -1: + # big 5, different row size + for y in [4,5,6,7,10,11,12,13,14,15]: + for x in range(16): + col = y*16+x + ch = chr(row) + chr(col) + cells.append(ch) + + else: + cells.append([None] * 160) + return cells + + def draw(self): + self.drawLabels(topLeft='%02X' % self.row) + + charList = self.makeRow(self.row) + self.drawChars(charList) + self.canv.grid(self.xlist, self.ylist) + + +def hBoxText(msg, canvas, x, y, fontName): + """Helper for stringwidth tests on Asian fonts. + + Registers font if needed. Then draws the string, + and a box around it derived from the stringWidth function""" + canvas.saveState() + try: + font = pdfmetrics.getFont(fontName) + except KeyError: + font = cidfonts.UnicodeCIDFont(fontName) + pdfmetrics.registerFont(font) + + canvas.setFillGray(0.8) + canvas.rect(x,y,pdfmetrics.stringWidth(msg, fontName, 16),16,stroke=0,fill=1) + canvas.setFillGray(0) + canvas.setFont(fontName, 16,16) + canvas.drawString(x,y,msg) + canvas.restoreState() + + +class CodeWidget(Widget): + """Block showing all the characters""" + def __init__(self): + self.x = 0 + self.y = 0 + self.width = 160 + self.height = 160 + + def draw(self): + dx = self.width / 16.0 + dy = self.height / 16.0 + g = Group() + g.add(Rect(self.x, self.y, self.width, self.height, + fillColor=None, strokeColor=colors.black)) + for x in range(16): + for y in range(16): + charValue = y * 16 + x + if charValue > 32: + s = String(self.x + x * dx, + self.y + (self.height - y*dy), chr(charValue)) + g.add(s) + return g + + + + + + +def test(): + c = Canvas('codecharts.pdf') + c.setFont('Helvetica-Bold', 24) + c.drawString(72, 750, 'Testing code page charts') + cc1 = SingleByteEncodingChart() + cc1.drawOn(c, 72, 500) + + cc2 = SingleByteEncodingChart(charsPerRow=32) + cc2.drawOn(c, 72, 300) + + cc3 = SingleByteEncodingChart(charsPerRow=25, hex=0) + cc3.drawOn(c, 72, 100) + +## c.showPage() +## +## c.setFont('Helvetica-Bold', 24) +## c.drawString(72, 750, 'Multi-byte Kuten code chart examples') +## KutenRowCodeChart(1, 'HeiseiMin-W3','EUC-H').drawOn(c, 72, 600) +## KutenRowCodeChart(16, 'HeiseiMin-W3','EUC-H').drawOn(c, 72, 450) +## KutenRowCodeChart(84, 'HeiseiMin-W3','EUC-H').drawOn(c, 72, 300) +## +## c.showPage() +## c.setFont('Helvetica-Bold', 24) +## c.drawString(72, 750, 'Big5 Code Chart Examples') +## #Big5CodeChart(0xA1, 'MSungStd-Light-Acro','ETenms-B5-H').drawOn(c, 72, 500) + + c.save() + print 'saved codecharts.pdf' + +if __name__=='__main__': + test() + + diff --git a/bin/reportlab/lib/colors.py b/bin/reportlab/lib/colors.py new file mode 100644 index 00000000000..ed537259e06 --- /dev/null +++ b/bin/reportlab/lib/colors.py @@ -0,0 +1,565 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/colors.py +__version__=''' $Id: colors.py 2587 2005-05-03 11:41:54Z rgbecker $ ''' + +import string, math +from types import StringType, ListType, TupleType +from reportlab.lib.utils import fp_str +_SeqTypes = (ListType,TupleType) + +class Color: + """This class is used to represent color. Components red, green, blue + are in the range 0 (dark) to 1 (full intensity).""" + + def __init__(self, red=0, green=0, blue=0): + "Initialize with red, green, blue in range [0-1]." + self.red, self.green, self.blue = red,green,blue + + def __repr__(self): + return "Color(%s)" % string.replace(fp_str(self.red, self.green, self.blue),' ',',') + + def __hash__(self): + return hash( (self.red, self.green, self.blue) ) + + def __cmp__(self,other): + try: + dsum = 4*self.red-4*other.red + 2*self.green-2*other.green + self.blue-other.blue + except: + return -1 + if dsum > 0: return 1 + if dsum < 0: return -1 + return 0 + + def rgb(self): + "Returns a three-tuple of components" + return (self.red, self.green, self.blue) + + def bitmap_rgb(self): + return tuple(map(lambda x: int(x*255)&255, self.rgb())) + + def hexval(self): + return '0x%02x%02x%02x' % self.bitmap_rgb() + +class CMYKColor(Color): + """This represents colors using the CMYK (cyan, magenta, yellow, black) + model commonly used in professional printing. This is implemented + as a derived class so that renderers which only know about RGB "see it" + as an RGB color through its 'red','green' and 'blue' attributes, according + to an approximate function. + + The RGB approximation is worked out when the object in constructed, so + the color attributes should not be changed afterwards. + + Extra attributes may be attached to the class to support specific ink models, + and renderers may look for these.""" + + def __init__(self, cyan=0, magenta=0, yellow=0, black=0, + spotName=None, density=1, knockout=None): + """ + Initialize with four colors in range [0-1]. the optional + spotName, density & knockout may be of use to specific renderers. + spotName is intended for use as an identifier to the renderer not client programs. + density is used to modify the overall amount of ink. + knockout is a renderer dependent option that determines whether the applied colour + knocksout (removes) existing colour; None means use the global default. + """ + self.cyan = cyan + self.magenta = magenta + self.yellow = yellow + self.black = black + self.spotName = spotName + self.density = max(min(density,1),0) # force into right range + self.knockout = knockout + + # now work out the RGB approximation. override + self.red, self.green, self.blue = cmyk2rgb( (cyan, magenta, yellow, black) ) + + if density<1: + #density adjustment of rgb approximants, effectively mix with white + r, g, b = self.red, self.green, self.blue + r = density*(r-1)+1 + g = density*(g-1)+1 + b = density*(b-1)+1 + self.red, self.green, self.blue = (r,g,b) + + def __repr__(self): + return "CMYKColor(%s%s%s%s)" % ( + string.replace(fp_str(self.cyan, self.magenta, self.yellow, self.black),' ',','), + (self.spotName and (',spotName='+repr(self.spotName)) or ''), + (self.density!=1 and (',density='+fp_str(self.density)) or ''), + (self.knockout is not None and (',knockout=%d' % self.knockout) or ''), + ) + + def __hash__(self): + return hash( (self.cyan, self.magenta, self.yellow, self.black, self.density, self.spotName) ) + + def __cmp__(self,other): + """Partial ordering of colors according to a notion of distance. + + Comparing across the two color models is of limited use.""" + # why the try-except? What can go wrong? + if isinstance(other, CMYKColor): + dsum = (((( (self.cyan-other.cyan)*2 + + (self.magenta-other.magenta))*2+ + (self.yellow-other.yellow))*2+ + (self.black-other.black))*2+ + (self.density-other.density))*2 + cmp(self.spotName or '',other.spotName or '') + else: # do the RGB comparison + try: + dsum = ((self.red-other.red)*2+(self.green-other.green))*2+(self.blue-other.blue) + except: # or just return 'not equal' if not a color + return -1 + if dsum >= 0: + return dsum>0 + else: + return -1 + + def cmyk(self): + "Returns a tuple of four color components - syntactic sugar" + return (self.cyan, self.magenta, self.yellow, self.black) + + def _density_str(self): + return fp_str(self.density) + +class PCMYKColor(CMYKColor): + '''100 based CMYKColor with density and a spotName; just like Rimas uses''' + def __init__(self,cyan,magenta,yellow,black,density=100,spotName=None,knockout=None): + CMYKColor.__init__(self,cyan/100.,magenta/100.,yellow/100.,black/100.,spotName,density/100.,knockout=knockout) + + def __repr__(self): + return "PCMYKColor(%s%s%s%s)" % ( + string.replace(fp_str(self.cyan*100, self.magenta*100, self.yellow*100, self.black*100),' ',','), + (self.spotName and (',spotName='+repr(self.spotName)) or ''), + (self.density!=1 and (',density='+fp_str(self.density*100)) or ''), + (self.knockout is not None and (',knockout=%d' % self.knockout) or ''), + ) + +def cmyk2rgb((c,m,y,k),density=1): + "Convert from a CMYK color tuple to an RGB color tuple" + # From the Adobe Postscript Ref. Manual 2nd ed. + r = 1.0 - min(1.0, c + k) + g = 1.0 - min(1.0, m + k) + b = 1.0 - min(1.0, y + k) + return (r,g,b) + +def rgb2cmyk(r,g,b): + '''one way to get cmyk from rgb''' + c = 1 - r + m = 1 - g + y = 1 - b + k = min(c,m,y) + c = min(1,max(0,c-k)) + m = min(1,max(0,m-k)) + y = min(1,max(0,y-k)) + k = min(1,max(0,k)) + return (c,m,y,k) + +def color2bw(colorRGB): + "Transform an RGB color to a black and white equivalent." + + col = colorRGB + r, g, b = col.red, col.green, col.blue + n = (r + g + b) / 3.0 + bwColorRGB = Color(n, n, n) + return bwColorRGB + +def HexColor(val): + """This function converts a hex string, or an actual integer number, + into the corresponding color. E.g., in "AABBCC" or 0xAABBCC, + AA is the red, BB is the green, and CC is the blue (00-FF). + + HTML uses a hex string with a preceding hash; if this is present, + it is stripped off. (AR, 3-3-2000) + + For completeness I assume that #aabbcc or 0xaabbcc are hex numbers + otherwise a pure integer is converted as decimal rgb + """ + + if type(val) == StringType: + b = 10 + if val[:1] == '#': + val = val[1:] + b = 16 + elif string.lower(val[:2]) == '0x': + b = 16 + val = val[2:] + val = string.atoi(val,b) + return Color(((val>>16)&0xFF)/255.0,((val>>8)&0xFF)/255.0,(val&0xFF)/255.0) + +def linearlyInterpolatedColor(c0, c1, x0, x1, x): + """ + Linearly interpolates colors. Can handle RGB, CMYK and PCMYK + colors - give ValueError if colours aren't the same. + Doesn't currently handle 'Spot Color Interpolation'. + """ + + if c0.__class__ != c1.__class__: + raise ValueError, "Color classes must be the same for interpolation!" + if x1x0 + if xx1+1e-8: # fudge factor for numerical problems + raise ValueError, "Can't interpolate: x=%f is not between %f and %f!" % (x,x0,x1) + if x<=x0: + return c0 + elif x>=x1: + return c1 + + cname = c0.__class__.__name__ + dx = float(x1-x0) + x = x-x0 + + if cname == 'Color': # RGB + r = c0.red+x*(c1.red - c0.red)/dx + g = c0.green+x*(c1.green- c0.green)/dx + b = c0.blue+x*(c1.blue - c0.blue)/dx + return Color(r,g,b) + elif cname == 'CMYKColor': + c = c0.cyan+x*(c1.cyan - c0.cyan)/dx + m = c0.magenta+x*(c1.magenta - c0.magenta)/dx + y = c0.yellow+x*(c1.yellow - c0.yellow)/dx + k = c0.black+x*(c1.black - c0.black)/dx + d = c0.density+x*(c1.density - c0.density)/dx + return CMYKColor(c,m,y,k, density=d) + elif cname == 'PCMYKColor': + if cmykDistance(c0,c1)<1e-8: + #colors same do density and preserve spotName if any + assert c0.spotName == c1.spotName, "Identical cmyk, but different spotName" + c = c0.cyan + m = c0.magenta + y = c0.yellow + k = c0.black + d = c0.density+x*(c1.density - c0.density)/dx + return PCMYKColor(c*100,m*100,y*100,k*100, density=d*100, spotName=c0.spotName) + elif cmykDistance(c0,_CMYK_white)<1e-8: + #special c0 is white + c = c1.cyan + m = c1.magenta + y = c1.yellow + k = c1.black + d = x*c1.density/dx + return PCMYKColor(c*100,m*100,y*100,k*100, density=d*100, spotName=c1.spotName) + elif cmykDistance(c1,_CMYK_white)<1e-8: + #special c1 is white + c = c0.cyan + m = c0.magenta + y = c0.yellow + k = c0.black + d = x*c0.density/dx + d = c0.density*(1-x/dx) + return PCMYKColor(c*100,m*100,y*100,k*100, density=d*100, spotName=c0.spotName) + else: + c = c0.cyan+x*(c1.cyan - c0.cyan)/dx + m = c0.magenta+x*(c1.magenta - c0.magenta)/dx + y = c0.yellow+x*(c1.yellow - c0.yellow)/dx + k = c0.black+x*(c1.black - c0.black)/dx + d = c0.density+x*(c1.density - c0.density)/dx + return PCMYKColor(c*100,m*100,y*100,k*100, density=d*100) + else: + raise ValueError, "Can't interpolate: Unknown color class %s!" % cname + +# special case -- indicates no drawing should be done +# this is a hangover from PIDDLE - suggest we ditch it since it is not used anywhere +#transparent = Color(-1, -1, -1) + +_CMYK_white=CMYKColor(0,0,0,0) +_PCMYK_white=PCMYKColor(0,0,0,0) +_CMYK_black=CMYKColor(0,0,0,1) +_PCMYK_black=PCMYKColor(0,0,0,100) + +# Special colors +ReportLabBlueOLD = HexColor(0x4e5688) +ReportLabBlue = HexColor(0x00337f) +ReportLabBluePCMYK = PCMYKColor(100,65,0,30,spotName='Pantone 288U') +ReportLabLightBlue = HexColor(0xb7b9d3) +ReportLabFidBlue=HexColor(0x3366cc) +ReportLabFidRed=HexColor(0xcc0033) +ReportLabGreen = HexColor(0x336600) +ReportLabLightGreen = HexColor(0x339933) + +# color constants -- mostly from HTML standard +aliceblue = HexColor(0xF0F8FF) +antiquewhite = HexColor(0xFAEBD7) +aqua = HexColor(0x00FFFF) +aquamarine = HexColor(0x7FFFD4) +azure = HexColor(0xF0FFFF) +beige = HexColor(0xF5F5DC) +bisque = HexColor(0xFFE4C4) +black = HexColor(0x000000) +blanchedalmond = HexColor(0xFFEBCD) +blue = HexColor(0x0000FF) +blueviolet = HexColor(0x8A2BE2) +brown = HexColor(0xA52A2A) +burlywood = HexColor(0xDEB887) +cadetblue = HexColor(0x5F9EA0) +chartreuse = HexColor(0x7FFF00) +chocolate = HexColor(0xD2691E) +coral = HexColor(0xFF7F50) +cornflowerblue = cornflower = HexColor(0x6495ED) +cornsilk = HexColor(0xFFF8DC) +crimson = HexColor(0xDC143C) +cyan = HexColor(0x00FFFF) +darkblue = HexColor(0x00008B) +darkcyan = HexColor(0x008B8B) +darkgoldenrod = HexColor(0xB8860B) +darkgray = HexColor(0xA9A9A9) +darkgreen = HexColor(0x006400) +darkkhaki = HexColor(0xBDB76B) +darkmagenta = HexColor(0x8B008B) +darkolivegreen = HexColor(0x556B2F) +darkorange = HexColor(0xFF8C00) +darkorchid = HexColor(0x9932CC) +darkred = HexColor(0x8B0000) +darksalmon = HexColor(0xE9967A) +darkseagreen = HexColor(0x8FBC8B) +darkslateblue = HexColor(0x483D8B) +darkslategray = HexColor(0x2F4F4F) +darkturquoise = HexColor(0x00CED1) +darkviolet = HexColor(0x9400D3) +deeppink = HexColor(0xFF1493) +deepskyblue = HexColor(0x00BFFF) +dimgray = HexColor(0x696969) +dodgerblue = HexColor(0x1E90FF) +firebrick = HexColor(0xB22222) +floralwhite = HexColor(0xFFFAF0) +forestgreen = HexColor(0x228B22) +fuchsia = HexColor(0xFF00FF) +gainsboro = HexColor(0xDCDCDC) +ghostwhite = HexColor(0xF8F8FF) +gold = HexColor(0xFFD700) +goldenrod = HexColor(0xDAA520) +gray = HexColor(0x808080) +grey = gray +green = HexColor(0x008000) +greenyellow = HexColor(0xADFF2F) +honeydew = HexColor(0xF0FFF0) +hotpink = HexColor(0xFF69B4) +indianred = HexColor(0xCD5C5C) +indigo = HexColor(0x4B0082) +ivory = HexColor(0xFFFFF0) +khaki = HexColor(0xF0E68C) +lavender = HexColor(0xE6E6FA) +lavenderblush = HexColor(0xFFF0F5) +lawngreen = HexColor(0x7CFC00) +lemonchiffon = HexColor(0xFFFACD) +lightblue = HexColor(0xADD8E6) +lightcoral = HexColor(0xF08080) +lightcyan = HexColor(0xE0FFFF) +lightgoldenrodyellow = HexColor(0xFAFAD2) +lightgreen = HexColor(0x90EE90) +lightgrey = HexColor(0xD3D3D3) +lightpink = HexColor(0xFFB6C1) +lightsalmon = HexColor(0xFFA07A) +lightseagreen = HexColor(0x20B2AA) +lightskyblue = HexColor(0x87CEFA) +lightslategray = HexColor(0x778899) +lightsteelblue = HexColor(0xB0C4DE) +lightyellow = HexColor(0xFFFFE0) +lime = HexColor(0x00FF00) +limegreen = HexColor(0x32CD32) +linen = HexColor(0xFAF0E6) +magenta = HexColor(0xFF00FF) +maroon = HexColor(0x800000) +mediumaquamarine = HexColor(0x66CDAA) +mediumblue = HexColor(0x0000CD) +mediumorchid = HexColor(0xBA55D3) +mediumpurple = HexColor(0x9370DB) +mediumseagreen = HexColor(0x3CB371) +mediumslateblue = HexColor(0x7B68EE) +mediumspringgreen = HexColor(0x00FA9A) +mediumturquoise = HexColor(0x48D1CC) +mediumvioletred = HexColor(0xC71585) +midnightblue = HexColor(0x191970) +mintcream = HexColor(0xF5FFFA) +mistyrose = HexColor(0xFFE4E1) +moccasin = HexColor(0xFFE4B5) +navajowhite = HexColor(0xFFDEAD) +navy = HexColor(0x000080) +oldlace = HexColor(0xFDF5E6) +olive = HexColor(0x808000) +olivedrab = HexColor(0x6B8E23) +orange = HexColor(0xFFA500) +orangered = HexColor(0xFF4500) +orchid = HexColor(0xDA70D6) +palegoldenrod = HexColor(0xEEE8AA) +palegreen = HexColor(0x98FB98) +paleturquoise = HexColor(0xAFEEEE) +palevioletred = HexColor(0xDB7093) +papayawhip = HexColor(0xFFEFD5) +peachpuff = HexColor(0xFFDAB9) +peru = HexColor(0xCD853F) +pink = HexColor(0xFFC0CB) +plum = HexColor(0xDDA0DD) +powderblue = HexColor(0xB0E0E6) +purple = HexColor(0x800080) +red = HexColor(0xFF0000) +rosybrown = HexColor(0xBC8F8F) +royalblue = HexColor(0x4169E1) +saddlebrown = HexColor(0x8B4513) +salmon = HexColor(0xFA8072) +sandybrown = HexColor(0xF4A460) +seagreen = HexColor(0x2E8B57) +seashell = HexColor(0xFFF5EE) +sienna = HexColor(0xA0522D) +silver = HexColor(0xC0C0C0) +skyblue = HexColor(0x87CEEB) +slateblue = HexColor(0x6A5ACD) +slategray = HexColor(0x708090) +snow = HexColor(0xFFFAFA) +springgreen = HexColor(0x00FF7F) +steelblue = HexColor(0x4682B4) +tan = HexColor(0xD2B48C) +teal = HexColor(0x008080) +thistle = HexColor(0xD8BFD8) +tomato = HexColor(0xFF6347) +turquoise = HexColor(0x40E0D0) +violet = HexColor(0xEE82EE) +wheat = HexColor(0xF5DEB3) +white = HexColor(0xFFFFFF) +whitesmoke = HexColor(0xF5F5F5) +yellow = HexColor(0xFFFF00) +yellowgreen = HexColor(0x9ACD32) +fidblue=HexColor(0x3366cc) +fidred=HexColor(0xcc0033) +fidlightblue=HexColor("#d6e0f5") + +ColorType=type(black) + + ################################################################ + # + # Helper functions for dealing with colors. These tell you + # which are predefined, so you can print color charts; + # and can give the nearest match to an arbitrary color object + # + ################################################################# + +def colorDistance(col1, col2): + """Returns a number between 0 and root(3) stating how similar + two colours are - distance in r,g,b, space. Only used to find + names for things.""" + return math.sqrt( + (col1.red - col2.red)**2 + + (col1.green - col2.green)**2 + + (col1.blue - col2.blue)**2 + ) + +def cmykDistance(col1, col2): + """Returns a number between 0 and root(4) stating how similar + two colours are - distance in r,g,b, space. Only used to find + names for things.""" + return math.sqrt( + (col1.cyan - col2.cyan)**2 + + (col1.magenta - col2.magenta)**2 + + (col1.yellow - col2.yellow)**2 + + (col1.black - col2.black)**2 + ) + +_namedColors = None + +def getAllNamedColors(): + #returns a dictionary of all the named ones in the module + # uses a singleton for efficiency + global _namedColors + if _namedColors is not None: return _namedColors + import colors + _namedColors = {} + for (name, value) in colors.__dict__.items(): + if isinstance(value, Color): + _namedColors[name] = value + + return _namedColors + +def describe(aColor,mode=0): + '''finds nearest colour match to aColor. + mode=0 print a string desription + mode=1 return a string description + mode=2 return (distance, colorName) + ''' + namedColors = getAllNamedColors() + closest = (10, None, None) #big number, name, color + for (name, color) in namedColors.items(): + distance = colorDistance(aColor, color) + if distance < closest[0]: + closest = (distance, name, color) + if mode<=1: + s = 'best match is %s, distance %0.4f' % (closest[1], closest[0]) + if mode==0: print s + else: return s + elif mode==2: + return (closest[1], closest[0]) + else: + raise ValueError, "Illegal value for mode "+str(mode) + +def toColor(arg,default=None): + '''try to map an arbitrary arg to a color instance''' + if isinstance(arg,Color): return arg + tArg = type(arg) + if tArg in _SeqTypes: + assert 3<=len(arg)<=4, 'Can only convert 3 and 4 sequences to color' + assert 0<=min(arg) and max(arg)<=1 + return len(arg)==3 and Color(arg[0],arg[1],arg[2]) or CMYKColor(arg[0],arg[1],arg[2],arg[3]) + elif tArg == StringType: + C = getAllNamedColors() + s = string.lower(arg) + if C.has_key(s): return C[s] + try: + return toColor(eval(arg)) + except: + pass + + try: + return HexColor(arg) + except: + if default is None: + raise 'Invalid color value', str(arg) + return default + +def toColorOrNone(arg,default=None): + '''as above but allows None as a legal value''' + if arg is None: + return None + else: + return toColor(arg, default) + +def setColors(**kw): + UNDEF = [] + progress = 1 + assigned = {} + while kw and progress: + progress = 0 + for k, v in kw.items(): + if type(v) in (type(()),type([])): + c = map(lambda x,UNDEF=UNDEF: toColor(x,UNDEF),v) + if type(v) is type(()): c = tuple(c) + ok = UNDEF not in c + else: + c = toColor(v,UNDEF) + ok = c is not UNDEF + if ok: + assigned[k] = c + del kw[k] + progress = 1 + + if kw: raise ValueError("Can't convert\n%s" % str(kw)) + getAllNamedColors() + for k, c in assigned.items(): + globals()[k] = c + if isinstance(c,Color): _namedColors[k] = c + +def Whiter(c,f): + '''given a color combine with white as c*f w*(1-f) 0<=f<=1''' + c = toColor(c) + if isinstance(c,PCMYKColor): + w = _PCMYK_white + elif isinstance(c,CMYKColor): w = _CMYK_white + else: w = white + return linearlyInterpolatedColor(w, c, 0, 1, f) + +def Blacker(c,f): + '''given a color combine with black as c*f+b*(1-f) 0<=f<=1''' + c = toColor(c) + if isinstance(c,PCMYKColor): + b = _PCMYK_black + elif isinstance(c,CMYKColor): b = _CMYK_black + else: b = black + return linearlyInterpolatedColor(b, c, 0, 1, f) diff --git a/bin/reportlab/lib/corp.py b/bin/reportlab/lib/corp.py new file mode 100644 index 00000000000..0904c4d3a9c --- /dev/null +++ b/bin/reportlab/lib/corp.py @@ -0,0 +1,452 @@ +#!/bin/env python +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/corp.py +""" This module includes some reusable routines for ReportLab's + 'Corporate Image' - the logo, standard page backdrops and + so on - you are advised to do the same for your own company!""" +__version__=''' $Id: corp.py 2484 2004-12-10 07:27:50Z andy $ ''' + +from reportlab.lib.units import inch,cm +from reportlab.lib.validators import * +from reportlab.lib.attrmap import * +from reportlab.graphics.shapes import definePath, Group, Drawing, Rect, PolyLine, String +from reportlab.graphics.widgetbase import Widget +from reportlab.lib.colors import Color, black, white, ReportLabBlue +from reportlab.pdfbase.pdfmetrics import stringWidth +from math import sin, pi + +class RL_CorpLogo(Widget): + '''Dinu's fat letter logo as hacked into decent paths by Robin''' + _attrMap = AttrMap( + x = AttrMapValue(isNumber,'Logo x-coord'), + y = AttrMapValue(isNumber,'Logo y-coord'), + angle = AttrMapValue(isNumber,'Logo rotation'), + strokeColor = AttrMapValue(isColorOrNone, 'Logo lettering stroke color'), + fillColor = AttrMapValue(isColorOrNone, 'Logo lettering fill color'), + strokeWidth = AttrMapValue(isNumber,'Logo lettering stroke width'), + background = AttrMapValue(isColorOrNone,desc="Logo background color"), + border = AttrMapValue(isColorOrNone,desc="Logo border color"), + borderWidth = AttrMapValue(isNumber,desc="Logo border width (1)"), + shadow = AttrMapValue(isNumberOrNone,desc="None or fraction of background for shadowing" ), + width = AttrMapValue(isNumber, desc="width in points of the logo (default 129)"), + height = AttrMapValue(isNumber, desc="height in points of the logo (default 86)"), + skewX = AttrMapValue(isNumber, desc="x-skew of the logo (default 10)"), + skewY = AttrMapValue(isNumber, desc="y-skew of the logo (default 0)"), + showPage = AttrMapValue(isBoolean, desc="If true show the page lines"), + xFlip = AttrMapValue(isBoolean, desc="If true do x reversal"), + yFlip = AttrMapValue(isBoolean, desc="If true do y reversal"), + ) + + def __init__(self): + self.fillColor = white + self.strokeColor = None + self.strokeWidth = 0.1 + self.background = ReportLabBlue + self.border = None + self.borderWidth = 1 + self.shadow = 0.5 + self.height = 86 + self.width = 130 + self.x = self.y = self.angle = self.skewY = self._dx = 0 + self.skewX = 10 + self._dy = 35.5 + self.showPage = 1 + + def demo(self): + D = Drawing(self.width, self.height) + D.add(self) + return D + + def _paintLogo(self, g, dx=0, dy=0, strokeColor=None, strokeWidth=0.1, fillColor=white): + P = [ + ('moveTo' ,15.7246,0 ), ('lineTo' ,9.49521,0 ), ('lineTo' ,6.64988,6.83711 ), ('curveTo' ,6.62224,6.95315 ,6.57391,7.10646 ,6.50485,7.29708 ), ('curveTo' ,6.43578,7.48767 ,6.35059,7.71559 ,6.24931,7.98079 ), ('lineTo' ,6.29074,6.71282 ), ('lineTo' ,6.29074,0 ), ('lineTo' ,0.55862,0 ), ('lineTo' ,0.55862,19.19365 ), ('lineTo' ,6.45649,19.19365 ), ('curveTo' ,9.05324,19.19365 ,10.99617,18.73371 ,12.28532,17.8138 ), ('curveTo' ,13.92439,16.63697 ,14.7439,14.96293 ,14.7439,12.79161 ), ('curveTo' ,14.7439,10.47114 ,13.64354,8.86755 ,11.44276,7.98079 ), 'closePath', ('moveTo' ,6.31838,10.30542 ), ('lineTo' ,6.70513,10.30542 ), ('curveTo' ,7.36812,10.30542 ,7.92062,10.53331 ,8.36261,10.98912 ), ('curveTo' ,8.80461,11.44491 ,9.0256,12.02504 ,9.0256,12.72947 ), ('curveTo' ,9.0256,14.16321 ,8.19227,14.88004 ,6.52556,14.88004 ), ('lineTo' ,6.31838,14.88004 ), 'closePath', + ('moveTo' ,25.06173,4.54978 ), ('lineTo' ,30.47611,4.45033 ), ('curveTo' ,30.08951,2.88402 ,29.33668,1.70513 ,28.21787,0.91369 ), ('curveTo' ,27.09906,0.12223 ,25.63726,-0.27348 ,23.83245,-0.27348 ), ('curveTo' ,21.69611,-0.27348 ,20.02024,0.32322 ,18.80475,1.5166 ), ('curveTo' ,17.59846,2.72658 ,16.99531,4.37988 ,16.99531,6.47662 ), ('curveTo' ,16.99531,8.6065 ,17.64451,10.34269 ,18.94286,11.68527 ), ('curveTo' ,20.24124,13.03612 ,21.91711,13.71152 ,23.97056,13.71152 ), ('curveTo' ,26.01482,13.71152 ,27.64466,13.06096 ,28.86015,11.75985 ), ('curveTo' ,30.07566,10.45042 ,30.68326,8.71423 ,30.68326,6.5512 ), ('lineTo' ,30.65586,5.66859 ), ('lineTo' ,22.53407,5.66859 ), ('curveTo' ,22.59855,4.29287 ,23.03132,3.60503 ,23.83245,3.60503 ), ('curveTo' ,24.45861,3.60503 ,24.86837,3.91994 ,25.06173,4.54978 ), 'closePath', ('moveTo' ,25.18604,8.35371 ), ('curveTo' ,25.18604,8.60235 ,25.15384,8.83024 ,25.08937,9.03742 ), ('curveTo' ,25.02489,9.24463 ,24.93514,9.42278 ,24.82001,9.57197 ), ('curveTo' ,24.70492,9.72113 ,24.56911,9.83923 ,24.41255,9.92624 ), ('curveTo' ,24.25603,10.01326 ,24.08568,10.05678 ,23.90152,10.05678 ), ('curveTo' ,23.51474,10.05678 ,23.20169,9.89725 ,22.96225,9.57819 ), ('curveTo' ,22.72283,9.25913 ,22.60314,8.85096 ,22.60314,8.35371 ), 'closePath', + ('moveTo' ,38.36308,-5.99181 ), ('lineTo' ,32.82428,-5.99181 ), ('lineTo' ,32.82428,13.43804 ), ('lineTo' ,38.36308,13.43804 ), ('lineTo' ,38.23873,11.53608 ), ('curveTo' ,38.46886,11.93387 ,38.70371,12.27159 ,38.94327,12.54922 ), ('curveTo' ,39.18254,12.82685 ,39.44037,13.05268 ,39.71676,13.22671 ), ('curveTo' ,39.99286,13.40074 ,40.28988,13.52712 ,40.60753,13.60585 ), ('curveTo' ,40.92518,13.68459 ,41.27759,13.72396 ,41.66419,13.72396 ), ('curveTo' ,43.10068,13.72396 ,44.2702,13.07755 ,45.17246,11.78472 ), ('curveTo' ,46.06588,10.50844 ,46.51229,8.81368 ,46.51229,6.70038 ), ('curveTo' ,46.51229,4.55394 ,46.08415,2.85502 ,45.22785,1.60362 ), ('curveTo' ,44.38983,0.35221 ,43.23416,-0.27348 ,41.76084,-0.27348 ), ('curveTo' ,40.41659,-0.27348 ,39.24235,0.42679 ,38.23873,1.82739 ), ('curveTo' ,38.2847,1.40472 ,38.31239,1.04007 ,38.32153,0.73345 ), ('curveTo' ,38.34923,0.41851 ,38.36308,0.04146 ,38.36308,-0.3978 ), 'closePath', ('moveTo' ,40.7802,6.84954 ), ('curveTo' ,40.7802,7.72802 ,40.66734,8.40964 ,40.44193,8.89448 ), ('curveTo' ,40.21621,9.37929 ,39.89621,9.62168 ,39.48191,9.62168 ), ('curveTo' ,38.62533,9.62168 ,38.19718,8.68108 ,38.19718,6.79983 ), ('curveTo' ,38.19718,4.87712 ,38.61177,3.91581 ,39.44037,3.91581 ), ('curveTo' ,39.85466,3.91581 ,40.18174,4.1727 ,40.42101,4.68654 ), ('curveTo' ,40.66057,5.20037 ,40.7802,5.92135 ,40.7802,6.84954 ), 'closePath', + ('moveTo' ,62.10648,6.51392 ), ('curveTo' ,62.10648,4.44205 ,61.47118,2.79288 ,60.2003,1.56631 ), ('curveTo' ,58.92971,0.33978 ,57.22626,-0.27348 ,55.08965,-0.27348 ), ('curveTo' ,52.99018,-0.27348 ,51.31914,0.35221 ,50.07595,1.60362 ), ('curveTo' ,48.8419,2.8633 ,48.22517,4.55394 ,48.22517,6.67551 ), ('curveTo' ,48.22517,8.79709 ,48.85575,10.50016 ,50.1175,11.78472 ), ('curveTo' ,51.36982,13.07755 ,53.03172,13.72396 ,55.1035,13.72396 ), ('curveTo' ,57.28608,13.72396 ,58.99866,13.08168 ,60.24185,11.79712 ), ('curveTo' ,61.48503,10.51259 ,62.10648,8.75154 ,62.10648,6.51392 ), 'closePath', ('moveTo' ,56.73358,6.67551 ), ('curveTo' ,56.73358,7.17276 ,56.69675,7.62236 ,56.62308,8.02428 ), ('curveTo' ,56.54942,8.42623 ,56.44334,8.77016 ,56.30544,9.05607 ), ('curveTo' ,56.16724,9.34198 ,56.00134,9.56369 ,55.80804,9.72113 ), ('curveTo' ,55.61474,9.8786 ,55.39817,9.95733 ,55.1589,9.95733 ), ('curveTo' ,54.68921,9.95733 ,54.31174,9.65898 ,54.02621,9.06229 ), ('curveTo' ,53.74068,8.54018 ,53.59807,7.75702 ,53.59807,6.71282 ), ('curveTo' ,53.59807,5.68515 ,53.74068,4.90202 ,54.02621,4.36332 ), ('curveTo' ,54.31174,3.76663 ,54.69392,3.46828 ,55.17275,3.46828 ), ('curveTo' ,55.62388,3.46828 ,55.99692,3.7625 ,56.29159,4.35088 ), ('curveTo' ,56.58625,5.0056 ,56.73358,5.78047 ,56.73358,6.67551 ), 'closePath', + ('moveTo' ,69.78629,0 ), ('lineTo' ,64.2475,0 ), ('lineTo' ,64.2475,13.43804 ), ('lineTo' ,69.78629,13.43804 ), ('lineTo' ,69.49605,10.81507 ), ('curveTo' ,70.33407,12.77921 ,71.71988,13.76126 ,73.65346,13.76126 ), ('lineTo' ,73.65346,8.16725 ), ('curveTo' ,73.04586,8.4656 ,72.5302,8.61478 ,72.10647,8.61478 ), ('curveTo' ,71.36068,8.61478 ,70.78756,8.37236 ,70.38711,7.88755 ), ('curveTo' ,69.98637,7.40274 ,69.78629,6.69623 ,69.78629,5.76804 ), 'closePath', + ('moveTo' ,81.55427,0 ), ('lineTo' ,76.00163,0 ), ('lineTo' ,76.00163,9.42278 ), ('lineTo' ,74.42725,9.42278 ), ('lineTo' ,74.42725,13.43804 ), ('lineTo' ,76.00163,13.43804 ), ('lineTo' ,76.00163,17.39113 ), ('lineTo' ,81.55427,17.39113 ), ('lineTo' ,81.55427,13.43804 ), ('lineTo' ,83.39121,13.43804 ), ('lineTo' ,83.39121,9.42278 ), ('lineTo' ,81.55427,9.42278 ), 'closePath', + ('moveTo' ,95.17333,0 ), ('lineTo' ,85.09024,0 ), ('lineTo' ,85.09024,19.19365 ), ('lineTo' ,90.85002,19.19365 ), ('lineTo' ,90.85002,4.61196 ), ('lineTo' ,95.17333,4.61196 ), 'closePath', + ('moveTo' ,110.00787,0 ), ('lineTo' ,104.45523,0 ), ('curveTo' ,104.5012,0.44754 ,104.53803,0.87433 ,104.56573,1.2804 ), ('curveTo' ,104.59313,1.68651 ,104.62083,2.01385 ,104.64853,2.26246 ), ('curveTo' ,103.69087,0.57182 ,102.40644,-0.27348 ,100.79492,-0.27348 ), ('curveTo' ,99.39527,-0.27348 ,98.28557,0.35637 ,97.46611,1.61605 ), ('curveTo' ,96.65578,2.86746 ,96.25062,4.59952 ,96.25062,6.81227 ), ('curveTo' ,96.25062,8.95041 ,96.66963,10.63276 ,97.50765,11.8593 ), ('curveTo' ,98.34538,13.10242 ,99.4872,13.72396 ,100.93312,13.72396 ), ('curveTo' ,102.41557,13.72396 ,103.61249,12.92008 ,104.52418,11.31231 ), ('curveTo' ,104.50591,11.47806 ,104.49206,11.62309 ,104.48293,11.74741 ), ('curveTo' ,104.4735,11.87173 ,104.46437,11.9753 ,104.45523,12.05819 ), ('lineTo' ,104.39983,12.84135 ), ('lineTo' ,104.35858,13.43804 ), ('lineTo' ,110.00787,13.43804 ), 'closePath', ('moveTo' ,104.39983,6.88685 ), ('curveTo' ,104.39983,7.38409 ,104.37921,7.80676 ,104.33766,8.15481 ), ('curveTo' ,104.29641,8.5029 ,104.22952,8.78672 ,104.13758,9.00636 ), ('curveTo' ,104.04535,9.22598 ,103.92572,9.38341 ,103.77839,9.47874 ), ('curveTo' ,103.63106,9.57403 ,103.45161,9.62168 ,103.23974,9.62168 ), ('curveTo' ,102.30036,9.62168 ,101.83096,8.49875 ,101.83096,6.25285 ), ('curveTo' ,101.83096,4.64508 ,102.24967,3.8412 ,103.0877,3.8412 ), ('curveTo' ,103.96255,3.8412 ,104.39983,4.85641 ,104.39983,6.88685 ), 'closePath', + ('moveTo' ,118.22604,0 ), ('lineTo' ,112.5629,0 ), ('lineTo' ,112.5629,20.99616 ), ('lineTo' ,118.10169,20.99616 ), ('lineTo' ,118.10169,13.63694 ), ('curveTo' ,118.10169,13.01538 ,118.07399,12.30268 ,118.01889,11.49877 ), ('curveTo' ,118.52542,12.31096 ,119.03636,12.88693 ,119.55202,13.22671 ), ('curveTo' ,120.08625,13.55821 ,120.75838,13.72396 ,121.5687,13.72396 ), ('curveTo' ,123.07885,13.72396 ,124.24837,13.09827 ,125.07697,11.84686 ), ('curveTo' ,125.90586,10.60373 ,126.32015,8.85099 ,126.32015,6.5885 ), ('curveTo' ,126.32015,4.42546 ,125.89201,2.74314 ,125.03571,1.54147 ), ('curveTo' ,124.18826,0.3315 ,123.01432,-0.27348 ,121.51331,-0.27348 ), ('curveTo' ,120.78608,-0.27348 ,120.16905,-0.12432 ,119.66252,0.17403 ), ('curveTo' ,119.41383,0.3315 ,119.15835,0.54283 ,118.8961,0.80803 ), ('curveTo' ,118.63356,1.07322 ,118.36866,1.40472 ,118.10169,1.80252 ), ('curveTo' ,118.11112,1.64505 ,118.12025,1.51039 ,118.12939,1.3985 ), ('curveTo' ,118.13852,1.28662 ,118.14766,1.19339 ,118.15709,1.11881 ), 'closePath', ('moveTo' ,120.58806,6.70038 ), ('curveTo' ,120.58806,8.62306 ,120.11837,9.5844 ,119.17898,9.5844 ), ('curveTo' ,118.35039,9.5844 ,117.93609,8.67693 ,117.93609,6.86198 ), ('curveTo' ,117.93609,4.96417 ,118.36424,4.01526 ,119.22053,4.01526 ), ('curveTo' ,120.13222,4.01526 ,120.58806,4.91027 ,120.58806,6.70038 ), 'closePath', + ] + (self.showPage and [ + ('moveTo',38.30626,-7.28346),('lineTo',38.30626,-25.55261),('lineTo',85.15777,-25.55261),('lineTo',85.15777,-1.39019),('lineTo',90.46172,-1.39019),('lineTo',90.46172,-31.15121),('lineTo',32.70766,-31.15121),('lineTo',32.70766,-7.28346), 'closePath', + ('moveTo' ,32.70766,14.52164 ), ('lineTo' ,32.70766,47.81862 ), ('lineTo' ,80.14849,47.81862 ), ('lineTo' ,90.46172,37.21073 ), ('lineTo' ,90.46172,20.12025 ), ('lineTo' ,85.15777,20.12025 ), ('lineTo' ,85.15777,30.72814 ), ('lineTo' ,73.66589,30.72814 ), ('lineTo' ,73.66589,42.22002 ), ('lineTo' ,38.30626,42.22002 ), ('lineTo' ,38.30626,14.52164 ), 'closePath', ('moveTo' ,79.2645,36.32674 ), ('lineTo' ,85.15777,36.32674 ), ('lineTo' ,79.2645,42.22002 ), 'closePath', + ] or []) + g.add(definePath(P,strokeColor=strokeColor,strokeWidth=strokeWidth,fillColor=fillColor, dx=dx, dy=dy)) + + def draw(self): + fillColor = self.fillColor + strokeColor = self.strokeColor + g = Group() + bg = self.background + bd = self.border + bdw = self.borderWidth + shadow = self.shadow + x, y = self.x, self.y + if bg: + if shadow is not None and 0<=shadow<1: + shadow = Color(bg.red*shadow,bg.green*shadow,bg.blue*shadow) + self._paintLogo(g,dy=-2.5, dx=2,fillColor=shadow) + self._paintLogo(g,fillColor=fillColor,strokeColor=strokeColor) + g.skew(kx=self.skewX, ky=self.skewY) + g.shift(self._dx,self._dy) + G = Group() + G.add(g) + _w, _h = 130, 86 + w, h = self.width, self.height + if bg or (bd and bdw): + G.insert(0,Rect(0,0,_w,_h,fillColor=bg,strokeColor=bd,strokeWidth=bdw)) + if w!=_w or h!=_h: G.scale(w/float(_w),h/float(_h)) + + angle = self.angle + if self.angle: + w, h = w/2., h/2. + G.shift(-w,-h) + G.rotate(angle) + G.shift(w,h) + xFlip = getattr(self,'xFlip',0) and -1 or 0 + yFlip = getattr(self,'yFlip',0) and -1 or 0 + if xFlip or yFlip: + sx = xFlip or 1 + sy = yFlip or 1 + G.shift(sx*x+w*xFlip,sy*y+yFlip*h) + G = Group(G,transform=(sx,0,0,sy,0,0)) + else: + G.shift(x,y) + return G + +class RL_CorpLogoReversed(RL_CorpLogo): + def __init__(self): + RL_CorpLogo.__init__(self) + self.background = white + self.fillColor = ReportLabBlue + +class RL_CorpLogoThin(Widget): + """The ReportLab Logo. + + New version created by John Precedo on 7-8 August 2001. + Based on bitmapped imaged from E-Id. + Improved by Robin Becker.""" + + _attrMap = AttrMap( + x = AttrMapValue(isNumber), + y = AttrMapValue(isNumber), + height = AttrMapValue(isNumberOrNone), + width = AttrMapValue(isNumberOrNone), + fillColor = AttrMapValue(isColorOrNone), + strokeColor = AttrMapValue( isColorOrNone) + ) + + _h = 90.5 + _w = 136.5 + _text='R e p o r t L a b' + _fontName = 'Helvetica-Bold' + _fontSize = 16 + + def __init__(self): + self.fillColor = ReportLabBlue + self.strokeColor = white + self.x = 0 + self.y = 0 + self.height = self._h + self.width = self._w + + def demo(self): + D = Drawing(self.width, self.height) + D.add(self) + return D + + def _getText(self, x=0, y=0, color=None): + return String(x,y, self._text, fontName=self._fontName, fontSize=self._fontSize, fillColor=color) + + def _sw(self,f=None,l=None): + text = self._text + if f is None: f = 0 + if l is None: l = len(text) + return stringWidth(text[f:l],self._fontName,self._fontSize) + + def _addPage(self, g, strokeWidth=3, color=None, dx=0, dy=0): + x1, x2 = 31.85+dx, 80.97+dx + fL = 10 # fold length + y1, y2 = dy-34, dy+50.5 + L = [[x1,dy-4,x1,y1, x2, y1, x2, dy-1], + [x1,dy+11,x1,y2,x2-fL,y2,x2,y2-fL,x2,dy+14], + [x2-10,y2,x2-10,y2-fL,x2,y2-fL]] + + for l in L: + g.add(PolyLine(l, strokeWidth=strokeWidth, strokeColor=color, strokeLineJoin=0)) + + def draw(self): + sx = 0.5 + fillColor = self.fillColor + strokeColor = self.strokeColor + shadow = Color(fillColor.red*sx,fillColor.green*sx,fillColor.blue*sx) + g = Group() + g2= Group() + g.add(Rect(fillColor=fillColor, strokeColor=fillColor, x=0, y=0, width=self._w, height=self._h)) + sx = (self._w-2)/self._sw() + g2.scale(sx,1) + self._addPage(g2,strokeWidth=3,dx=2,dy=-2.5,color=shadow) + self._addPage(g2,strokeWidth=3,color=strokeColor) + g2.scale(1/sx,1) + g2.add(self._getText(x=1,y=0,color=shadow)) + g2.add(self._getText(x=0,y=1,color=strokeColor)) + g2.scale(sx,1) + g2.skew(kx=10, ky=0) + g2.shift(0,38) + g.add(g2) + g.scale(self.width/self._w,self.height/self._h) + g.shift(self.x,self.y) + return g + +class ReportLabLogo: + """vector reportlab logo centered in a 250x by 150y rectangle""" + + def __init__(self, atx=0, aty=0, width=2.5*inch, height=1.5*inch, powered_by=0): + self.origin = (atx, aty) + self.dimensions = (width, height) + self.powered_by = powered_by + + def draw(self, canvas): + from reportlab.graphics import renderPDF + canvas.saveState() + (atx,aty) = self.origin + (width, height) = self.dimensions + logo = RL_CorpLogo() + logo.width, logo.height = width, height + renderPDF.draw(logo.demo(),canvas,atx,aty,0) + canvas.restoreState() + +class RL_BusinessCard(Widget): + """Widget that creates a single business card. + Uses RL_CorpLogo for the logo. + + For a black border around your card, set self.border to 1. + To change the details on the card, over-ride the following properties: + self.name, self.position, self.telephone, self.mobile, self.fax, self.email, self.web + The office locations are set in self.rh_blurb_top ("London office" etc), and + self.rh_blurb_bottom ("New York office" etc). + """ + # for items where it 'isString' the string can be an empty one... + _attrMap = AttrMap( + fillColor = AttrMapValue(isColorOrNone), + strokeColor = AttrMapValue(isColorOrNone), + altStrokeColor = AttrMapValue(isColorOrNone), + x = AttrMapValue(isNumber), + y = AttrMapValue(isNumber), + height = AttrMapValue(isNumber), + width = AttrMapValue(isNumber), + borderWidth = AttrMapValue(isNumber), + bleed=AttrMapValue(isNumberOrNone), + cropMarks=AttrMapValue(isBoolean), + border=AttrMapValue(isBoolean), + name=AttrMapValue(isString), + position=AttrMapValue(isString), + telephone=AttrMapValue(isString), + mobile=AttrMapValue(isString), + fax=AttrMapValue(isString), + email=AttrMapValue(isString), + web=AttrMapValue(isString), + rh_blurb_top=AttrMapValue(isListOfStringsOrNone), + rh_blurb_bottom=AttrMapValue(isListOfStringsOrNone) + ) + + _h = 5.35*cm + _w = 8.5*cm + _fontName = 'Helvetica-Bold' + _strapline = "strategic reporting solutions for e-business" + + + def __init__(self): + self.fillColor = ReportLabBlue + self.strokeColor = black + self.altStrokeColor = white + self.x = 0 + self.y = 0 + self.height = self._h + self.width = self._w + self.borderWidth = self.width/6.15 + self.bleed=0.2*cm + self.cropMarks=1 + self.border=0 + #Over-ride these with your own info + self.name="Joe Cool" + self.position="Freelance Demonstrator" + self.telephone="020 8545 7271" + self.mobile="-" + self.fax="020 8544 1311" + self.email="info@reportlab.com" + self.web="www.reportlab.com" + self.rh_blurb_top = ["London office:", + "ReportLab Europe Ltd", + "Lombard Business Park", + "8 Lombard Road", + "Wimbledon", + "London SW19 3TZ", + "United Kingdom"] + self.rh_blurb_bottom = ["New York office:", + "ReportLab Inc", + "219 Harper Street", + "Highland Park", + "New Jersey 08904", + "USA"] + + def demo(self): + D = Drawing(self.width, self.height) + D.add(self) + return D + + def draw(self): + fillColor = self.fillColor + strokeColor = self.strokeColor + + g = Group() + g.add(Rect(x = 0, y = 0, + fillColor = self.fillColor, + strokeColor = self.fillColor, + width = self.borderWidth, + height = self.height)) + g.add(Rect(x = 0, y = self.height-self.borderWidth, + fillColor = self.fillColor, + strokeColor = self.fillColor, + width = self.width, + height = self.borderWidth)) + + g2 = Group() + rl=RL_CorpLogo() + rl.height = 1.25*cm + rl.width = 1.9*cm + rl.draw() + g2.add(rl) + g.add(g2) + g2.shift(x=(self.width-(rl.width+(self.width/42))), + y=(self.height - (rl.height+(self.height/42)))) + + g.add(String(x = self.borderWidth/5.0, + y = ((self.height - (rl.height+(self.height/42)))+((38/90.5)*rl.height)), + fontSize = 6, + fillColor = self.altStrokeColor, + fontName = "Helvetica-BoldOblique", + textAnchor = 'start', + text = self._strapline)) + + leftText=["Tel:", "Mobile:", "Fax:", "Email:", "Web:"] + leftDetails=[self.telephone,self.mobile,self.fax,self.email,self.web] + leftText.reverse() + leftDetails.reverse() + for f in range(len(leftText),0,-1): + g.add(String(x = self.borderWidth+(self.borderWidth/5.0), + y = (self.borderWidth/5.0)+((f-1)*(5*1.2)), + fontSize = 5, + fillColor = self.strokeColor, + fontName = "Helvetica", + textAnchor = 'start', + text = leftText[f-1])) + g.add(String(x = self.borderWidth+(self.borderWidth/5.0)+self.borderWidth, + y = (self.borderWidth/5.0)+((f-1)*(5*1.2)), + fontSize = 5, + fillColor = self.strokeColor, + fontName = "Helvetica", + textAnchor = 'start', + text = leftDetails[f-1])) + + rightText=self.rh_blurb_bottom + rightText.reverse() + for f in range(len(rightText),0,-1): + g.add(String(x = self.width-((self.borderWidth/5.0)), + y = (self.borderWidth/5.0)+((f-1)*(5*1.2)), + fontSize = 5, + fillColor = self.strokeColor, + fontName = "Helvetica", + textAnchor = 'end', + text = rightText[f-1])) + + ty = (self.height-self.borderWidth-(self.borderWidth/5.0)+2) +# g.add(Line(self.borderWidth, ty, self.borderWidth+(self.borderWidth/5.0), ty)) +# g.add(Line(self.borderWidth+(self.borderWidth/5.0), ty, self.borderWidth+(self.borderWidth/5.0), +# ty+(self.borderWidth/5.0))) +# g.add(Line(self.borderWidth, ty-10, +# self.borderWidth+(self.borderWidth/5.0), ty-10)) + + rightText=self.rh_blurb_top + for f in range(1,(len(rightText)+1)): + g.add(String(x = self.width-(self.borderWidth/5.0), + y = ty-((f)*(5*1.2)), + fontSize = 5, + fillColor = self.strokeColor, + fontName = "Helvetica", + textAnchor = 'end', + text = rightText[f-1])) + + g.add(String(x = self.borderWidth+(self.borderWidth/5.0), + y = ty-10, + fontSize = 10, + fillColor = self.strokeColor, + fontName = "Helvetica", + textAnchor = 'start', + text = self.name)) + + ty1 = ty-10*1.2 + + g.add(String(x = self.borderWidth+(self.borderWidth/5.0), + y = ty1-8, + fontSize = 8, + fillColor = self.strokeColor, + fontName = "Helvetica", + textAnchor = 'start', + text = self.position)) + if self.border: + g.add(Rect(x = 0, y = 0, + fillColor=None, + strokeColor = black, + width = self.width, + height = self.height)) + g.shift(self.x,self.y) + return g + + +def test(): + """This function produces a pdf with examples. """ + + #white on blue + rl = RL_CorpLogo() + rl.width = 129 + rl.height = 86 + D = Drawing(rl.width,rl.height) + D.add(rl) + D.__dict__['verbose'] = 1 + D.save(fnRoot='corplogo_whiteonblue',formats=['pdf','eps','jpg','gif']) + + + #blue on white + rl = RL_CorpLogoReversed() + rl.width = 129 + rl.height = 86 + D = Drawing(rl.width,rl.height) + D.add(rl) + D.__dict__['verbose'] = 1 + D.save(fnRoot='corplogo_blueonwhite',formats=['pdf','eps','jpg','gif']) + + #gray on white + rl = RL_CorpLogoReversed() + rl.fillColor = Color(0.2, 0.2, 0.2) + rl.width = 129 + rl.height = 86 + D = Drawing(rl.width,rl.height) + D.add(rl) + D.__dict__['verbose'] = 1 + D.save(fnRoot='corplogo_grayonwhite',formats=['pdf','eps','jpg','gif']) + + + rl = RL_BusinessCard() + rl.x=25 + rl.y=25 + rl.border=1 + D = Drawing(rl.width+50,rl.height+50) + D.add(rl) + D.__dict__['verbose'] = 1 + D.save(fnRoot='RL_BusinessCard',formats=['pdf']) + +if __name__=='__main__': + test() diff --git a/bin/reportlab/lib/enums.py b/bin/reportlab/lib/enums.py new file mode 100644 index 00000000000..f28897a929c --- /dev/null +++ b/bin/reportlab/lib/enums.py @@ -0,0 +1,11 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/enums.py +__version__=''' $Id: enums.py 2385 2004-06-17 15:26:05Z rgbecker $ ''' +__doc__=""" +holder for all reportlab's enumerated types +""" +TA_LEFT = 0 +TA_CENTER = 1 +TA_RIGHT = 2 +TA_JUSTIFY = 4 \ No newline at end of file diff --git a/bin/reportlab/lib/extformat.py b/bin/reportlab/lib/extformat.py new file mode 100644 index 00000000000..f3450596081 --- /dev/null +++ b/bin/reportlab/lib/extformat.py @@ -0,0 +1,81 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/extformat.py +from tokenize import tokenprog +import sys + +def _matchorfail(text, pos): + match = tokenprog.match(text, pos) + if match is None: raise ValueError(text, pos) + return match, match.end() + +''' + Extended dictionary formatting + We allow expressions in the parentheses instead of + just a simple variable. +''' +def dictformat(_format, L={}, G={}): + format = _format + + S = {} + chunks = [] + pos = 0 + n = 0 + + while 1: + pc = format.find("%", pos) + if pc < 0: break + nextchar = format[pc+1] + + if nextchar == "(": + chunks.append(format[pos:pc]) + pos, level = pc+2, 1 + while level: + match, pos = _matchorfail(format, pos) + tstart, tend = match.regs[3] + token = format[tstart:tend] + if token == "(": level = level+1 + elif token == ")": level = level-1 + vname = '__superformat_%d' % n + n += 1 + S[vname] = eval(format[pc+2:pos-1],L,G) + chunks.append('%%(%s)' % vname) + else: + nc = pc+1+(nextchar=="%") + chunks.append(format[pos:nc]) + pos = nc + + if pos < len(format): chunks.append(format[pos:]) + return (''.join(chunks)) % S + +def magicformat(format): + """Evaluate and substitute the appropriate parts of the string.""" + try: 1/0 + except: frame = sys.exc_traceback.tb_frame + while frame.f_globals["__name__"] == __name__: frame = frame.f_back + return dictformat(format,frame.f_locals, frame.f_globals) + +if __name__=='__main__': + from reportlab.lib.formatters import DecimalFormatter + _DF={} + def df(n,dp=2,ds='.',ts=','): + try: + _df = _DF[dp,ds] + except KeyError: + _df = _DF[dp,ds] = DecimalFormatter(places=dp,decimalSep=ds,thousandSep=ts) + return _df(n) + + from reportlab.lib.extformat import magicformat + + Z={'abc': ('ab','c')} + x = 300000.23 + percent=79.2 + class dingo: + a=3 + print magicformat(''' +$%%(df(x,dp=3))s --> $%(df(x,dp=3))s +$%%(df(x,dp=2,ds=',',ts='.'))s --> $%(df(x,dp=2,ds=',',ts='.'))s +%%(percent).2f%%%% --> %(percent).2f%% +%%(dingo.a)s --> %(dingo.a)s +%%(Z['abc'][0])s --> %(Z['abc'][0])s +''') diff --git a/bin/reportlab/lib/fonts.py b/bin/reportlab/lib/fonts.py new file mode 100644 index 00000000000..7793530fbc8 --- /dev/null +++ b/bin/reportlab/lib/fonts.py @@ -0,0 +1,89 @@ +#!/bin/env python +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/fonts.py +__version__=''' $Id: fonts.py 2385 2004-06-17 15:26:05Z rgbecker $ ''' +import string, sys, os +############################################################################### +# A place to put useful font stuff +############################################################################### +# +# Font Mappings +# The brute force approach to finding the correct postscript font name; +# much safer than the rule-based ones we tried. +# preprocessor to reduce font face names to the shortest list +# possible. Add any aliases you wish; it keeps looking up +# until it finds no more translations to do. Any input +# will be lowercased before checking. +_family_alias = { + 'serif':'times', + 'sansserif':'helvetica', + 'monospaced':'courier', + 'arial':'helvetica' + } +#maps a piddle font to a postscript one. +_tt2ps_map = { + #face, bold, italic -> ps name + ('times', 0, 0) :'Times-Roman', + ('times', 1, 0) :'Times-Bold', + ('times', 0, 1) :'Times-Italic', + ('times', 1, 1) :'Times-BoldItalic', + + ('courier', 0, 0) :'Courier', + ('courier', 1, 0) :'Courier-Bold', + ('courier', 0, 1) :'Courier-Oblique', + ('courier', 1, 1) :'Courier-BoldOblique', + + ('helvetica', 0, 0) :'Helvetica', + ('helvetica', 1, 0) :'Helvetica-Bold', + ('helvetica', 0, 1) :'Helvetica-Oblique', + ('helvetica', 1, 1) :'Helvetica-BoldOblique', + + + # there is only one Symbol font + ('symbol', 0, 0) :'Symbol', + ('symbol', 1, 0) :'Symbol', + ('symbol', 0, 1) :'Symbol', + ('symbol', 1, 1) :'Symbol', + + # ditto for dingbats + ('zapfdingbats', 0, 0) :'ZapfDingbats', + ('zapfdingbats', 1, 0) :'ZapfDingbats', + ('zapfdingbats', 0, 1) :'ZapfDingbats', + ('zapfdingbats', 1, 1) :'ZapfDingbats', + + + } + +_ps2tt_map={} +for k,v in _tt2ps_map.items(): + if not _ps2tt_map.has_key(k): + _ps2tt_map[string.lower(v)] = k + +def ps2tt(psfn): + 'ps fontname to family name, bold, italic' + psfn = string.lower(psfn) + if _ps2tt_map.has_key(psfn): + return _ps2tt_map[psfn] + raise ValueError, "Can't map determine family/bold/italic for %s" % psfn + +def tt2ps(fn,b,i): + 'family name + bold & italic to ps font name' + K = (string.lower(fn),b,i) + if _tt2ps_map.has_key(K): + return _tt2ps_map[K] + else: + fn, b1, i1 = ps2tt(K[0]) + K = fn, b1|b, i1|i + if _tt2ps_map.has_key(K): + return _tt2ps_map[K] + raise ValueError, "Can't find concrete font for family=%s, bold=%d, italic=%d" % (fn, b, i) + +def addMapping(face, bold, italic, psname): + 'allow a custom font to be put in the mapping' + k = (string.lower(face), bold, italic) + _tt2ps_map[k] = psname + # rebuild inverse - inefficient + for k,v in _tt2ps_map.items(): + if not _ps2tt_map.has_key(k): + _ps2tt_map[string.lower(v)] = k diff --git a/bin/reportlab/lib/formatters.py b/bin/reportlab/lib/formatters.py new file mode 100644 index 00000000000..19230f3c375 --- /dev/null +++ b/bin/reportlab/lib/formatters.py @@ -0,0 +1,100 @@ +#!/bin/env python +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/formatters.py +__version__=''' $Id: formatters.py 2385 2004-06-17 15:26:05Z rgbecker $ ''' +__doc__=""" +These help format numbers and dates in a user friendly way. + +Used by the graphics framework. +""" +import string, sys, os + +class Formatter: + "Base formatter - simply applies python format strings" + def __init__(self, pattern): + self.pattern = pattern + def format(self, obj): + return self.pattern % obj + def __repr__(self): + return "%s('%s')" % (self.__class__.__name__, self.pattern) + def __call__(self, x): + return self.format(x) + + +class DecimalFormatter(Formatter): + """lets you specify how to build a decimal. + + A future NumberFormatter class will take Microsoft-style patterns + instead - "$#,##0.00" is WAY easier than this.""" + def __init__(self, places=2, decimalSep='.', thousandSep=None, prefix=None, suffix=None): + self.places = places + self.dot = decimalSep + self.comma = thousandSep + self.prefix = prefix + self.suffix = suffix + + def format(self, num): + # positivize the numbers + sign=num<0 + if sign: + num = -num + places, sep = self.places, self.dot + strip = places<=0 + if places and strip: places = -places + strInt = ('%.' + str(places) + 'f') % num + if places: + strInt, strFrac = strInt.split('.') + strFrac = sep + strFrac + if strip: + while strFrac and strFrac[-1] in ['0',sep]: strFrac = strFrac[:-1] + else: + strFrac = '' + + if self.comma is not None: + strNew = '' + while strInt: + left, right = strInt[0:-3], strInt[-3:] + if left == '': + #strNew = self.comma + right + strNew + strNew = right + strNew + else: + strNew = self.comma + right + strNew + strInt = left + strInt = strNew + + strBody = strInt + strFrac + if sign: strBody = '-' + strBody + if self.prefix: + strBody = self.prefix + strBody + if self.suffix: + strBody = strBody + self.suffix + return strBody + + def __repr__(self): + return "%s(places=%d, decimalSep=%s, thousandSep=%s, prefix=%s, suffix=%s)" % ( + self.__class__.__name__, + self.places, + repr(self.dot), + repr(self.comma), + repr(self.prefix), + repr(self.suffix) + ) + +if __name__=='__main__': + def t(n, s, places=2, decimalSep='.', thousandSep=None, prefix=None, suffix=None): + f=DecimalFormatter(places,decimalSep,thousandSep,prefix,suffix) + r = f(n) + print "places=%2d dot=%-4s comma=%-4s prefix=%-4s suffix=%-4s result=%10s %s" %(f.places, f.dot, f.comma, f.prefix, f.suffix,r, r==s and 'OK' or 'BAD') + t(1000.9,'1,000.9',1,thousandSep=',') + t(1000.95,'1,001.0',1,thousandSep=',') + t(1000.95,'1,001',-1,thousandSep=',') + t(1000.9,'1,001',0,thousandSep=',') + t(1000.9,'1000.9',1) + t(1000.95,'1001.0',1) + t(1000.95,'1001',-1) + t(1000.9,'1001',0) + t(1000.1,'1000.1',1) + t(1000.55,'1000.6',1) + t(1000.449,'1000.4',-1) + t(1000.45,'1000',0) \ No newline at end of file diff --git a/bin/reportlab/lib/logger.py b/bin/reportlab/lib/logger.py new file mode 100644 index 00000000000..35555873a3e --- /dev/null +++ b/bin/reportlab/lib/logger.py @@ -0,0 +1,61 @@ +#!/bin/env python +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/logger.py +__version__=''' $Id: logger.py 2385 2004-06-17 15:26:05Z rgbecker $ ''' + +from sys import stderr +class Logger: + ''' + An extended file type thing initially equivalent to sys.stderr + You can add/remove file type things; it has a write method + ''' + def __init__(self): + self._fps = [stderr] + self._fns = {} + + def add(self,fp): + '''add the file/string fp to the destinations''' + if type(fp) is StringType: + if fp in self._fns: return + fp = open(fn,'wb') + self._fns[fn] = fp + self._fps.append(fp) + + def remove(self,fp): + '''remove the file/string fp from the destinations''' + if type(fp) is StringType: + if fp not in self._fns: return + fn = fp + fp = self._fns[fn] + del self.fns[fn] + if fp in self._fps: + del self._fps[self._fps.index(fp)] + + def write(self,text): + '''write text to all the destinations''' + if text[-1]!='\n': text=text+'\n' + map(lambda fp,t=text: fp.write(t),self._fps) + + def __call__(self,text): + self.write(text) + +logger=Logger() + +class WarnOnce: + + def __init__(self,kind='Warn'): + self.uttered = {} + self.pfx = '%s: '%kind + self.enabled = 1 + + def once(self,warning): + if not self.uttered.has_key(warning): + if self.enabled: logger.write(self.pfx + warning) + self.uttered[warning] = 1 + + def __call__(self,warning): + self.once(warning) + +warnOnce=WarnOnce() +infoOnce=WarnOnce('Info') \ No newline at end of file diff --git a/bin/reportlab/lib/normalDate.py b/bin/reportlab/lib/normalDate.py new file mode 100644 index 00000000000..efcea715013 --- /dev/null +++ b/bin/reportlab/lib/normalDate.py @@ -0,0 +1,605 @@ +#!/usr/bin/env python +# normalDate.py - version 1.0 - 20000717 +#hacked by Robin Becker 10/Apr/2001 +#major changes include +# using Types instead of type(0) etc +# BusinessDate class +# __radd__, __rsub__ methods +# formatMS stuff + +# derived from an original version created +# by Jeff Bauer of Rubicon Research and used +# with his kind permission +__version__=''' $Id: normalDate.py 2742 2005-12-12 11:58:08Z oualid $ ''' + + + +_bigBangScalar = -4345732 # based on (-9999, 1, 1) BC/BCE minimum +_bigCrunchScalar = 2958463 # based on (9999,12,31) AD/CE maximum +_daysInMonthNormal = [31,28,31,30,31,30,31,31,30,31,30,31] +_daysInMonthLeapYear = [31,29,31,30,31,30,31,31,30,31,30,31] +_dayOfWeekName = ['Monday', 'Tuesday', 'Wednesday', 'Thursday', + 'Friday', 'Saturday', 'Sunday'] +_monthName = ['January', 'February', 'March', 'April', 'May', 'June', + 'July','August','September','October','November','December'] + +from types import IntType, StringType, ListType, TupleType +import string, re, time, datetime +if hasattr(time,'struct_time'): + _DateSeqTypes = (ListType,TupleType,time.struct_time) +else: + _DateSeqTypes = (ListType,TupleType) + +_fmtPat = re.compile('\\{(m{1,5}|yyyy|yy|d{1,4})\\}',re.MULTILINE|re.IGNORECASE) +_iso_re = re.compile(r'(\d\d\d\d|\d\d)-(\d\d)-(\d\d)') + +def getStdMonthNames(): + return map(string.lower,_monthName) + +def getStdShortMonthNames(): + return map(lambda x: x[:3],getStdMonthNames()) + +def getStdDayNames(): + return map(string.lower,_dayOfWeekName) + +def getStdShortDayNames(): + return map(lambda x: x[:3],getStdDayNames()) + +def isLeapYear(year): + """determine if specified year is leap year, returns Python boolean""" + if year < 1600: + if year % 4: + return 0 + else: + return 1 + elif year % 4 != 0: + return 0 + elif year % 100 != 0: + return 1 + elif year % 400 != 0: + return 0 + else: + return 1 + +class NormalDateException(Exception): + """Exception class for NormalDate""" + pass + +class NormalDate: + """ + NormalDate is a specialized class to handle dates without + all the excess baggage (time zones, daylight savings, leap + seconds, etc.) of other date structures. The minimalist + strategy greatly simplifies its implementation and use. + + Internally, NormalDate is stored as an integer with values + in a discontinuous range of -99990101 to 99991231. The + integer value is used principally for storage and to simplify + the user interface. Internal calculations are performed by + a scalar based on Jan 1, 1900. + + Valid NormalDate ranges include (-9999,1,1) B.C.E. through + (9999,12,31) C.E./A.D. + + 1.0 - No changes, except the version number. After 3 years of use + by various parties I think we can consider it stable. + 0.8 - added Prof. Stephen Walton's suggestion for a range method + - module author resisted the temptation to use lambda <0.5 wink> + 0.7 - added Dan Winkler's suggestions for __add__, __sub__ methods + 0.6 - modifications suggested by Kevin Digweed to fix: + - dayOfWeek, dayOfWeekAbbrev, clone methods + - permit NormalDate to be a better behaved superclass + 0.5 - minor tweaking + 0.4 - added methods __cmp__, __hash__ + - added Epoch variable, scoped to the module + - added setDay, setMonth, setYear methods + 0.3 - minor touch-ups + 0.2 - fixed bug for certain B.C.E leap years + - added Jim Fulton's suggestions for short alias class name =ND + and __getstate__, __setstate__ methods + + Special thanks: Roedy Green + """ + def __init__(self, normalDate=None): + """ + Accept 1 of 4 values to initialize a NormalDate: + 1. None - creates a NormalDate for the current day + 2. integer in yyyymmdd format + 3. string in yyyymmdd format + 4. tuple in (yyyy, mm, dd) - localtime/gmtime can also be used + """ + if normalDate is None: + self.setNormalDate(time.localtime(time.time())) + else: + self.setNormalDate(normalDate) + + def add(self, days): + """add days to date; use negative integers to subtract""" + if not type(days) is IntType: + raise NormalDateException( \ + 'add method parameter must be integer type') + self.normalize(self.scalar() + days) + + def __add__(self, days): + """add integer to normalDate and return a new, calculated value""" + if not type(days) is IntType: + raise NormalDateException( \ + '__add__ parameter must be integer type') + cloned = self.clone() + cloned.add(days) + return cloned + + def __radd__(self,days): + '''for completeness''' + return self.__add__(days) + + def clone(self): + """return a cloned instance of this normalDate""" + return self.__class__(self.normalDate) + + def __cmp__(self, target): + if target is None: + return 1 + elif not hasattr(target, 'normalDate'): + return 1 + else: + return cmp(self.normalDate, target.normalDate) + + def day(self): + """return the day as integer 1-31""" + return int(repr(self.normalDate)[-2:]) + + def dayOfWeek(self): + """return integer representing day of week, Mon=0, Tue=1, etc.""" + return apply(dayOfWeek, self.toTuple()) + + def dayOfWeekAbbrev(self): + """return day of week abbreviation for current date: Mon, Tue, etc.""" + return _dayOfWeekName[self.dayOfWeek()][:3] + + def dayOfWeekName(self): + """return day of week name for current date: Monday, Tuesday, etc.""" + return _dayOfWeekName[self.dayOfWeek()] + + def dayOfYear(self): + """day of year""" + if self.isLeapYear(): + daysByMonth = _daysInMonthLeapYear + else: + daysByMonth = _daysInMonthNormal + priorMonthDays = 0 + for m in xrange(self.month() - 1): + priorMonthDays = priorMonthDays + daysByMonth[m] + return self.day() + priorMonthDays + + def daysBetweenDates(self, normalDate): + """ + return value may be negative, since calculation is + self.scalar() - arg + """ + if type(normalDate) is _NDType: + return self.scalar() - normalDate.scalar() + else: + return self.scalar() - NormalDate(normalDate).scalar() + + def equals(self, target): + if type(target) is _NDType: + if target is None: + return self.normalDate is None + else: + return self.normalDate == target.normalDate + else: + return 0 + + def endOfMonth(self): + """returns (cloned) last day of month""" + return self.__class__(self.__repr__()[-8:-2]+str(self.lastDayOfMonth())) + + def firstDayOfMonth(self): + """returns (cloned) first day of month""" + return self.__class__(self.__repr__()[-8:-2]+"01") + + def formatUS(self): + """return date as string in common US format: MM/DD/YY""" + d = self.__repr__() + return "%s/%s/%s" % (d[-4:-2], d[-2:], d[-6:-4]) + + def formatUSCentury(self): + """return date as string in 4-digit year US format: MM/DD/YYYY""" + d = self.__repr__() + return "%s/%s/%s" % (d[-4:-2], d[-2:], d[-8:-4]) + + def _fmtM(self): + return str(self.month()) + + def _fmtMM(self): + return '%02d' % self.month() + + def _fmtMMM(self): + return self.monthAbbrev() + + def _fmtMMMM(self): + return self.monthName() + + def _fmtMMMMM(self): + return self.monthName()[0] + + def _fmtD(self): + return str(self.day()) + + def _fmtDD(self): + return '%02d' % self.day() + + def _fmtDDD(self): + return self.dayOfWeekAbbrev() + + def _fmtDDDD(self): + return self.dayOfWeekName() + + def _fmtYY(self): + return '%02d' % (self.year()%100) + + def _fmtYYYY(self): + return str(self.year()) + + def formatMS(self,fmt): + '''format like MS date using the notation + {YY} --> 2 digit year + {YYYY} --> 4 digit year + {M} --> month as digit + {MM} --> 2 digit month + {MMM} --> abbreviated month name + {MMMM} --> monthname + {MMMMM} --> first character of monthname + {D} --> day of month as digit + {DD} --> 2 digit day of month + {DDD} --> abrreviated weekday name + {DDDD} --> weekday name + ''' + r = fmt[:] + f = 0 + while 1: + m = _fmtPat.search(r,f) + if m: + y = getattr(self,'_fmt'+string.upper(m.group()[1:-1]))() + i, j = m.span() + r = (r[0:i] + y) + r[j:] + f = i + len(y) + else: + return r + + def __getstate__(self): + """minimize persistent storage requirements""" + return self.normalDate + + def __hash__(self): + return hash(self.normalDate) + + def __int__(self): + return self.normalDate + + def isLeapYear(self): + """ + determine if specified year is leap year, returning true (1) or + false (0) + """ + return isLeapYear(self.year()) + + def _isValidNormalDate(self, normalDate): + """checks for date validity in [-]yyyymmdd format""" + if type(normalDate) is not IntType: + return 0 + if len(repr(normalDate)) > 9: + return 0 + if normalDate < 0: + dateStr = "%09d" % normalDate + else: + dateStr = "%08d" % normalDate + if len(dateStr) < 8: + return 0 + elif len(dateStr) == 9: + if (dateStr[0] != '-' and dateStr[0] != '+'): + return 0 + year = int(dateStr[:-4]) + if year < -9999 or year > 9999 or year == 0: + return 0 # note: zero (0) is not a valid year + month = int(dateStr[-4:-2]) + if month < 1 or month > 12: + return 0 + if isLeapYear(year): + maxDay = _daysInMonthLeapYear[month - 1] + else: + maxDay = _daysInMonthNormal[month - 1] + day = int(dateStr[-2:]) + if day < 1 or day > maxDay: + return 0 + if year == 1582 and month == 10 and day > 4 and day < 15: + return 0 # special case of 10 days dropped: Oct 5-14, 1582 + return 1 + + def lastDayOfMonth(self): + """returns last day of the month as integer 28-31""" + if self.isLeapYear(): + return _daysInMonthLeapYear[self.month() - 1] + else: + return _daysInMonthNormal[self.month() - 1] + + def localeFormat(self): + """override this method to use your preferred locale format""" + return self.formatUS() + + def month(self): + """returns month as integer 1-12""" + return int(repr(self.normalDate)[-4:-2]) + + def monthAbbrev(self): + """returns month as a 3-character abbreviation, i.e. Jan, Feb, etc.""" + return _monthName[self.month() - 1][:3] + + def monthName(self): + """returns month name, i.e. January, February, etc.""" + return _monthName[self.month() - 1] + + def normalize(self, scalar): + """convert scalar to normalDate""" + if scalar < _bigBangScalar: + msg = "normalize(%d): scalar below minimum" % \ + _bigBangScalar + raise NormalDateException(msg) + if scalar > _bigCrunchScalar: + msg = "normalize(%d): scalar exceeds maximum" % \ + _bigCrunchScalar + raise NormalDateException(msg) + from math import floor + if scalar >= -115860: + year = 1600 + int(floor((scalar + 109573) / 365.2425)) + elif scalar >= -693597: + year = 4 + int(floor((scalar + 692502) / 365.2425)) + else: + year = -4 + int(floor((scalar + 695058) / 365.2425)) + days = scalar - firstDayOfYear(year) + 1 + if days <= 0: + year = year - 1 + days = scalar - firstDayOfYear(year) + 1 + daysInYear = 365 + if isLeapYear(year): + daysInYear = daysInYear + 1 + if days > daysInYear: + year = year + 1 + days = scalar - firstDayOfYear(year) + 1 + # add 10 days if between Oct 15, 1582 and Dec 31, 1582 + if (scalar >= -115860 and scalar <= -115783): + days = days + 10 + if isLeapYear(year): + daysByMonth = _daysInMonthLeapYear + else: + daysByMonth = _daysInMonthNormal + dc = 0; month = 12 + for m in xrange(len(daysByMonth)): + dc = dc + daysByMonth[m] + if dc >= days: + month = m + 1 + break + # add up the days in prior months + priorMonthDays = 0 + for m in xrange(month - 1): + priorMonthDays = priorMonthDays + daysByMonth[m] + day = days - priorMonthDays + self.setNormalDate((year, month, day)) + + def range(self, days): + """Return a range of normalDates as a list. Parameter + may be an int or normalDate.""" + if type(days) is not IntType: + days = days - self # if not int, assume arg is normalDate type + r = [] + for i in range(days): + r.append(self + i) + return r + + def __repr__(self): + """print format: [-]yyyymmdd""" + # Note: When disassembling a NormalDate string, be sure to + # count from the right, i.e. epochMonth = int(`Epoch`[-4:-2]), + # or the slice won't work for dates B.C. + if self.normalDate < 0: + return "%09d" % self.normalDate + else: + return "%08d" % self.normalDate + + def scalar(self): + """days since baseline date: Jan 1, 1900""" + (year, month, day) = self.toTuple() + days = firstDayOfYear(year) + day - 1 + if self.isLeapYear(): + for m in xrange(month - 1): + days = days + _daysInMonthLeapYear[m] + else: + for m in xrange(month - 1): + days = days + _daysInMonthNormal[m] + if year == 1582: + if month > 10 or (month == 10 and day > 4): + days = days - 10 + return days + + def setDay(self, day): + """set the day of the month""" + maxDay = self.lastDayOfMonth() + if day < 1 or day > maxDay: + msg = "day is outside of range 1 to %d" % maxDay + raise NormalDateException(msg) + (y, m, d) = self.toTuple() + self.setNormalDate((y, m, day)) + + def setMonth(self, month): + """set the month [1-12]""" + if month < 1 or month > 12: + raise NormalDateException('month is outside range 1 to 12') + (y, m, d) = self.toTuple() + self.setNormalDate((y, month, d)) + + def setNormalDate(self, normalDate): + """ + accepts date as scalar string/integer (yyyymmdd) or tuple + (year, month, day, ...)""" + tn=type(normalDate) + if tn is IntType: + self.normalDate = normalDate + elif tn is StringType: + try: + self.normalDate = int(normalDate) + except: + m = _iso_re.match(normalDate) + if m: + self.setNormalDate(m.group(1)+m.group(2)+m.group(3)) + else: + raise NormalDateException("unable to setNormalDate(%s)" % `normalDate`) + elif tn in _DateSeqTypes: + self.normalDate = int("%04d%02d%02d" % normalDate[:3]) + elif tn is _NDType: + self.normalDate = normalDate.normalDate + elif isinstance(normalDate,(datetime.datetime,datetime.date)): + self.normalDate = (normalDate.year*100+normalDate.month)*100+normalDate.day + if not self._isValidNormalDate(self.normalDate): + raise NormalDateException("unable to setNormalDate(%s)" % `normalDate`) + + def setYear(self, year): + if year == 0: + raise NormalDateException('cannot set year to zero') + elif year < -9999: + raise NormalDateException('year cannot be less than -9999') + elif year > 9999: + raise NormalDateException('year cannot be greater than 9999') + (y, m, d) = self.toTuple() + self.setNormalDate((year, m, d)) + + __setstate__ = setNormalDate + + def __sub__(self, v): + if type(v) is IntType: + return self.__add__(-v) + return self.scalar() - v.scalar() + + def __rsub__(self,v): + if type(v) is IntType: + return NormalDate(v) - self + else: + return v.scalar() - self.scalar() + + def toTuple(self): + """return date as (year, month, day) tuple""" + return (self.year(), self.month(), self.day()) + + def year(self): + """return year in yyyy format, negative values indicate B.C.""" + return int(repr(self.normalDate)[:-4]) + +################# Utility functions ################# + +def bigBang(): + """return lower boundary as a NormalDate""" + return NormalDate((-9999, 1, 1)) + +def bigCrunch(): + """return upper boundary as a NormalDate""" + return NormalDate((9999, 12, 31)) + +def dayOfWeek(y, m, d): + """return integer representing day of week, Mon=0, Tue=1, etc.""" + if m == 1 or m == 2: + m = m + 12 + y = y - 1 + return (d + 2*m + 3*(m+1)/5 + y + y/4 - y/100 + y/400) % 7 + +def firstDayOfYear(year): + """number of days to the first of the year, relative to Jan 1, 1900""" + if type(year) is not IntType: + msg = "firstDayOfYear() expected integer, got %s" % type(year) + raise NormalDateException(msg) + if year == 0: + raise NormalDateException('first day of year cannot be zero (0)') + elif year < 0: # BCE calculation + firstDay = (year * 365) + int((year - 1) / 4) - 693596 + else: # CE calculation + leapAdjust = int((year + 3) / 4) + if year > 1600: + leapAdjust = leapAdjust - int((year + 99 - 1600) / 100) + \ + int((year + 399 - 1600) / 400) + firstDay = year * 365 + leapAdjust - 693963 + if year > 1582: + firstDay = firstDay - 10 + return firstDay + +def FND(d): + '''convert to ND if required''' + return (type(d) is _NDType) and d or ND(d) + +Epoch=bigBang() +ND=NormalDate +_NDType = type(Epoch) +BDEpoch=ND(15821018) +BDEpochScalar = -115857 + +class BusinessDate(NormalDate): + """ + Specialised NormalDate + """ + def add(self, days): + """add days to date; use negative integers to subtract""" + if not type(days) is IntType: + raise NormalDateException('add method parameter must be integer type') + self.normalize(self.scalar() + days) + + def __add__(self, days): + """add integer to BusinessDate and return a new, calculated value""" + if not type(days) is IntType: + raise NormalDateException('__add__ parameter must be integer type') + cloned = self.clone() + cloned.add(days) + return cloned + + def __sub__(self, v): + return type(v) is IntType and self.__add__(-v) or self.scalar() - v.scalar() + + def asNormalDate(self): + return ND(self.normalDate) + + def daysBetweenDates(self, normalDate): + return self.asNormalDate.daysBetweenDates(normalDate) + + def _checkDOW(self): + if self.dayOfWeek()>4: raise NormalDateException("%s isn't a business day" % `self.normalDate`) + + def normalize(self, i): + i = int(i) + NormalDate.normalize(self,(i/5)*7+i%5+BDEpochScalar) + + def scalar(self): + d = self.asNormalDate() + i = d - BDEpoch #luckily BDEpoch is a Monday so we don't have a problem + #concerning the relative weekday + return 5*(i/7) + i%7 + + def setNormalDate(self, normalDate): + NormalDate.setNormalDate(self,normalDate) + self._checkDOW() + +if __name__ == '__main__': + today = NormalDate() + print "NormalDate test:" + print " Today (%s) is: %s %s" % (today, today.dayOfWeekAbbrev(), today.localeFormat()) + yesterday = today - 1 + print " Yesterday was: %s %s" % (yesterday.dayOfWeekAbbrev(), yesterday.localeFormat()) + tomorrow = today + 1 + print " Tomorrow will be: %s %s" % (tomorrow.dayOfWeekAbbrev(), tomorrow.localeFormat()) + print " Days between tomorrow and yesterday: %d" % (tomorrow - yesterday) + print today.formatMS('{d}/{m}/{yy}') + print today.formatMS('{dd}/{m}/{yy}') + print today.formatMS('{ddd} {d}/{m}/{yy}') + print today.formatMS('{dddd} {d}/{m}/{yy}') + print today.formatMS('{d}/{mm}/{yy}') + print today.formatMS('{d}/{mmm}/{yy}') + print today.formatMS('{d}/{mmmm}/{yy}') + print today.formatMS('{d}/{m}/{yyyy}') + b = BusinessDate('20010116') + print 'b=',b,'b.scalar()', b.scalar() diff --git a/bin/reportlab/lib/pagesizes.py b/bin/reportlab/lib/pagesizes.py new file mode 100644 index 00000000000..75fb8ddd588 --- /dev/null +++ b/bin/reportlab/lib/pagesizes.py @@ -0,0 +1,55 @@ +#!/bin/env python +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/pagesizes.py + +"""This module defines a few common page sizes in points (1/72 inch). +To be expanded to include things like label sizes, envelope windows +etc.""" +__version__=''' $Id: pagesizes.py 2385 2004-06-17 15:26:05Z rgbecker $ ''' + +from reportlab.lib.units import cm, inch + +_W, _H = (21*cm, 29.7*cm) + +A6 = (_W*.5, _H*.5) +A5 = (_H*.5, _W) +A4 = (_W, _H) +A3 = (_H, _W*2) +A2 = (_W*2, _H*2) +A1 = (_H*2, _W*4) +A0 = (_W*4, _H*4) + +LETTER = (8.5*inch, 11*inch) +LEGAL = (8.5*inch, 14*inch) +ELEVENSEVENTEEN = (11*inch, 17*inch) +# lower case is deprecated as of 12/2001, but here +# for compatability +letter=LETTER +legal=LEGAL +elevenSeventeen = ELEVENSEVENTEEN + +_BW, _BH = (25*cm, 35.3*cm) +B6 = (_BW*.5, _BH*.5) +B5 = (_BH*.5, _BW) +B4 = (_BW, _BH) +B3 = (_BH*2, _BW) +B2 = (_BW*2, _BH*2) +B1 = (_BH*4, _BW*2) +B0 = (_BW*4, _BH*4) + +def landscape(pagesize): + """Use this to get page orientation right""" + a, b = pagesize + if a < b: + return (b, a) + else: + return (a, b) + +def portrait(pagesize): + """Use this to get page orientation right""" + a, b = pagesize + if a >= b: + return (b, a) + else: + return (a, b) \ No newline at end of file diff --git a/bin/reportlab/lib/randomtext.py b/bin/reportlab/lib/randomtext.py new file mode 100644 index 00000000000..075b9d711c7 --- /dev/null +++ b/bin/reportlab/lib/randomtext.py @@ -0,0 +1,348 @@ +#!/bin/env python +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/randomtext.py + +__version__=''' $Id: randomtext.py 2436 2004-09-11 14:18:33Z rgbecker $ ''' + +############################################################################### +# generates so-called 'Greek Text' for use in filling documents. +############################################################################### +""" +This module exposes a function randomText() which generates paragraphs. +These can be used when testing out document templates and stylesheets. +A number of 'themes' are provided - please contribute more! +We need some real Greek text too. + +There are currently six themes provided: + STARTUP (words suitable for a business plan - or not as the case may be), + COMPUTERS (names of programming languages and operating systems etc), + BLAH (variations on the word 'blah'), + BUZZWORD (buzzword bingo), + STARTREK (Star Trek), + PRINTING (print-related terms) + PYTHON (snippets and quotes from Monty Python) + CHOMSKY (random lingusitic nonsense) + +EXAMPLE USAGE: + from reportlab.lib import randomtext + print randomtext.randomText(randomtext.PYTHON, 10) + + This prints a random number of random sentences (up to a limit + of ten) using the theme 'PYTHON'. + +""" + +#theme one :-) +STARTUP = ['strategic', 'direction', 'proactive', 'venture capital', + 'reengineering', 'forecast', 'resources', 'SWOT analysis', + 'forward-thinking', 'profit', 'growth', 'doubletalk', 'B2B', 'B2C', + 'venture capital', 'IPO', "NASDAQ meltdown - we're all doomed!"] + +#theme two - computery things. +COMPUTERS = ['Python', 'Perl', 'Pascal', 'Java', 'Javascript', + 'VB', 'Basic', 'LISP', 'Fortran', 'ADA', 'APL', 'C', 'C++', + 'assembler', 'Larry Wall', 'Guido van Rossum', 'XML', 'HTML', + 'cgi', 'cgi-bin', 'Amiga', 'Macintosh', 'Dell', 'Microsoft', + 'firewall', 'server', 'Linux', 'Unix', 'MacOS', 'BeOS', 'AS/400', + 'sendmail', 'TCP/IP', 'SMTP', 'RFC822-compliant', 'dynamic', + 'Internet', 'A/UX', 'Amiga OS', 'BIOS', 'boot managers', 'CP/M', + 'DOS', 'file system', 'FreeBSD', 'Freeware', 'GEOS', 'GNU', + 'Hurd', 'Linux', 'Mach', 'Macintosh OS', 'mailing lists', 'Minix', + 'Multics', 'NetWare', 'NextStep', 'OS/2', 'Plan 9', 'Realtime', + 'UNIX', 'VMS', 'Windows', 'X Windows', 'Xinu', 'security', 'Intel', + 'encryption', 'PGP' , 'software', 'ActiveX', 'AppleScript', 'awk', + 'BETA', 'COBOL', 'Delphi', 'Dylan', 'Eiffel', 'extreme programming', + 'Forth', 'Fortran', 'functional languages', 'Guile', 'format your hard drive', + 'Icon', 'IDL', 'Infer', 'Intercal', 'J', 'Java', 'JavaScript', 'CD-ROM', + 'JCL', 'Lisp', '"literate programming"', 'Logo', 'MUMPS', 'C: drive', + 'Modula-2', 'Modula-3', 'Oberon', 'Occam', 'OpenGL', 'parallel languages', + 'Pascal', 'Perl', 'PL/I', 'PostScript', 'Prolog', 'hardware', 'Blue Screen of Death', + 'Rexx', 'RPG', 'Scheme', 'scripting languages', 'Smalltalk', 'crash!', 'disc crash', + 'Spanner', 'SQL', 'Tcl/Tk', 'TeX', 'TOM', 'Visual', 'Visual Basic', '4GL', + 'VRML', 'Virtual Reality Modeling Language', 'difference engine', '...went into "yo-yo mode"', + 'Sun', 'Sun Microsystems', 'Hewlett Packard', 'output device', + 'CPU', 'memory', 'registers', 'monitor', 'TFT display', 'plasma screen', + 'bug report', '"mis-feature"', '...millions of bugs!', 'pizza', + '"illiterate programming"','...lots of pizza!', 'pepperoni pizza', + 'coffee', 'Jolt Cola[TM]', 'beer', 'BEER!'] + +#theme three - 'blah' - for when you want to be subtle. :-) +BLAH = ['Blah', 'BLAH', 'blahblah', 'blahblahblah', 'blah-blah', + 'blah!', '"Blah Blah Blah"', 'blah-de-blah', 'blah?', 'blah!!!', + 'blah...', 'Blah.', 'blah;', 'blah, Blah, BLAH!', 'Blah!!!'] + +#theme four - 'buzzword bingo' time! +BUZZWORD = ['intellectual capital', 'market segment', 'flattening', + 'regroup', 'platform', 'client-based', 'long-term', 'proactive', + 'quality vector', 'out of the loop', 'implement', + 'streamline', 'cost-centered', 'phase', 'synergy', + 'synergize', 'interactive', 'facilitate', + 'appropriate', 'goal-setting', 'empowering', 'low-risk high-yield', + 'peel the onion', 'goal', 'downsize', 'result-driven', + 'conceptualize', 'multidisciplinary', 'gap analysis', 'dysfunctional', + 'networking', 'knowledge management', 'goal-setting', + 'mastery learning', 'communication', 'real-estate', 'quarterly', + 'scalable', 'Total Quality Management', 'best of breed', + 'nimble', 'monetize', 'benchmark', 'hardball', + 'client-centered', 'vision statement', 'empowerment', + 'lean & mean', 'credibility', 'synergistic', + 'backward-compatible', 'hardball', 'stretch the envelope', + 'bleeding edge', 'networking', 'motivation', 'best practice', + 'best of breed', 'implementation', 'Total Quality Management', + 'undefined', 'disintermediate', 'mindset', 'architect', + 'gap analysis', 'morale', 'objective', 'projection', + 'contribution', 'proactive', 'go the extra mile', 'dynamic', + 'world class', 'real estate', 'quality vector', 'credibility', + 'appropriate', 'platform', 'projection', 'mastery learning', + 'recognition', 'quality', 'scenario', 'performance based', + 'solutioning', 'go the extra mile', 'downsize', 'phase', + 'networking', 'experiencing slippage', 'knowledge management', + 'high priority', 'process', 'ethical', 'value-added', 'implement', + 're-factoring', 're-branding', 'embracing change'] + +#theme five - Star Trek +STARTREK = ['Starfleet', 'Klingon', 'Romulan', 'Cardassian', 'Vulcan', + 'Benzite', 'IKV Pagh', 'emergency transponder', 'United Federation of Planets', + 'Bolian', "K'Vort Class Bird-of-Prey", 'USS Enterprise', 'USS Intrepid', + 'USS Reliant', 'USS Voyager', 'Starfleet Academy', 'Captain Picard', + 'Captain Janeway', 'Tom Paris', 'Harry Kim', 'Counsellor Troi', + 'Lieutenant Worf', 'Lieutenant Commander Data', 'Dr. Beverly Crusher', + 'Admiral Nakamura', 'Irumodic Syndrome', 'Devron system', 'Admiral Pressman', + 'asteroid field', 'sensor readings', 'Binars', 'distress signal', 'shuttlecraft', + 'cloaking device', 'shuttle bay 2', 'Dr. Pulaski', 'Lwaxana Troi', 'Pacifica', + 'William Riker', "Chief O'Brian", 'Soyuz class science vessel', 'Wolf-359', + 'Galaxy class vessel', 'Utopia Planitia yards', 'photon torpedo', 'Archer IV', + 'quantum flux', 'spacedock', 'Risa', 'Deep Space Nine', 'blood wine', + 'quantum torpedoes', 'holodeck', 'Romulan Warbird', 'Betazoid', 'turbolift', 'battle bridge', + 'Memory Alpha', '...with a phaser!', 'Romulan ale', 'Ferrengi', 'Klingon opera', + 'Quark', 'wormhole', 'Bajoran', 'cruiser', 'warship', 'battlecruiser', '"Intruder alert!"', + 'scout ship', 'science vessel', '"Borg Invasion imminent!" ', '"Abandon ship!"', + 'Red Alert!', 'warp-core breech', '"All hands abandon ship! This is not a drill!"'] + +#theme six - print-related terms +PRINTING = ['points', 'picas', 'leading', 'kerning', 'CMYK', 'offset litho', + 'type', 'font family', 'typography', 'type designer', + 'baseline', 'white-out type', 'WOB', 'bicameral', 'bitmap', + 'blockletter', 'bleed', 'margin', 'body', 'widow', 'orphan', + 'cicero', 'cursive', 'letterform', 'sidehead', 'dingbat', 'leader', + 'DPI', 'drop-cap', 'paragraph', 'En', 'Em', 'flush left', 'left justified', + 'right justified', 'centered', 'italic', 'Latin letterform', 'ligature', + 'uppercase', 'lowercase', 'serif', 'sans-serif', 'weight', 'type foundry', + 'fleuron', 'folio', 'gutter', 'whitespace', 'humanist letterform', 'caption', + 'page', 'frame', 'ragged setting', 'flush-right', 'rule', 'drop shadows', + 'prepress', 'spot-colour', 'duotones', 'colour separations', 'four-colour printing', + 'Pantone[TM]', 'service bureau', 'imagesetter'] + +#it had to be done!... +#theme seven - the "full Monty"! +PYTHON = ['Good evening ladies and Bruces','I want to buy some cheese', 'You do have some cheese, do you?', + "Of course sir, it's a cheese shop sir, we've got...",'discipline?... naked? ... With a melon!?', + 'The Church Police!!' , "There's a dead bishop on the landing", 'Would you like a twist of lemming sir?', + '"Conquistador Coffee brings a new meaning to the word vomit"','Your lupins please', + 'Crelm Toothpaste, with the miracle ingredient Fraudulin', + "Well there's the first result and the Silly Party has held Leicester.", + 'Hello, I would like to buy a fish license please', "Look, it's people like you what cause unrest!", + "When we got home, our Dad would thrash us to sleep with his belt!", 'Luxury', "Gumby Brain Specialist", + "My brain hurts!!!", "My brain hurts too.", "How not to be seen", + "In this picture there are 47 people. None of them can be seen", + "Mrs Smegma, will you stand up please?", + "Mr. Nesbitt has learned the first lesson of 'Not Being Seen', not to stand up.", + "My hovercraft is full of eels", "Ah. You have beautiful thighs.", "My nipples explode with delight", + "Drop your panties Sir William, I cannot wait 'til lunchtime", + "I'm a completely self-taught idiot.", "I always wanted to be a lumberjack!!!", + "Told you so!! Oh, coitus!!", "", + "Nudge nudge?", "Know what I mean!", "Nudge nudge, nudge nudge?", "Say no more!!", + "Hello, well it's just after 8 o'clock, and time for the penguin on top of your television set to explode", + "Oh, intercourse the penguin!!", "Funny that penguin being there, isn't it?", + "I wish to register a complaint.", "Now that's what I call a dead parrot", "Pining for the fjords???", + "No, that's not dead, it's ,uhhhh, resting", "This is an ex-parrot!!", + "That parrot is definitely deceased.", "No, no, no - it's spelt Raymond Luxury Yach-t, but it's pronounced 'Throatwobbler Mangrove'.", + "You're a very silly man and I'm not going to interview you.", "No Mungo... never kill a customer." + "And I'd like to conclude by putting my finger up my nose", + "egg and Spam", "egg bacon and Spam", "egg bacon sausage and Spam", "Spam bacon sausage and Spam", + "Spam egg Spam Spam bacon and Spam", "Spam sausage Spam Spam Spam bacon Spam tomato and Spam", + "Spam Spam Spam egg and Spam", "Spam Spam Spam Spam Spam Spam baked beans Spam Spam Spam", + "Spam!!", "I don't like Spam!!!", "You can't have egg, bacon, Spam and sausage without the Spam!", + "I'll have your Spam. I Love it!", + "I'm having Spam Spam Spam Spam Spam Spam Spam baked beans Spam Spam Spam and Spam", + "Have you got anything without Spam?", "There's Spam egg sausage and Spam, that's not got much Spam in it.", + "No one expects the Spanish Inquisition!!", "Our weapon is surprise, surprise and fear!", + "Get the comfy chair!", "Amongst our weaponry are such diverse elements as: fear, surprise, ruthless efficiency, an almost fanatical devotion to the Pope, and nice red uniforms - Oh damn!", + "Nobody expects the... Oh bugger!", "What swims in the sea and gets caught in nets? Henri Bergson?", + "Goats. Underwater goats with snorkels and flippers?", "A buffalo with an aqualung?", + "Dinsdale was a looney, but he was a happy looney.", "Dinsdale!!", + "The 127th Upper-Class Twit of the Year Show", "What a great Twit!", + "thought by many to be this year's outstanding twit", + "...and there's a big crowd here today to see these prize idiots in action.", + "And now for something completely different.", "Stop that, it's silly", + "We interrupt this program to annoy you and make things generally irritating", + "This depraved and degrading spectacle is going to stop right now, do you hear me?", + "Stop right there!", "This is absolutely disgusting and I'm not going to stand for it", + "I object to all this sex on the television. I mean, I keep falling off", + "Right! Stop that, it's silly. Very silly indeed", "Very silly indeed", "Lemon curry?", + "And now for something completely different, a man with 3 buttocks", + "I've heard of unisex, but I've never had it", "That's the end, stop the program! Stop it!"] +leadins=[ + "To characterize a linguistic level L,", + "On the other hand,", + "This suggests that", + "It appears that", + "Furthermore,", + "We will bring evidence in favor of the following thesis: ", + "To provide a constituent structure for T(Z,K),", + "From C1, it follows that", + "For any transformation which is sufficiently diversified in application to be of any interest,", + "Analogously,", + "Clearly,", + "Note that", + "Of course,", + "Suppose, for instance, that", + "Thus", + "With this clarification,", + "Conversely,", + "We have already seen that", + "By combining adjunctions and certain deformations,", + "I suggested that these results would follow from the assumption that", + "If the position of the trace in (99c) were only relatively inaccessible to movement,", + "However, this assumption is not correct, since", + "Comparing these examples with their parasitic gap counterparts in (96) and (97), we see that", + "In the discussion of resumptive pronouns following (81),", + "So far,", + "Nevertheless,", + "For one thing,", + "Summarizing, then, we assume that", + "A consequence of the approach just outlined is that", + "Presumably,", + "On our assumptions,", + "It may be, then, that", + "It must be emphasized, once again, that", + "Let us continue to suppose that", + "Notice, incidentally, that", + "A majority of informed linguistic specialists agree that", + ] + +subjects = [ + "the notion of level of grammaticalness", + "a case of semigrammaticalness of a different sort", + "most of the methodological work in modern linguistics", + "a subset of English sentences interesting on quite independent grounds", + "the natural general principle that will subsume this case", + "an important property of these three types of EC", + "any associated supporting element", + "the appearance of parasitic gaps in domains relatively inaccessible to ordinary extraction", + "the speaker-hearer's linguistic intuition", + "the descriptive power of the base component", + "the earlier discussion of deviance", + "this analysis of a formative as a pair of sets of features", + "this selectionally introduced contextual feature", + "a descriptively adequate grammar", + "the fundamental error of regarding functional notions as categorial", + "relational information", + "the systematic use of complex symbols", + "the theory of syntactic features developed earlier", + ] + +verbs= [ + "can be defined in such a way as to impose", + "delimits", + "suffices to account for", + "cannot be arbitrary in", + "is not subject to", + "does not readily tolerate", + "raises serious doubts about", + "is not quite equivalent to", + "does not affect the structure of", + "may remedy and, at the same time, eliminate", + "is not to be considered in determining", + "is to be regarded as", + "is unspecified with respect to", + "is, apparently, determined by", + "is necessary to impose an interpretation on", + "appears to correlate rather closely with", + "is rather different from", + ] + +objects = [ + "problems of phonemic and morphological analysis.", + "a corpus of utterance tokens upon which conformity has been defined by the paired utterance test.", + "the traditional practice of grammarians.", + "the levels of acceptability from fairly high (e.g. (99a)) to virtual gibberish (e.g. (98d)).", + "a stipulation to place the constructions into these various categories.", + "a descriptive fact.", + "a parasitic gap construction.", + "the extended c-command discussed in connection with (34).", + "the ultimate standard that determines the accuracy of any proposed grammar.", + "the system of base rules exclusive of the lexicon.", + "irrelevant intervening contexts in selectional rules.", + "nondistinctness in the sense of distinctive feature theory.", + "a general convention regarding the forms of the grammar.", + "an abstract underlying order.", + "an important distinction in language use.", + "the requirement that branching is not tolerated within the dominance scope of a complex symbol.", + "the strong generative capacity of the theory.", + ] + +def format_wisdom(text,line_length=72): + try: + import textwrap + return textwrap.fill(text, line_length) + except: + return text + +def chomsky(times = 1): + if not isinstance(times, int): + return format_wisdom(__doc__) + import random + prevparts = [] + newparts = [] + output = [] + for i in xrange(times): + for partlist in (leadins, subjects, verbs, objects): + while 1: + part = random.choice(partlist) + if part not in prevparts: + break + newparts.append(part) + output.append(' '.join(newparts)) + prevparts = newparts + newparts = [] + return format_wisdom(' '.join(output)) + +from reportlab import rl_config +if rl_config.invariant: + if not getattr(rl_config,'_random',None): + rl_config._random = 1 + import random + random.seed(2342471922L) + del random +del rl_config + +def randomText(theme=STARTUP, sentences=5): + #this may or may not be appropriate in your company + if type(theme)==type(''): + if theme.lower()=='chomsky': return chomsky(sentences) + elif theme.upper() in ('STARTUP','COMPUTERS','BLAH','BUZZWORD','STARTREK','PRINTING','PYTHON'): + theme = globals()[theme] + else: + raise ValueError('Unknown theme "%s"' % theme) + + from random import randint, choice + + RANDOMWORDS = theme + + #sentences = 5 + output = "" + for sentenceno in range(randint(1,sentences)): + output = output + 'Blah' + for wordno in range(randint(10,25)): + if randint(0,4)==0: + word = choice(RANDOMWORDS) + else: + word = 'blah' + output = output + ' ' +word + output = output+'. ' + return output + +if __name__=='__main__': + print chomsky(5) diff --git a/bin/reportlab/lib/rltempfile.py b/bin/reportlab/lib/rltempfile.py new file mode 100644 index 00000000000..d4e58aab507 --- /dev/null +++ b/bin/reportlab/lib/rltempfile.py @@ -0,0 +1,29 @@ +#Copyright ReportLab Europe Ltd. 2000-2006 +#see license.txt for license details +# $URI:$ +__version__=''' $Id: rltempfile.py 2892 2006-05-19 14:16:02Z rgbecker $ ''' +_rl_tempdir=None +__all__ = ('get_rl_tempdir', 'get_rl_tempdir') +import os, tempfile +def _rl_getuid(): + if hasattr(os,'getuid'): + return os.getuid() + else: + return '' + +def get_rl_tempdir(*subdirs): + global _rl_tempdir + if _rl_tempdir is None: + _rl_tempdir = os.path.join(tempfile.gettempdir(),'ReportLab_tmp%s' % str(_rl_getuid())) + d = _rl_tempdir + if subdirs: d = os.path.join(*((d,)+subdirs)) + try: + os.makedirs(d) + except: + pass + return d + +def get_rl_tempfile(fn=None): + if not fn: + fn = tempfile.mktemp() + return os.path.join(get_rl_tempdir(),fn) diff --git a/bin/reportlab/lib/rparsexml.py b/bin/reportlab/lib/rparsexml.py new file mode 100644 index 00000000000..37982050ee2 --- /dev/null +++ b/bin/reportlab/lib/rparsexml.py @@ -0,0 +1,431 @@ +"""Radically simple xml parsing + +Example parse + +text in xml + +( "this", + {"type": "xml"}, + [ "text ", + ("b", None, ["in"], None), + " xml" + ] + None ) + +{ 0: "this" + "type": "xml" + 1: ["text ", + {0: "b", 1:["in"]}, + " xml"] +} + +Ie, xml tag translates to a tuple: + (name, dictofattributes, contentlist, miscellaneousinfo) + +where miscellaneousinfo can be anything, (but defaults to None) +(with the intention of adding, eg, line number information) + +special cases: name of "" means "top level, no containing tag". +Top level parse always looks like this + + ("", list, None, None) + + contained text of None means + +In order to support stuff like + + + +AT THE MOMENT & ETCETERA ARE IGNORED. THEY MUST BE PROCESSED +IN A POST-PROCESSING STEP. + +PROLOGUES ARE NOT UNDERSTOOD. OTHER STUFF IS PROBABLY MISSING. +""" + +RequirePyRXP = 0 # set this to 1 to disable the nonvalidating fallback parser. + +import string +try: + #raise ImportError, "dummy error" + simpleparse = 0 + import pyRXPU + def warnCB(s): + print s + pyRXP_parser = pyRXPU.Parser( + ErrorOnValidityErrors=1, + NoNoDTDWarning=1, + ExpandCharacterEntities=1, + ExpandGeneralEntities=1, + warnCB = warnCB, + srcName='string input', + ReturnUTF8 = 1, + ) + def parsexml(xmlText, oneOutermostTag=0,eoCB=None,entityReplacer=None): + pyRXP_parser.eoCB = eoCB + p = pyRXP_parser.parse(xmlText) + return oneOutermostTag and p or ('',None,[p],None) +except ImportError: + simpleparse = 1 + +NONAME = "" +NAMEKEY = 0 +CONTENTSKEY = 1 +CDATAMARKER = "" +replacelist = [("<", "<"), (">", ">"), ("&", "&")] # amp must be last +#replacelist = [] +def unEscapeContentList(contentList): + result = [] + from string import replace + for e in contentList: + if "&" in e: + for (old, new) in replacelist: + e = replace(e, old, new) + result.append(e) + return result + +def parsexmlSimple(xmltext, oneOutermostTag=0,eoCB=None,entityReplacer=unEscapeContentList): + """official interface: discard unused cursor info""" + if RequirePyRXP: + raise ImportError, "pyRXP not found, fallback parser disabled" + (result, cursor) = parsexml0(xmltext,entityReplacer=entityReplacer) + if oneOutermostTag: + return result[2][0] + else: + return result + +if simpleparse: + parsexml = parsexmlSimple + +def parseFile(filename): + raw = open(filename, 'r').read() + return parsexml(raw) + +verbose = 0 + +def skip_prologue(text, cursor): + """skip any prologue found after cursor, return index of rest of text""" + ### NOT AT ALL COMPLETE!!! definitely can be confused!!! + from string import find + prologue_elements = ("!DOCTYPE", "?xml", "!--") + done = None + while done is None: + #print "trying to skip:", repr(text[cursor:cursor+20]) + openbracket = find(text, "<", cursor) + if openbracket<0: break + past = openbracket+1 + found = None + for e in prologue_elements: + le = len(e) + if text[past:past+le]==e: + found = 1 + cursor = find(text, ">", past) + if cursor<0: + raise ValueError, "can't close prologue %s" % `e` + cursor = cursor+1 + if found is None: + done=1 + #print "done skipping" + return cursor + +def parsexml0(xmltext, startingat=0, toplevel=1, + # snarf in some globals + strip=string.strip, split=string.split, find=string.find, entityReplacer=unEscapeContentList, + #len=len, None=None + #LENCDATAMARKER=LENCDATAMARKER, CDATAMARKER=CDATAMARKER + ): + """simple recursive descent xml parser... + return (dictionary, endcharacter) + special case: comment returns (None, endcharacter)""" + #from string import strip, split, find + #print "parsexml0", `xmltext[startingat: startingat+10]` + # DEFAULTS + NameString = NONAME + ContentList = AttDict = ExtraStuff = None + if toplevel is not None: + #if verbose: print "at top level" + #if startingat!=0: + # raise ValueError, "have to start at 0 for top level!" + xmltext = strip(xmltext) + cursor = startingat + #look for interesting starting points + firstbracket = find(xmltext, "<", cursor) + afterbracket2char = xmltext[firstbracket+1:firstbracket+3] + #print "a", `afterbracket2char` + #firstampersand = find(xmltext, "&", cursor) + #if firstampersand>0 and firstampersand0: + #afterbracket2char = xmltext[firstbracket:firstbracket+2] + if toplevel is not None: + #print "toplevel with no outer tag" + NameString = name = NONAME + cursor = skip_prologue(xmltext, cursor) + #break + elif firstbracket<0: + raise ValueError, "non top level entry should be at start tag: %s" % repr(xmltext[:10]) + # special case: CDATA + elif afterbracket2char=="![" and xmltext[firstbracket:firstbracket+9]=="": + raise ValueError, "invalid comment: contains double dashes %s" % repr(xmltext[cursor:cursor+20]) + return (None, endcomment+1) # shortcut exit + else: + # get the rest of the tag + #if verbose: print "parsing start tag" + # make sure the tag isn't in doublequote pairs + closebracket = find(xmltext, ">", firstbracket) + noclose = closebracket<0 + startsearch = closebracket+1 + pastfirstbracket = firstbracket+1 + tagcontent = xmltext[pastfirstbracket:closebracket] + # shortcut, no equal means nothing but name in the tag content + if '=' not in tagcontent: + if tagcontent[-1]=="/": + # simple case + #print "simple case", tagcontent + tagcontent = tagcontent[:-1] + docontents = None + name = strip(tagcontent) + NameString = name + cursor = startsearch + else: + if '"' in tagcontent: + # check double quotes + stop = None + # not inside double quotes! (the split should have odd length) + if noclose or len(split(tagcontent+".", '"'))% 2: + stop=1 + while stop is None: + closebracket = find(xmltext, ">", startsearch) + startsearch = closebracket+1 + noclose = closebracket<0 + tagcontent = xmltext[pastfirstbracket:closebracket] + # not inside double quotes! (the split should have odd length) + if noclose or len(split(tagcontent+".", '"'))% 2: + stop=1 + if noclose: + raise ValueError, "unclosed start tag %s" % repr(xmltext[firstbracket:firstbracket+20]) + cursor = startsearch + #cursor = closebracket+1 + # handle simple tag /> syntax + if xmltext[closebracket-1]=="/": + #if verbose: print "it's a simple tag" + closebracket = closebracket-1 + tagcontent = tagcontent[:-1] + docontents = None + #tagcontent = xmltext[firstbracket+1:closebracket] + tagcontent = strip(tagcontent) + taglist = split(tagcontent, "=") + #if not taglist: + # raise ValueError, "tag with no name %s" % repr(xmltext[firstbracket:firstbracket+20]) + taglist0 = taglist[0] + taglist0list = split(taglist0) + #if len(taglist0list)>2: + # raise ValueError, "bad tag head %s" % repr(taglist0) + name = taglist0list[0] + #print "tag name is", name + NameString = name + # now parse the attributes + attributename = taglist0list[-1] + # put a fake att name at end of last taglist entry for consistent parsing + taglist[-1] = taglist[-1]+" f" + AttDict = D = {} + taglistindex = 1 + lasttaglistindex = len(taglist) + #for attentry in taglist[1:]: + while taglistindexlasttaglistindex: + raise ValueError, "unclosed value " + repr(attentry) + nextattentry = taglist[taglistindex] + taglistindex = taglistindex+1 + attentry = "%s=%s" % (attentry, nextattentry) + attentry = strip(attentry) # only needed for while loop... + attlist = split(attentry) + nextattname = attlist[-1] + attvalue = attentry[:-len(nextattname)] + attvalue = strip(attvalue) + try: + first = attvalue[0]; last=attvalue[-1] + except: + raise ValueError, "attvalue,attentry,attlist="+repr((attvalue, attentry,attlist)) + if first==last=='"' or first==last=="'": + attvalue = attvalue[1:-1] + #print attributename, "=", attvalue + D[attributename] = attvalue + attributename = nextattname + # pass over other tags and content looking for end tag + if docontents is not None: + #print "now looking for end tag" + ContentList = L + while docontents is not None: + nextopenbracket = find(xmltext, "<", cursor) + if nextopenbracket", nextopenbracket) + if nextclosebracket\n%s\n" % (name, attributes, textpprint, name) + # otherwise must be a simple tag + return "<%s %s/>" % (name, attributes) + +dump = 0 +def testparse(s): + from time import time + from pprint import pprint + now = time() + D = parsexmlSimple(s) + print "DONE", time()-now + if dump&4: + pprint(D) + #pprint(D) + if dump&1: + print "============== reformatting" + p = pprettyprint(D) + print p + +def test(): + testparse("""text <>in xml + + text in xml ]]> + just testing brackets feature + """) + +filenames = [ #"../../reportlab/demos/pythonpoint/pythonpoint.xml", + "samples/hamlet.xml"] + +#filenames = ["moa.xml"] + +dump=1 +if __name__=="__main__": + test() + from time import time + now = time() + for f in filenames: + t = open(f).read() + print "parsing", f + testparse(t) + print "elapsed", time()-now diff --git a/bin/reportlab/lib/sequencer.py b/bin/reportlab/lib/sequencer.py new file mode 100644 index 00000000000..c9d9274d2d7 --- /dev/null +++ b/bin/reportlab/lib/sequencer.py @@ -0,0 +1,284 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/sequencer.py +__version__=''' $Id: sequencer.py 2423 2004-08-24 11:36:22Z rgbecker $ ''' +"""This module defines a single public class, Sequencer, which aids in +numbering and formatting lists.""" +# +# roman numbers conversion thanks to +# +# fredrik lundh, november 1996 (based on a C hack from 1984) +# +# fredrik@pythonware.com +# http://www.pythonware.com + +_RN_TEMPLATES = [ 0, 01, 011, 0111, 012, 02, 021, 0211, 02111, 013 ] +_RN_LETTERS = "IVXLCDM" + +from string import lower + + +def _format_I(value): + if value < 0 or value > 3999: + raise ValueError, "illegal value" + str = "" + base = -1 + while value: + value, index = divmod(value, 10) + tmp = _RN_TEMPLATES[index] + while tmp: + tmp, index = divmod(tmp, 8) + str = _RN_LETTERS[index+base] + str + base = base + 2 + return str + +def _format_i(num): + return lower(_format_I(num)) + +def _format_123(num): + """The simplest formatter""" + return str(num) + +def _format_ABC(num): + """Uppercase. Wraps around at 26.""" + n = (num -1) % 26 + return chr(n+65) + +def _format_abc(num): + """Lowercase. Wraps around at 26.""" + n = (num -1) % 26 + return chr(n+97) + + +class _Counter: + """Private class used by Sequencer. Each counter + knows its format, and the IDs of anything it + resets, as well as its value. Starts at zero + and increments just before you get the new value, + so that it is still 'Chapter 5' and not 'Chapter 6' + when you print 'Figure 5.1'""" + + def __init__(self): + self._base = 0 + self._value = self._base + self._formatter = _format_123 + self._resets = [] + + def setFormatter(self, formatFunc): + self._formatter = formatFunc + + def reset(self, value=None): + if value: + self._value = value + else: + self._value = self._base + + def next(self): + self._value = self._value + 1 + v = self._value + for counter in self._resets: + counter.reset() + return v + + def _this(self): + return self._value + + def nextf(self): + """Returns next value formatted""" + return self._formatter(self.next()) + + def thisf(self): + return self._formatter(self._this()) + + def chain(self, otherCounter): + if not otherCounter in self._resets: + self._resets.append(otherCounter) + + +class Sequencer: + """Something to make it easy to number paragraphs, sections, + images and anything else. The features include registering + new string formats for sequences, and 'chains' whereby + some counters are reset when their parents. + It keeps track of a number of + 'counters', which are created on request: + Usage: + >>> seq = layout.Sequencer() + >>> seq.next('Bullets') + 1 + >>> seq.next('Bullets') + 2 + >>> seq.next('Bullets') + 3 + >>> seq.reset('Bullets') + >>> seq.next('Bullets') + 1 + >>> seq.next('Figures') + 1 + >>> + """ + + def __init__(self): + self._counters = {} #map key to current number + self._defaultCounter = None + + self._formatters = { + # the formats it knows initially + '1':_format_123, + 'A':_format_ABC, + 'a':_format_abc, + 'I':_format_I, + 'i':_format_i, + } + + def _getCounter(self, counter=None): + """Creates one if not present""" + try: + return self._counters[counter] + except KeyError: + cnt = _Counter() + self._counters[counter] = cnt + return cnt + + def _this(self, counter=None): + """Retrieves counter value but does not increment. For + new counters, sets base value to 1.""" + if not counter: + counter = self._defaultCounter + return self._getCounter(counter)._this() + + def next(self, counter=None): + """Retrieves the numeric value for the given counter, then + increments it by one. New counters start at one.""" + if not counter: + counter = self._defaultCounter + return self._getCounter(counter).next() + + def thisf(self, counter=None): + if not counter: + counter = self._defaultCounter + return self._getCounter(counter).thisf() + + def nextf(self, counter=None): + """Retrieves the numeric value for the given counter, then + increments it by one. New counters start at one.""" + if not counter: + counter = self._defaultCounter + return self._getCounter(counter).nextf() + + def setDefaultCounter(self, default=None): + """Changes the key used for the default""" + self._defaultCounter = default + + def registerFormat(self, format, func): + """Registers a new formatting function. The funtion + must take a number as argument and return a string; + fmt is a short menmonic string used to access it.""" + self._formatters[format] = func + + def setFormat(self, counter, format): + """Specifies that the given counter should use + the given format henceforth.""" + func = self._formatters[format] + self._getCounter(counter).setFormatter(func) + + def reset(self, counter=None, base=0): + if not counter: + counter = self._defaultCounter + self._getCounter(counter)._value = base + + def chain(self, parent, child): + p = self._getCounter(parent) + c = self._getCounter(child) + p.chain(c) + + def __getitem__(self, key): + """Allows compact notation to support the format function. + s['key'] gets current value, s['key+'] increments.""" + if key[-1:] == '+': + counter = key[:-1] + return self.nextf(counter) + else: + return self.thisf(key) + + def format(self, template): + """The crowning jewels - formats multi-level lists.""" + return template % self + + def dump(self): + """Write current state to stdout for diagnostics""" + counters = self._counters.items() + counters.sort() + print 'Sequencer dump:' + for (key, counter) in counters: + print ' %s: value = %d, base = %d, format example = %s' % ( + key, counter._this(), counter._base, counter.thisf()) + + +"""Your story builder needs to set this to""" +_sequencer = None + +def getSequencer(): + global _sequencer + if _sequencer is None: + _sequencer = Sequencer() + return _sequencer + +def setSequencer(seq): + global _sequencer + s = _sequencer + _sequencer = seq + return s + +def test(): + s = Sequencer() + print 'Counting using default sequence: %d %d %d' % (s.next(),s.next(), s.next()) + print 'Counting Figures: Figure %d, Figure %d, Figure %d' % ( + s.next('figure'), s.next('figure'), s.next('figure')) + print 'Back to default again: %d' % s.next() + s.setDefaultCounter('list1') + print 'Set default to list1: %d %d %d' % (s.next(),s.next(), s.next()) + s.setDefaultCounter() + print 'Set default to None again: %d %d %d' % (s.next(),s.next(), s.next()) + print + print 'Creating Appendix counter with format A, B, C...' + s.setFormat('Appendix', 'A') + print ' Appendix %s, Appendix %s, Appendix %s' % ( + s.nextf('Appendix'), s.nextf('Appendix'),s.nextf('Appendix')) + + def format_french(num): + return ('un','deux','trois','quatre','cinq')[(num-1)%5] + print + print 'Defining a custom format with french words:' + s.registerFormat('french', format_french) + s.setFormat('FrenchList', 'french') + print ' ', + for i in range(1,6): + print s.nextf('FrenchList'), + print + print 'Chaining H1 and H2 - H2 goes back to one when H1 increases' + s.chain('H1','H2') + print ' H1 = %d' % s.next('H1') + print ' H2 = %d' % s.next('H2') + print ' H2 = %d' % s.next('H2') + print ' H2 = %d' % s.next('H2') + print ' H1 = %d' % s.next('H1') + print ' H2 = %d' % s.next('H2') + print ' H2 = %d' % s.next('H2') + print ' H2 = %d' % s.next('H2') + print + print 'GetItem notation - append a plus to increment' + print ' seq["Appendix"] = %s' % s["Appendix"] + print ' seq["Appendix+"] = %s' % s["Appendix+"] + print ' seq["Appendix+"] = %s' % s["Appendix+"] + print ' seq["Appendix"] = %s' % s["Appendix"] + print + print 'Finally, string format notation for nested lists. Cool!' + print 'The expression ("Figure %(Chapter)s.%(Figure+)s" % seq) gives:' + print ' Figure %(Chapter)s.%(Figure+)s' % s + print ' Figure %(Chapter)s.%(Figure+)s' % s + print ' Figure %(Chapter)s.%(Figure+)s' % s + + +if __name__=='__main__': + test() diff --git a/bin/reportlab/lib/set_ops.py b/bin/reportlab/lib/set_ops.py new file mode 100644 index 00000000000..eb44fb2abbc --- /dev/null +++ b/bin/reportlab/lib/set_ops.py @@ -0,0 +1,38 @@ +#!/bin/env python +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/set_ops.py +import types +import string + +def __set_coerce(t, S): + if t is types.ListType: + return list(S) + elif t is types.TupleType: + return tuple(S) + elif t is types.StringType: + return string.join(S, '') + return S + +def unique(seq): + result = [] + for i in seq: + if i not in result: + result.append(i) + return __set_coerce(type(seq), result) + +def intersect(seq1, seq2): + result = [] + if type(seq1) != type(seq2) and type(seq2) == types.StringType: seq2 = list(seq2) + for i in seq1: + if i in seq2 and i not in result: result.append(i) + return __set_coerce(type(seq1), result) + +def union(seq1, seq2): + if type(seq1) == type(seq2): + return unique(seq1 + seq2) + if type(seq1) == types.ListType or type(seq2) == types.ListType: + return unique(list(seq1) + list(seq2)) + if type(seq1) == types.TupleType or type(seq2) == types.TupleType: + return unique(tuple(seq1) + tuple(seq2)) + return unique(list(seq1) + list(seq2)) diff --git a/bin/reportlab/lib/styles.py b/bin/reportlab/lib/styles.py new file mode 100644 index 00000000000..4da775eaaf5 --- /dev/null +++ b/bin/reportlab/lib/styles.py @@ -0,0 +1,257 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/styles.py +__version__=''' $Id: styles.py 2830 2006-04-05 15:18:32Z rgbecker $ ''' + +from reportlab.lib.colors import white, black +from reportlab.lib.enums import TA_LEFT, TA_CENTER + +########################################################### +# This class provides an 'instance inheritance' +# mechanism for its descendants, simpler than acquisition +# but not as far-reaching +########################################################### +class PropertySet: + defaults = {} + + def __init__(self, name, parent=None, **kw): + """When initialized, it copies the class defaults; + then takes a copy of the attributes of the parent + if any. All the work is done in init - styles + should cost little to use at runtime.""" + # step one - validate the hell out of it + assert not self.defaults.has_key('name'), "Class Defaults may not contain a 'name' attribute" + assert not self.defaults.has_key('parent'), "Class Defaults may not contain a 'parent' attribute" + if parent: + assert parent.__class__ == self.__class__, "Parent style must have same class as new style" + + #step two + self.name = name + self.parent = parent + self.__dict__.update(self.defaults) + + #step two - copy from parent if any. Try to be + # very strict that only keys in class defaults are + # allowed, so they cannot inherit + self.refresh() + + #step three - copy keywords if any + for (key, value) in kw.items(): + self.__dict__[key] = value + + + def __repr__(self): + return "<%s '%s'>" % (self.__class__.__name__, self.name) + + def refresh(self): + """re-fetches attributes from the parent on demand; + use if you have been hacking the styles. This is + used by __init__""" + if self.parent: + for (key, value) in self.parent.__dict__.items(): + if (key not in ['name','parent']): + self.__dict__[key] = value + + + def listAttrs(self, indent=''): + print indent + 'name =', self.name + print indent + 'parent =', self.parent + keylist = self.__dict__.keys() + keylist.sort() + keylist.remove('name') + keylist.remove('parent') + for key in keylist: + value = self.__dict__.get(key, None) + print indent + '%s = %s' % (key, value) + +class ParagraphStyle(PropertySet): + defaults = { + 'fontName':'Times-Roman', + 'fontSize':10, + 'leading':12, + 'leftIndent':0, + 'rightIndent':0, + 'firstLineIndent':0, + 'alignment':TA_LEFT, + 'spaceBefore':0, + 'spaceAfter':0, + 'bulletFontName':'Times-Roman', + 'bulletFontSize':10, + 'bulletIndent':0, + 'textColor': black, + 'backColor':None, + 'wordWrap':None, + } + +class LineStyle(PropertySet): + defaults = { + 'width':1, + 'color': black + } + def prepareCanvas(self, canvas): + """You can ask a LineStyle to set up the canvas for drawing + the lines.""" + canvas.setLineWidth(1) + #etc. etc. + +class StyleSheet1: + """This may or may not be used. The idea is to + 1. slightly simplify construction of stylesheets; + 2. enforce rules to validate styles when added + (e.g. we may choose to disallow having both + 'heading1' and 'Heading1' - actual rules are + open to discussion); + 3. allow aliases and alternate style lookup + mechanisms + 4. Have a place to hang style-manipulation + methods (save, load, maybe support a GUI + editor) + Access is via getitem, so they can be + compatible with plain old dictionaries. + """ + def __init__(self): + self.byName = {} + self.byAlias = {} + + + def __getitem__(self, key): + try: + return self.byAlias[key] + except KeyError: + try: + return self.byName[key] + except KeyError: + raise KeyError, "Style '%s' not found in stylesheet" % key + + def has_key(self, key): + if self.byAlias.has_key(key): + return 1 + elif self.byName.has_key(key): + return 1 + else: + return 0 + + def add(self, style, alias=None): + key = style.name + if self.byName.has_key(key): + raise KeyError, "Style '%s' already defined in stylesheet" % key + if self.byAlias.has_key(key): + raise KeyError, "Style name '%s' is already an alias in stylesheet" % key + + if alias: + if self.byName.has_key(alias): + raise KeyError, "Style '%s' already defined in stylesheet" % alias + if self.byAlias.has_key(alias): + raise KeyError, "Alias name '%s' is already an alias in stylesheet" % alias + #passed all tests? OK, add it + self.byName[key] = style + if alias: + self.byAlias[alias] = style + + def list(self): + styles = self.byName.items() + styles.sort() + alii = {} + for (alias, style) in self.byAlias.items(): + alii[style] = alias + for (name, style) in styles: + alias = alii.get(style, None) + print name, alias + style.listAttrs(' ') + print + + + + +def testStyles(): + pNormal = ParagraphStyle('Normal',None) + pNormal.fontName = 'Times-Roman' + pNormal.fontSize = 12 + pNormal.leading = 14.4 + + pNormal.listAttrs() + print + pPre = ParagraphStyle('Literal', pNormal) + pPre.fontName = 'Courier' + pPre.listAttrs() + return pNormal, pPre + +def getSampleStyleSheet(): + """Returns a stylesheet object""" + stylesheet = StyleSheet1() + + stylesheet.add(ParagraphStyle(name='Normal', + fontName='Times-Roman', + fontSize=10, + leading=12) + ) + + stylesheet.add(ParagraphStyle(name='BodyText', + parent=stylesheet['Normal'], + spaceBefore=6) + ) + stylesheet.add(ParagraphStyle(name='Italic', + parent=stylesheet['BodyText'], + fontName = 'Times-Italic') + ) + + stylesheet.add(ParagraphStyle(name='Heading1', + parent=stylesheet['Normal'], + fontName = 'Times-Bold', + fontSize=18, + leading=22, + spaceAfter=6), + alias='h1') + + stylesheet.add(ParagraphStyle(name='Title', + parent=stylesheet['Normal'], + fontName = 'Times-Bold', + fontSize=18, + leading=22, + alignment=TA_CENTER, + spaceAfter=6), + alias='title') + + stylesheet.add(ParagraphStyle(name='Heading2', + parent=stylesheet['Normal'], + fontName = 'Times-Bold', + fontSize=14, + leading=18, + spaceBefore=12, + spaceAfter=6), + alias='h2') + + stylesheet.add(ParagraphStyle(name='Heading3', + parent=stylesheet['Normal'], + fontName = 'Times-BoldItalic', + fontSize=12, + leading=14, + spaceBefore=12, + spaceAfter=6), + alias='h3') + + stylesheet.add(ParagraphStyle(name='Bullet', + parent=stylesheet['Normal'], + firstLineIndent=0, + spaceBefore=3), + alias='bu') + + stylesheet.add(ParagraphStyle(name='Definition', + parent=stylesheet['Normal'], + firstLineIndent=0, + leftIndent=36, + bulletIndent=0, + spaceBefore=6, + bulletFontName='Times-BoldItalic'), + alias='df') + + stylesheet.add(ParagraphStyle(name='Code', + parent=stylesheet['Normal'], + fontName='Courier', + fontSize=8, + leading=8.8, + firstLineIndent=0, + leftIndent=36)) + + + return stylesheet diff --git a/bin/reportlab/lib/textsplit.py b/bin/reportlab/lib/textsplit.py new file mode 100644 index 00000000000..5c6733c788a --- /dev/null +++ b/bin/reportlab/lib/textsplit.py @@ -0,0 +1,210 @@ +#Copyright ReportLab Europe Ltd. 2000-2006 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/textsplit.py + +"""Helpers for text wrapping, hyphenation, Asian text splitting and kinsoku shori. + +How to split a 'big word' depends on the language and the writing system. This module +works on a Unicode string. It ought to grow by allowing ore algoriths to be plugged +in based on possible knowledge of the language and desirable 'niceness' of the algorithm. + +""" + +__version__=''' $Id: textsplit.py 2833 2006-04-05 16:01:20Z rgbecker $ ''' + +from types import StringType, UnicodeType +import unicodedata +from reportlab.pdfbase.pdfmetrics import stringWidth +from reportlab.rl_config import _FUZZ + +CANNOT_START_LINE = [ + #strongly prohibited e.g. end brackets, stop, exclamation... + u'!\',.:;?!")]\u3001\u3002\u300d\u300f\u3011\u3015\uff3d\u3011\uff09', + #middle priority e.g. continuation small vowels - wrapped on two lines but one string... + u'\u3005\u2015\u3041\u3043\u3045\u3047\u3049\u3063\u3083\u3085\u3087\u308e\u30a1\u30a3' + u'\u30a5\u30a7\u30a9\u30c3\u30e3\u30e5\u30e7\u30ee\u30fc\u30f5\u30f6', + #weakly prohibited - continuations, celsius symbol etc. + u'\u309b\u309c\u30fb\u30fd\u30fe\u309d\u309e\u2015\u2010\xb0\u2032\u2033\u2103\uffe0\uff05\u2030' + ] + +ALL_CANNOT_START = u''.join(CANNOT_START_LINE) +CANNOT_END_LINE = [ + #strongly prohibited + u'\u2018\u201c\uff08[{\uff08\u3014\uff3b\uff5b\u3008\u300a\u300c\u300e\u3010', + #weaker - currency symbols, hash, postcode - prefixes + u'$\u00a3@#\uffe5\uff04\uffe1\uff20\u3012\u00a7' + ] +ALL_CANNOT_END = u''.join(CANNOT_END_LINE) +def getCharWidths(word, fontName, fontSize): + """Returns a list of glyph widths. Should be easy to optimize in _rl_accel + + >>> getCharWidths('Hello', 'Courier', 10) + [6.0, 6.0, 6.0, 6.0, 6.0] + >>> from reportlab.pdfbase.cidfonts import UnicodeCIDFont + >>> from reportlab.pdfbase.pdfmetrics import registerFont + >>> registerFont(UnicodeCIDFont('HeiseiMin-W3')) + >>> getCharWidths(u'\u6771\u4EAC', 'HeiseiMin-W3', 10) #most kanji are 100 ems + [10.0, 10.0] + """ + #character-level function call; the performance is going to SUCK + + return [stringWidth(uChar, fontName, fontSize) for uChar in word] + +def wordSplit(word, availWidth, fontName, fontSize, encoding='utf8'): + """Attempts to break a word which lacks spaces into two parts, the first of which + fits in the remaining space. It is allowed to add hyphens or whatever it wishes. + + This is intended as a wrapper for some language- and user-choice-specific splitting + algorithms. It should only be called after line breaking on spaces, which covers western + languages and is highly optimised already. It works on the 'last unsplit word'. + + Presumably with further study one could write a Unicode splitting algorithm for text + fragments whick was much faster. + + Courier characters should be 6 points wide. + >>> wordSplit('HelloWorld', 30, 'Courier', 10) + [[0.0, 'Hello'], [0.0, 'World']] + >>> wordSplit('HelloWorld', 31, 'Courier', 10) + [[1.0, 'Hello'], [1.0, 'World']] + """ + if type(word) is not UnicodeType: + uword = word.decode(encoding) + else: + uword = word + + charWidths = getCharWidths(uword, fontName, fontSize) + lines = dumbSplit(uword, charWidths, availWidth) + + if type(word) is not UnicodeType: + lines2 = [] + #convert back + for (extraSpace, text) in lines: + lines2.append([extraSpace, text.encode(encoding)]) + lines = lines2 + + return lines + +def dumbSplit(word, widths, availWidth): + """This function attempts to fit as many characters as possible into the available + space, cutting "like a knife" between characters. This would do for Chinese. + It returns a list of (text, extraSpace) items where text is a Unicode string, + and extraSpace is the points of unused space available on the line. This is a + structure which is fairly easy to display, and supports 'backtracking' approaches + after the fact. + + Test cases assume each character is ten points wide... + + >>> dumbSplit(u'Hello', [10]*5, 60) + [[10.0, u'Hello']] + >>> dumbSplit(u'Hello', [10]*5, 50) + [[0.0, u'Hello']] + >>> dumbSplit(u'Hello', [10]*5, 40) + [[0.0, u'Hell'], [30, u'o']] + """ + + _more = """ + #>>> dumbSplit(u'Hello', [10]*5, 4) # less than one character + #(u'', u'Hello') + # this says 'Nihongo wa muzukashii desu ne!' (Japanese is difficult isn't it?) in 12 characters + >>> jtext = u'\u65e5\u672c\u8a9e\u306f\u96e3\u3057\u3044\u3067\u3059\u306d\uff01' + >>> dumbSplit(jtext, [10]*11, 30) # + (u'\u65e5\u672c\u8a9e', u'\u306f\u96e3\u3057\u3044\u3067\u3059\u306d\uff01') + """ + assert type(word) is UnicodeType + lines = [] + widthUsed = 0.0 + lineStartPos = 0 + for (i, w) in enumerate(widths): + widthUsed += w + if widthUsed > availWidth + _FUZZ: + #used more than can fit... + #ping out with previous cut, then set up next line with one character + + extraSpace = availWidth - widthUsed + w + #print 'ending a line; used %d, available %d' % (widthUsed, availWidth) + selected = word[lineStartPos:i] + + + #This is the most important of the Japanese typography rules. + #if next character cannot start a line, wrap it up to this line so it hangs + #in the right margin. We won't do two or more though - that's unlikely and + #would result in growing ugliness. + nextChar = word[i] + if nextChar in ALL_CANNOT_START: + #it's punctuation or a closing bracket of some kind. 'wrap up' + #so it stays on the line above, slightly exceeding our target width. + #print 'wrapping up', repr(nextChar) + selected += nextChar + extraSpace -= w + i += 1 + + + + lines.append([extraSpace, selected]) + lineStartPos = i + widthUsed = w + i -= 1 + #any characters left? + if widthUsed > 0: + extraSpace = availWidth - widthUsed + lines.append([extraSpace, word[lineStartPos:]]) + + return lines + +def kinsokuShoriSplit(word, widths, availWidth): + #NOT USED OR FINISHED YET! + """Split according to Japanese rules according to CJKV (Lunde). + + Essentially look for "nice splits" so that we don't end a line + with an open bracket, or start one with a full stop, or stuff like + that. There is no attempt to try to split compound words into + constituent kanji. It currently uses wrap-down: packs as much + on a line as possible, then backtracks if needed + + + This returns a number of words each of which should just about fit + on a line. If you give it a whole paragraph at once, it will + do all the splits. + + It's possible we might slightly step over the width limit + if we do hanging punctuation marks in future (e.g. dangle a Japanese + full stop in the right margin rather than using a whole character + box. + + """ + lines = [] + assert len(word) == len(widths) + curWidth = 0.0 + curLine = [] + i = 0 #character index - we backtrack at times so cannot use for loop + while 1: + ch = word[i] + w = widths[i] + if curWidth + w < availWidth: + curLine.append(ch) + curWidth += w + else: + #end of line. check legality + if ch in CANNOT_END_LINE[0]: + pass + #to be completed + + +# This recipe refers: +# +# http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/148061 +import re +rx=re.compile(u"([\u2e80-\uffff])", re.UNICODE) +def cjkwrap(text, width, encoding="utf8"): + return reduce(lambda line, word, width=width: '%s%s%s' % + (line, + [' ','\n', ''][(len(line)-line.rfind('\n')-1 + + len(word.split('\n',1)[0] ) >= width) or + line[-1:] == '\0' and 2], + word), + rx.sub(r'\1\0 ', unicode(text,encoding)).split(' ') + ).replace('\0', '').encode(encoding) + +if __name__=='__main__': + import doctest, textsplit + doctest.testmod(textsplit) diff --git a/bin/reportlab/lib/tocindex.py b/bin/reportlab/lib/tocindex.py new file mode 100644 index 00000000000..d94189fa630 --- /dev/null +++ b/bin/reportlab/lib/tocindex.py @@ -0,0 +1,294 @@ +# Tables of Contents and Indices +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/tocindex.py +__version__=''' $Id: tocindex.py 2385 2004-06-17 15:26:05Z rgbecker $ ''' +__doc__='' +""" +This module will contain standard Table of Contents and Index objects. +under development, and pending certain hooks adding in DocTemplate +As of today, it onyl handles the formatting aspect of TOCs +""" + +import string + +from reportlab.platypus import Flowable, BaseDocTemplate, Paragraph, \ + PageBreak, Frame, PageTemplate, NextPageTemplate +from reportlab.platypus.doctemplate import IndexingFlowable +from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet +from reportlab.platypus import tables +from reportlab.lib import enums +from reportlab.lib import colors +from reportlab.lib.units import inch, cm +from reportlab.rl_config import defaultPageSize + + ############################################################## + # + # we first define a paragraph style for each level of the + # table, and one for the table as whole; you can supply your + # own. + # + ############################################################## + + +levelZeroParaStyle = ParagraphStyle(name='LevelZero', + fontName='Times-Roman', + fontSize=10, + leading=12) +levelOneParaStyle = ParagraphStyle(name='LevelOne', + parent = levelZeroParaStyle, + firstLineIndent = 0, + leftIndent = 18) +levelTwoParaStyle = ParagraphStyle(name='LevelTwo', + parent = levelOneParaStyle, + firstLineIndent = 0, + leftIndent = 36) +levelThreeParaStyle = ParagraphStyle(name='LevelThree', + parent = levelTwoParaStyle, + firstLineIndent = 0, + leftIndent = 54) + +defaultTableStyle = tables.TableStyle([ + ('VALIGN',(0,0),(-1,-1),'TOP'), + ('INNERGRID', (0,0), (-1,-1), 0.25, colors.black), + ('BOX', (0,0), (-1,-1), 0.25, colors.black), + ]) + + + + + + +class TableOfContents0(IndexingFlowable): + """This creates a formatted table of contents. It presumes + a correct block of data is passed in. The data block contains + a list of (level, text, pageNumber) triplets. You can supply + a paragraph style for each level (starting at zero).""" + def __init__(self): + self.entries = [] + self.rightColumnWidth = 72 + self.levelStyles = [levelZeroParaStyle, + levelOneParaStyle, + levelTwoParaStyle, + levelThreeParaStyle] + self.tableStyle = defaultTableStyle + self._table = None + self._entries = [] + self._lastEntries = [] + + def beforeBuild(self): + # keep track of the last run + self._lastEntries = self._entries[:] + self.clearEntries() + + def isIndexing(self): + return 1 + + def isSatisfied(self): + return (self._entries == self._lastEntries) + + def notify(self, kind, stuff): + """DocTemplate framework can call this with all kinds + of events; we say we are interested in 'TOCEntry' + events.""" + if kind == 'TOCEntry': + (level, text, pageNum) = stuff + self.addEntry(level, text, pageNum) + #print 'TOC notified of ', stuff +## elif kind == 'debug': +## # hack to watch its state +## import pprint +## print 'Last Entries first 5:' +## for (level, text, pageNum) in self._lastEntries[0:5]: +## print (level, text[0:30], pageNum), +## print +## print 'Current Entries first 5:' +## for (level, text, pageNum) in self._lastEntries[0:5]: +## print (level, text[0:30], pageNum), + + + def clearEntries(self): + self._entries = [] + + def addEntry(self, level, text, pageNum): + """Adds one entry; allows incremental buildup by a doctemplate. + Requires that enough styles are defined.""" + assert type(level) == type(1), "Level must be an integer" + assert level < len(self.levelStyles), \ + "Table of contents must have a style defined " \ + "for paragraph level %d before you add an entry" % level + self._entries.append((level, text, pageNum)) + + def addEntries(self, listOfEntries): + """Bulk creation. If you knew the titles but + not the page numbers, you could supply them to + get sensible output on the first run.""" + for (level, text, pageNum) in listOfEntries: + self.addEntry(level, text, pageNum) + + def wrap(self, availWidth, availHeight): + """All table properties should be known by now.""" + widths = (availWidth - self.rightColumnWidth, + self.rightColumnWidth) + + # makes an internal table which does all the work. + # we draw the LAST RUN's entries! If there are + # none, we make some dummy data to keep the table + # from complaining + if len(self._lastEntries) == 0: + _tempEntries = [(0,'Placeholder for table of contents',0)] + else: + _tempEntries = self._lastEntries + tableData = [] + for (level, text, pageNum) in _tempEntries: + leftColStyle = self.levelStyles[level] + #right col style is right aligned + rightColStyle = ParagraphStyle(name='leftColLevel%d' % level, + parent=leftColStyle, + leftIndent=0, + alignment=enums.TA_RIGHT) + leftPara = Paragraph(text, leftColStyle) + rightPara = Paragraph(str(pageNum), rightColStyle) + tableData.append([leftPara, rightPara]) + self._table = tables.Table(tableData, colWidths=widths, + style=self.tableStyle) + self.width, self.height = self._table.wrap(availWidth, availHeight) + return (self.width, self.height) + + def split(self, availWidth, availHeight): + """At this stage we do not care about splitting the entries, + we wil just return a list of platypus tables. Presumably the + calling app has a pointer to the original TableOfContents object; + Platypus just sees tables.""" + return self._table.split(availWidth, availHeight) + + def drawOn(self, canvas, x, y, _sW=0): + """Don't do this at home! The standard calls for implementing + draw(); we are hooking this in order to delegate ALL the drawing + work to the embedded table object""" + self._table.drawOn(canvas, x, y, _sW) + + + + ################################################################################# + # + # everything from here down is concerned with creating a good example document + # i.e. test code as well as tutorial +PAGE_HEIGHT = defaultPageSize[1] +def getSampleTOCData(depth=3): + """Returns a longish block of page numbers and headings over 3 levels""" + from random import randint + pgNum = 2 + data = [] + for chapter in range(1,8): + data.append((0, """Chapter %d with a really long name which will hopefully + wrap onto a second line, fnding out if the right things happen with + full paragraphs n the table of contents""" % chapter, pgNum)) + pgNum = pgNum + randint(0,2) + if depth > 1: + for section in range(1,5): + data.append((1, 'Chapter %d Section %d' % (chapter, section), pgNum)) + pgNum = pgNum + randint(0,2) + if depth > 2: + for subSection in range(1,6): + data.append(2, 'Chapter %d Section %d Subsection %d' % + (chapter, section, subSection), + pgNum) + pgNum = pgNum + randint(0,1) + from pprint import pprint as pp + pp(data) + return data + + +def getSampleStory(depth=3): + """Makes a story with lots of paragraphs. Uses the random + TOC data and makes paragraphs to correspond to each.""" + from reportlab.platypus.doctemplate import randomText + from random import randint + + styles = getSampleStyleSheet() + TOCData = getSampleTOCData(depth) + + story = [Paragraph("This is a demo of the table of contents object", + styles['Heading1'])] + + toc = TableOfContents0() # empty on first pass + #toc.addEntries(TOCData) # init with random page numbers + story.append(toc) + + # the next full page should switch to internal page style + story.append(NextPageTemplate("Body")) + + # now make some paragraphs to correspond to it + for (level, text, pageNum) in TOCData: + if level == 0: + #page break before chapter + story.append(PageBreak()) + + headingStyle = (styles['Heading1'], styles['Heading2'], styles['Heading3'])[level] + headingPara = Paragraph(text, headingStyle) + story.append(headingPara) + # now make some body text + for i in range(randint(1,6)): + bodyPara = Paragraph(randomText(), + styles['Normal']) + story.append(bodyPara) + + return story + +class MyDocTemplate(BaseDocTemplate): + """Example of how to do the indexing. Need the onDraw hook + to find out which things are drawn on which pages""" + def afterInit(self): + """Set up the page templates""" + frameT = Frame(self.leftMargin, self.bottomMargin, self.width, self.height, id='normal') + self.addPageTemplates([PageTemplate(id='Front',frames=frameT), + PageTemplate(id='Body',frames=frameT) + ]) + # just need a unique key generator for outline entries; + # easiest is to count all flowables in afterFlowable + # and set up a counter variable here + self._uniqueKey = 0 + + + def afterFlowable(self, flowable): + """Our rule for the table of contents is simply to take + the text of H1, H2 and H3 elements. We broadcast a + notification to the DocTemplate, which should inform + the TOC and let it pull them out. Also build an outline""" + self._uniqueKey = self._uniqueKey + 1 + + if hasattr(flowable, 'style'): + if flowable.style.name == 'Heading1': + self.notify('TOCEntry', (0, flowable.getPlainText(), self.page)) + self.canv.bookmarkPage(str(self._uniqueKey)) + self.canv.addOutlineEntry(flowable.getPlainText()[0:10], str(self._uniqueKey), 0) + + elif flowable.style.name == 'Heading2': + self.notify('TOCEntry', (1, flowable.getPlainText(), self.page)) + self.canv.bookmarkPage(str(self._uniqueKey)) + self.canv.addOutlineEntry(flowable.getPlainText(), str(self._uniqueKey), 1) + + elif flowable.style.name == 'Heading3': + self.notify('TOCEntry', (2, flowable.getPlainText(), self.page)) + self.canv.bookmarkPage(str(self._uniqueKey)) + self.canv.addOutlineEntry(flowable.getPlainText(), str(self._uniqueKey), 2) + + def beforePage(self): + """decorate the page""" + self.canv.saveState() + self.canv.setStrokeColor(colors.red) + self.canv.setLineWidth(5) + self.canv.line(66,72,66,PAGE_HEIGHT-72) + self.canv.setFont('Times-Roman',12) + self.canv.drawString(4 * inch, 0.75 * inch, "Page %d" % doc.page) + self.canv.restoreState() + +if __name__=='__main__': + from reportlab.platypus import SimpleDocTemplate + doc = MyDocTemplate('tocindex.pdf') + + #change this to depth=3 for a BIG document + story = getSampleStory(depth=2) + + doc.multiBuild(story, 'tocindex.pdf') \ No newline at end of file diff --git a/bin/reportlab/lib/units.py b/bin/reportlab/lib/units.py new file mode 100644 index 00000000000..79067d8462f --- /dev/null +++ b/bin/reportlab/lib/units.py @@ -0,0 +1,23 @@ +#!/bin/env python +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/units.py +__version__=''' $Id: units.py 2524 2005-02-17 08:43:01Z rgbecker $ ''' + +inch = 72.0 +cm = inch / 2.54 +mm = cm * 0.1 +pica = 12.0 + +def toLength(s): + '''convert a string to a length''' + try: + if s[-2:]=='cm': return float(s[:-2])*cm + if s[-2:]=='in': return float(s[:-2])*inch + if s[-2:]=='pt': return float(s[:-2]) + if s[-1:]=='i': return float(s[:-1])*inch + if s[-2:]=='mm': return float(s[:-2])*mm + if s[-4:]=='pica': return float(s[:-4])*pica + return float(s) + except: + raise ValueError, "Can't convert '%s' to length" % s diff --git a/bin/reportlab/lib/utils.py b/bin/reportlab/lib/utils.py new file mode 100644 index 00000000000..c61dc8710a6 --- /dev/null +++ b/bin/reportlab/lib/utils.py @@ -0,0 +1,827 @@ +#Copyright ReportLab Europe Ltd. 2000-2006 +#see license.txt for license details +# $URI:$ +__version__=''' $Id: utils.py 2892 2006-05-19 14:16:02Z rgbecker $ ''' + +import string, os, sys, imp +from reportlab.lib.logger import warnOnce +from types import * +from rltempfile import get_rl_tempfile, get_rl_tempdir, _rl_getuid +SeqTypes = (ListType,TupleType) +if sys.hexversion<0x2020000: + def isSeqType(v): + return type(v) in SeqTypes +else: + def isSeqType(v): + return isinstance(v,(tuple,list)) + +if sys.hexversion<0x2030000: + True = 1 + False = 0 + +def _findFiles(dirList,ext='.ttf'): + from os.path import isfile, isdir, join as path_join + from os import listdir + ext = ext.lower() + R = [] + A = R.append + for D in dirList: + if not isdir(D): continue + for fn in listdir(D): + fn = path_join(D,fn) + if isfile(fn) and (not ext or fn.lower().endswith(ext)): A(fn) + return R + +try: + _UserDict = dict +except: + from UserDict import UserDict as _UserDict + +class CIDict(_UserDict): + def __init__(self,*a,**kw): + map(self.update, a) + self.update(kw) + + def update(self,D): + for k,v in D.items(): self[k] = v + + def __setitem__(self,k,v): + try: + k = k.lower() + except: + pass + _UserDict.__setitem__(self,k,v) + + def __getitem__(self,k): + try: + k = k.lower() + except: + pass + return _UserDict.__getitem__(self,k) + + def __delitem__(self,k): + try: + k = k.lower() + except: + pass + return _UserDict.__delitem__(self,k) + + def get(self,k,dv=None): + try: + return self[k] + except KeyError: + return dv + + def has_key(self,k): + try: + self[k] + return True + except: + return False + + def pop(self,k,*a): + try: + k = k.lower() + except: + pass + return _UserDict.pop(*((self,k)+a)) + + def setdefault(self,k,*a): + try: + k = k.lower() + except: + pass + return _UserDict.setdefault(*((self,k)+a)) + +if os.name == 'mac': + #with the Mac, we need to tag the file in a special + #way so the system knows it is a PDF file. + #This supplied by Joe Strout + import macfs, macostools + _KNOWN_MAC_EXT = { + 'BMP' : ('ogle','BMP '), + 'EPS' : ('ogle','EPSF'), + 'EPSF': ('ogle','EPSF'), + 'GIF' : ('ogle','GIFf'), + 'JPG' : ('ogle','JPEG'), + 'JPEG': ('ogle','JPEG'), + 'PCT' : ('ttxt','PICT'), + 'PICT': ('ttxt','PICT'), + 'PNG' : ('ogle','PNGf'), + 'PPM' : ('ogle','.PPM'), + 'TIF' : ('ogle','TIFF'), + 'TIFF': ('ogle','TIFF'), + 'PDF' : ('CARO','PDF '), + 'HTML': ('MSIE','TEXT'), + } + def markfilename(filename,creatorcode=None,filetype=None,ext='PDF'): + try: + if creatorcode is None or filetype is None and ext is not None: + try: + creatorcode, filetype = _KNOWN_MAC_EXT[string.upper(ext)] + except: + return + macfs.FSSpec(filename).SetCreatorType(creatorcode,filetype) + macostools.touched(filename) + except: + pass +else: + def markfilename(filename,creatorcode=None,filetype=None): + pass + +import reportlab +__RL_DIR=os.path.dirname(reportlab.__file__) #possibly relative +_RL_DIR=os.path.isabs(__RL_DIR) and __RL_DIR or os.path.abspath(__RL_DIR) +del reportlab + +#Attempt to detect if this copy of reportlab is running in a +#file system (as opposed to mostly running in a zip or McMillan +#archive or Jar file). This is used by test cases, so that +#we can write test cases that don't get activated in a compiled +try: + __file__ +except: + __file__ = sys.argv[0] +import glob, fnmatch +try: + _isFSD = not __loader__ + _archive = os.path.normcase(os.path.normpath(__loader__.archive)) + _archivepfx = _archive + os.sep + _archivedir = os.path.dirname(_archive) + _archivedirpfx = _archivedir + os.sep + _archivepfxlen = len(_archivepfx) + _archivedirpfxlen = len(_archivedirpfx) + def __startswith_rl(fn, + _archivepfx=_archivepfx, + _archivedirpfx=_archivedirpfx, + _archive=_archive, + _archivedir=_archivedir, + os_path_normpath=os.path.normpath, + os_path_normcase=os.path.normcase, + os_getcwd=os.getcwd, + os_sep=os.sep, + os_sep_len = len(os.sep)): + '''if the name starts with a known prefix strip it off''' + fn = os_path_normpath(fn.replace('/',os_sep)) + nfn = os_path_normcase(fn) + if nfn in (_archivedir,_archive): return 1,'' + if nfn.startswith(_archivepfx): return 1,fn[_archivepfxlen:] + if nfn.startswith(_archivedirpfx): return 1,fn[_archivedirpfxlen:] + cwd = os_path_normcase(os_getcwd()) + n = len(cwd) + if nfn.startswith(cwd): + if fn[n:].startswith(os_sep): return 1, fn[n+os_sep_len:] + if n==len(fn): return 1,'' + return not os.path.isabs(fn),fn + + def _startswith_rl(fn): + return __startswith_rl(fn)[1] + + def rl_glob(pattern,glob=glob.glob,fnmatch=fnmatch.fnmatch, _RL_DIR=_RL_DIR,pjoin=os.path.join): + c, pfn = __startswith_rl(pattern) + r = glob(pfn) + if c or r==[]: + r += map(lambda x,D=_archivepfx,pjoin=pjoin: pjoin(_archivepfx,x),filter(lambda x,pfn=pfn,fnmatch=fnmatch: fnmatch(x,pfn),__loader__._files.keys())) + return r +except: + _isFSD = os.path.isfile(__file__) #slight risk of wrong path + __loader__ = None + def _startswith_rl(fn): + return fn + def rl_glob(pattern,glob=glob.glob): + return glob(pattern) +del glob, fnmatch +_isFSSD = _isFSD and os.path.isfile(os.path.splitext(__file__)[0] +'.py') + +def isFileSystemDistro(): + '''return truth if a file system distribution''' + return _isFSD + +def isCompactDistro(): + '''return truth if not a file system distribution''' + return not _isFSD + +def isSourceDistro(): + '''return truth if a source file system distribution''' + return _isFSSD + +try: + #raise ImportError + ### NOTE! FP_STR SHOULD PROBABLY ALWAYS DO A PYTHON STR() CONVERSION ON ARGS + ### IN CASE THEY ARE "LAZY OBJECTS". ACCELLERATOR DOESN'T DO THIS (YET) + try: + from _rl_accel import fp_str # in case of builtin version + except ImportError: + from reportlab.lib._rl_accel import fp_str # specific +except ImportError: + from math import log + _log_10 = lambda x,log=log,_log_e_10=log(10.0): log(x)/_log_e_10 + _fp_fmts = "%.0f", "%.1f", "%.2f", "%.3f", "%.4f", "%.5f", "%.6f" + import re + _tz_re = re.compile('0+$') + del re + def fp_str(*a): + if len(a)==1 and isSeqType(a[0]): a = a[0] + s = [] + A = s.append + for i in a: + sa =abs(i) + if sa<=1e-7: A('0') + else: + l = sa<=1 and 6 or min(max(0,(6-int(_log_10(sa)))),6) + n = _fp_fmts[l]%i + if l: + n = _tz_re.sub('',n) + try: + if n[-1]=='.': n = n[:-1] + except: + print i, n + raise + A((n[0]!='0' or len(n)==1) and n or n[1:]) + return string.join(s) + +#hack test for comma users +if ',' in fp_str(0.25): + _FP_STR = fp_str + def fp_str(*a): + return string.replace(apply(_FP_STR,a),',','.') + +def recursiveImport(modulename, baseDir=None, noCWD=0, debug=0): + """Dynamically imports possible packagized module, or raises ImportError""" + normalize = lambda x: os.path.normcase(os.path.abspath(os.path.normpath(x))) + path = map(normalize,sys.path) + if baseDir: + if not isSeqType(baseDir): + tp = [baseDir] + else: + tp = filter(None,list(baseDir)) + for p in tp: + p = normalize(p) + if p not in path: path.insert(0,p) + + if noCWD: + for p in ('','.',normalize('.')): + while p in path: + if debug: print 'removed "%s" from path' % p + path.remove(p) + elif '.' not in path: + path.insert(0,'.') + + if debug: + import pprint + pp = pprint.pprint + print 'path=', + pp(path) + + #make import errors a bit more informative + opath = sys.path + try: + sys.path = path + exec 'import %s\nm = %s\n' % (modulename,modulename) in locals() + sys.path = opath + return m + except ImportError: + sys.path = opath + msg = "recursiveimport(%s,baseDir=%s) failed" % (modulename,baseDir) + if baseDir: + msg = msg + " under paths '%s'" % `path` + raise ImportError, msg + +def recursiveGetAttr(obj, name): + "Can call down into e.g. object1.object2[4].attr" + return eval(name, obj.__dict__) + +def recursiveSetAttr(obj, name, value): + "Can call down into e.g. object1.object2[4].attr = value" + #get the thing above last. + tokens = string.split(name, '.') + if len(tokens) == 1: + setattr(obj, name, value) + else: + most = string.join(tokens[:-1], '.') + last = tokens[-1] + parent = recursiveGetAttr(obj, most) + setattr(parent, last, value) + +def import_zlib(): + try: + import zlib + except ImportError: + zlib = None + from reportlab.rl_config import ZLIB_WARNINGS + if ZLIB_WARNINGS: warnOnce('zlib not available') + return zlib + + +# Image Capability Detection. Set a flag haveImages +# to tell us if either PIL or Java imaging libraries present. +# define PIL_Image as either None, or an alias for the PIL.Image +# module, as there are 2 ways to import it + +if sys.platform[0:4] == 'java': + try: + import javax.imageio + import java.awt.image + haveImages = 1 + except: + haveImages = 0 +else: + try: + from PIL import Image + except ImportError: + try: + import Image + except ImportError: + Image = None + haveImages = Image is not None + if haveImages: del Image + + +__StringIO=None +def getStringIO(buf=None): + '''unified StringIO instance interface''' + global __StringIO + if not __StringIO: + try: + from cStringIO import StringIO + except ImportError: + from StringIO import StringIO + __StringIO = StringIO + return buf is not None and __StringIO(buf) or __StringIO() + +class ArgvDictValue: + '''A type to allow clients of getArgvDict to specify a conversion function''' + def __init__(self,value,func): + self.value = value + self.func = func + +def getArgvDict(**kw): + ''' Builds a dictionary from its keyword arguments with overrides from sys.argv. + Attempts to be smart about conversions, but the value can be an instance + of ArgDictValue to allow specifying a conversion function. + ''' + def handleValue(v,av,func): + if func: + v = func(av) + else: + t = type(v) + if t is StringType: + v = av + elif t is FloatType: + v = float(av) + elif t is IntType: + v = int(av) + elif t is ListType: + v = list(eval(av)) + elif t is TupleType: + v = tuple(eval(av)) + else: + raise TypeError, "Can't convert string '%s' to %s" % (av,str(t)) + return v + + A = sys.argv[1:] + R = {} + for k, v in kw.items(): + if isinstance(v,ArgvDictValue): + v, func = v.value, v.func + else: + func = None + handled = 0 + ke = k+'=' + for a in A: + if string.find(a,ke)==0: + av = a[len(ke):] + A.remove(a) + R[k] = handleValue(v,av,func) + handled = 1 + break + + if not handled: R[k] = handleValue(v,v,func) + + return R + +def getHyphenater(hDict=None): + try: + from reportlab.lib.pyHnj import Hyphen + if hDict is None: hDict=os.path.join(os.path.dirname(__file__),'hyphen.mashed') + return Hyphen(hDict) + except ImportError, errMsg: + if str(errMsg)!='No module named pyHnj': raise + return None + +def _className(self): + '''Return a shortened class name''' + try: + name = self.__class__.__name__ + i=string.rfind(name,'.') + if i>=0: return name[i+1:] + return name + except AttributeError: + return str(self) + +def open_for_read_by_name(name,mode='b'): + if 'r' not in mode: mode = 'r'+mode + try: + return open(name,mode) + except IOError: + if _isFSD or __loader__ is None: raise + #we have a __loader__, perhaps the filename starts with + #the dirname(reportlab.__file__) or is relative + name = _startswith_rl(name) + s = __loader__.get_data(name) + if 'b' not in mode and os.linesep!='\n': s = s.replace(os.linesep,'\n') + return getStringIO(s) + +import urllib +def open_for_read(name,mode='b', urlopen=urllib.urlopen): + '''attempt to open a file or URL for reading''' + if hasattr(name,'read'): return name + try: + return open_for_read_by_name(name,mode) + except: + try: + return getStringIO(urlopen(name).read()) + except: + raise IOError('Cannot open resource "%s"' % name) +del urllib + +def open_and_read(name,mode='b'): + return open_for_read(name,mode).read() + +def open_and_readlines(name,mode='t'): + return open_and_read(name,mode).split('\n') + +def rl_isfile(fn,os_path_isfile=os.path.isfile): + if hasattr(fn,'read'): return True + if os_path_isfile(fn): return True + if _isFSD or __loader__ is None: return False + fn = _startswith_rl(fn) + return fn in __loader__._files.keys() + +def rl_isdir(pn,os_path_isdir=os.path.isdir,os_path_normpath=os.path.normpath): + if os_path_isdir(pn): return True + if _isFSD or __loader__ is None: return False + pn = _startswith_rl(os_path_normpath(pn)) + if not pn.endswith(os.sep): pn += os.sep + return len(filter(lambda x,pn=pn: x.startswith(pn),__loader__._files.keys()))>0 + +def rl_get_module(name,dir): + if sys.modules.has_key(name): + om = sys.modules[name] + del sys.modules[name] + else: + om = None + try: + f = None + try: + f, p, desc= imp.find_module(name,[dir]) + return imp.load_module(name,f,p,desc) + except: + if isCompactDistro(): + #attempt a load from inside the zip archive + import zipimport + dir = _startswith_rl(dir) + dir = (dir=='.' or not dir) and _archive or os.path.join(_archive,dir.replace('/',os.sep)) + zi = zipimport.zipimporter(dir) + return zi.load_module(name) + raise ImportError('%s[%s]' % (name,dir)) + finally: + if om: sys.modules[name] = om + del om + if f: f.close() + +def _isPILImage(im): + try: + from PIL.Image import Image + return isinstance(im,Image) + except ImportError: + return 0 + +class ImageReader: + "Wraps up either PIL or Java to get data from bitmaps" + def __init__(self, fileName): + if isinstance(fileName,ImageReader): + self.__dict__ = fileName.__dict__ #borgize + return + if not haveImages: + raise RuntimeError('Imaging Library not available, unable to import bitmaps') + #start wih lots of null private fields, to be populated by + #the relevant engine. + self.fileName = fileName + self._image = None + self._width = None + self._height = None + self._transparent = None + self._data = None + if _isPILImage(fileName): + self._image = fileName + self.fp = fileName.fp + try: + self.fileName = im.fileName + except AttributeError: + self.fileName = 'PILIMAGE_%d' % id(self) + else: + try: + self.fp = open_for_read(fileName,'b') + #detect which library we are using and open the image + if sys.platform[0:4] == 'java': + from javax.imageio import ImageIO + self._image = ImageIO.read(self.fp) + else: + import PIL.Image + self._image = PIL.Image.open(self.fp) + if self._image=='JPEG': self.jpeg_fh = self._jpeg_fp + except: + et,ev,tb = sys.exc_info() + if hasattr(ev,'args'): + a = str(ev.args[-1])+(' fileName='+fileName) + ev.args= ev.args[:-1]+(a,) + raise et,ev,tb + else: + raise + + + def _jpeg_fh(self): + fp = self.fp + fp.seek(0) + return fp + + def jpeg_fh(self): + return None + + def getSize(self): + if (self._width is None or self._height is None): + if sys.platform[0:4] == 'java': + self._width = self._image.getWidth() + self._height = self._image.getHeight() + else: + self._width, self._height = self._image.size + return (self._width, self._height) + + def getRGBData(self): + "Return byte array of RGB data as string" + if self._data is None: + if sys.platform[0:4] == 'java': + import jarray + from java.awt.image import PixelGrabber + width, height = self.getSize() + buffer = jarray.zeros(width*height, 'i') + pg = PixelGrabber(self._image, 0,0,width,height,buffer,0,width) + pg.grabPixels() + # there must be a way to do this with a cast not a byte-level loop, + # I just haven't found it yet... + pixels = [] + a = pixels.append + for i in range(len(buffer)): + rgb = buffer[i] + a(chr((rgb>>16)&0xff)) + a(chr((rgb>>8)&0xff)) + a(chr(rgb&0xff)) + self._data = ''.join(pixels) + self.mode = 'RGB' + else: + im = self._image + mode = self.mode = im.mode + if mode not in ('L','RGB','CMYK'): + im = im.convert('RGB') + self.mode = 'RGB' + self._data = im.tostring() + return self._data + + def getImageData(self): + width, height = self.getSize() + return width, height, self.getRGBData() + + def getTransparent(self): + if sys.platform[0:4] == 'java': + return None + else: + if self._image.info.has_key("transparency"): + transparency = self._image.info["transparency"] * 3 + palette = self._image.palette + try: + palette = palette.palette + except: + palette = palette.data + return map(ord, palette[transparency:transparency+3]) + else: + return None + +def getImageData(imageFileName): + "Get width, height and RGB pixels from image file. Wraps Java/PIL" + try: + return imageFileName.getImageData() + except AttributeError: + return ImageReader(imageFileName).getImageData() + +class DebugMemo: + '''Intended as a simple report back encapsulator + + Typical usages + 1) To record error data + dbg = DebugMemo(fn='dbgmemo.dbg',myVar=value) + dbg.add(anotherPayload='aaaa',andagain='bbb') + dbg.dump() + + 2) To show the recorded info + dbg = DebugMemo(fn='dbgmemo.dbg',mode='r') + dbg.load() + dbg.show() + + 3) To re-use recorded information + dbg = DebugMemo(fn='dbgmemo.dbg',mode='r') + dbg.load() + myTestFunc(dbg.payload('myVar'),dbg.payload('andagain')) + + in addition to the payload variables the dump records many useful bits + of information which are also printed in the show() method. + ''' + def __init__(self,fn='rl_dbgmemo.dbg',mode='w',getScript=1,modules=(),capture_traceback=1, stdout=None, **kw): + import time, socket + self.fn = fn + if mode!='w': return + if not stdout: + self.stdout = sys.stdout + else: + if hasattr(stdout,'write'): + self.stdout = stdout + else: + self.stdout = open(stdout,'w') + self.store = store = {} + if capture_traceback and sys.exc_info() != (None,None,None): + import traceback + s = getStringIO() + traceback.print_exc(None,s) + store['__traceback'] = s.getvalue() + cwd=os.getcwd() + lcwd = os.listdir(cwd) + exed = os.path.abspath(os.path.dirname(sys.argv[0])) + store.update({ 'gmt': time.asctime(time.gmtime(time.time())), + 'platform': sys.platform, + 'version': sys.version, + 'hexversion': hex(sys.hexversion), + 'executable': sys.executable, + 'exec_prefix': sys.exec_prefix, + 'prefix': sys.prefix, + 'path': sys.path, + 'argv': sys.argv, + 'cwd': cwd, + 'hostname': socket.gethostname(), + 'lcwd': lcwd, + 'byteorder': sys.byteorder, + 'maxint': sys.maxint, + 'maxint': getattr(sys,'maxunicode','????'), + 'api_version': getattr(sys,'api_version','????'), + 'version_info': getattr(sys,'version_info','????'), + 'winver': getattr(sys,'winver','????'), + 'environment': os.environ, + }) + for M,A in ( + (sys,('getwindowsversion','getfilesystemencoding')), + (os,('uname', 'ctermid', 'getgid', 'getuid', 'getegid', + 'geteuid', 'getlogin', 'getgroups', 'getpgrp', 'getpid', 'getppid', + )), + ): + for a in A: + if hasattr(M,a): + try: + store[a] = getattr(M,a)() + except: + pass + if exed!=cwd: + try: + store.update({'exed': exed, 'lexed': os.listdir(exed),}) + except: + pass + if getScript: + fn = os.path.abspath(sys.argv[0]) + if os.path.isfile(fn): + try: + store['__script'] = (fn,open(fn,'r').read()) + except: + pass + module_versions = {} + for n,m in sys.modules.items(): + if n=='reportlab' or n=='rlextra' or n[:10]=='reportlab.' or n[:8]=='rlextra.': + v = getattr(m,'__version__',None) + if v: module_versions[n] = v + store['__module_versions'] = module_versions + self.store['__payload'] = {} + self._add(kw) + + def _add(self,D): + payload = self.store['__payload'] + for k, v in D.items(): + payload[k] = v + + def add(self,**kw): + self._add(kw) + + def dump(self): + import pickle + pickle.dump(self.store,open(self.fn,'wb')) + + def load(self): + import pickle + self.store = pickle.load(open(self.fn,'rb')) + + def _show_module_versions(self,k,v): + self._writeln(k[2:]) + K = v.keys() + K.sort() + for k in K: + vk = v[k] + try: + m = recursiveImport(k,sys.path[:],1) + d = getattr(m,'__version__',None)==vk and 'SAME' or 'DIFFERENT' + except: + m = None + d = '??????unknown??????' + self._writeln(' %s = %s (%s)' % (k,vk,d)) + + def _banner(self,k,what): + self._writeln('###################%s %s##################' % (what,k[2:])) + + def _start(self,k): + self._banner(k,'Start ') + + def _finish(self,k): + self._banner(k,'Finish ') + + def _show_lines(self,k,v): + self._start(k) + self._writeln(v) + self._finish(k) + + def _show_file(self,k,v): + k = '%s %s' % (k,os.path.basename(v[0])) + self._show_lines(k,v[1]) + + def _show_payload(self,k,v): + if v: + import pprint + self._start(k) + pprint.pprint(v,self.stdout) + self._finish(k) + + specials = {'__module_versions': _show_module_versions, + '__payload': _show_payload, + '__traceback': _show_lines, + '__script': _show_file, + } + def show(self): + K = self.store.keys() + K.sort() + for k in K: + if k not in self.specials.keys(): self._writeln('%-15s = %s' % (k,self.store[k])) + for k in K: + if k in self.specials.keys(): apply(self.specials[k],(self,k,self.store[k])) + + def payload(self,name): + return self.store['__payload'][name] + + def __setitem__(self,name,value): + self.store['__payload'][name] = value + + def __getitem__(self,name): + return self.store['__payload'][name] + + def _writeln(self,msg): + self.stdout.write(msg+'\n') + +def _flatten(L,a): + for x in L: + if isSeqType(x): _flatten(x,a) + else: a(x) + +def flatten(L): + '''recursively flatten the list or tuple L''' + R = [] + _flatten(L,R.append) + return R + +def find_locals(func,depth=0): + '''apply func to the locals at each stack frame till func returns a non false value''' + while 1: + _ = func(sys._getframe(depth).f_locals) + if _: return _ + depth += 1 + +class _FmtSelfDict: + def __init__(self,obj,overrideArgs): + self.obj = obj + self._overrideArgs = overrideArgs + def __getitem__(self,k): + try: + return self._overrideArgs[k] + except KeyError: + try: + return self.obj.__dict__[k] + except KeyError: + return getattr(self.obj,k) + +class FmtSelfDict: + '''mixin to provide the _fmt method''' + def _fmt(self,fmt,**overrideArgs): + D = _FmtSelfDict(self, overrideArgs) + return fmt % D diff --git a/bin/reportlab/lib/validators.py b/bin/reportlab/lib/validators.py new file mode 100644 index 00000000000..3f7f06edb79 --- /dev/null +++ b/bin/reportlab/lib/validators.py @@ -0,0 +1,324 @@ +#Copyright ReportLab Europe Ltd. 2000-2004 +#see license.txt for license details +#history http://www.reportlab.co.uk/cgi-bin/viewcvs.cgi/public/reportlab/trunk/reportlab/lib/validators.py +__version__=''' $Id: validators.py 2875 2006-05-18 07:00:16Z andy $ ''' +""" +This module contains some standard verifying functions which can be +used in an attribute map. +""" + +import string, sys, codecs +from types import * +_SequenceTypes = (ListType,TupleType) +_NumberTypes = (FloatType,IntType) +from reportlab.lib import colors +if sys.hexversion<0x2030000: + True = 1 + False = 0 + +class Validator: + "base validator class" + def __call__(self,x): + return self.test(x) + + def __str__(self): + return getattr(self,'_str',self.__class__.__name__) + + def normalize(self,x): + return x + + def normalizeTest(self,x): + try: + self.normalize(x) + return True + except: + return False + +class _isAnything(Validator): + def test(self,x): + return True + +class _isNothing(Validator): + def test(self,x): + return False + +class _isBoolean(Validator): + if sys.hexversion>=0x2030000: + def test(self,x): + if type(x) in (IntType,BooleanType): return x in (0,1) + return self.normalizeTest(x) + else: + def test(self,x): + if type(x) is IntType: return x in (0,1) + return self.normalizeTest(x) + + def normalize(self,x): + if x in (0,1): return x + try: + S = string.upper(x) + except: + raise ValueError, 'Must be boolean' + if S in ('YES','TRUE'): return True + if S in ('NO','FALSE',None): return False + raise ValueError, 'Must be boolean' + +class _isString(Validator): + def test(self,x): + return type(x) in (StringType, UnicodeType) + +class _isCodec(Validator): + def test(self,x): + if type(x) not in (StringType, UnicodeType): + return False + try: + a,b,c,d = codecs.lookup(x) + return True + except LookupError: + return False + +class _isNumber(Validator): + def test(self,x): + if type(x) in _NumberTypes: return True + return self.normalizeTest(x) + + def normalize(self,x): + try: + return float(x) + except: + return int(x) + +class _isInt(Validator): + def test(self,x): + if type(x) not in (IntType,StringType): return False + return self.normalizeTest(x) + + def normalize(self,x): + return int(x) + +class _isNumberOrNone(_isNumber): + def test(self,x): + return x is None or isNumber(x) + + def normalize(self,x): + if x is None: return x + return _isNumber.normalize(x) + +class _isListOfNumbersOrNone(Validator): + "ListOfNumbersOrNone validator class." + def test(self, x): + if x is None: return True + return isListOfNumbers(x) + +class _isListOfShapes(Validator): + "ListOfShapes validator class." + def test(self, x): + from reportlab.graphics.shapes import Shape + if type(x) in _SequenceTypes: + answer = 1 + for element in x: + if not isinstance(x, Shape): + answer = 0 + return answer + else: + return False + +class _isListOfStringsOrNone(Validator): + "ListOfStringsOrNone validator class." + + def test(self, x): + if x is None: return True + return isListOfStrings(x) + +class _isTransform(Validator): + "Transform validator class." + def test(self, x): + if type(x) in _SequenceTypes: + if len(x) == 6: + for element in x: + if not isNumber(element): + return False + return True + else: + return False + else: + return False + +class _isColor(Validator): + "Color validator class." + def test(self, x): + return isinstance(x, colors.Color) + +class _isColorOrNone(Validator): + "ColorOrNone validator class." + def test(self, x): + if x is None: return True + return isColor(x) + +class _isValidChild(Validator): + "ValidChild validator class." + def test(self, x): + """Is this child allowed in a drawing or group? + I.e. does it descend from Shape or UserNode? + """ + + from reportlab.graphics.shapes import UserNode, Shape + return isinstance(x, UserNode) or isinstance(x, Shape) + +class _isValidChildOrNone(_isValidChild): + def test(self,x): + return _isValidChild.test(self,x) or x is None + +class _isCallable(Validator): + def test(self, x): + return callable(x) + +class OneOf(Validator): + """Make validator functions for list of choices. + + Usage: + f = reportlab.lib.validators.OneOf('happy','sad') + or + f = reportlab.lib.validators.OneOf(('happy','sad')) + f('sad'),f('happy'), f('grumpy') + (1,1,0) + """ + def __init__(self, enum,*args): + if type(enum) in [ListType,TupleType]: + if args!=(): + raise ValueError, "Either all singleton args or a single sequence argument" + self._enum = tuple(enum)+args + else: + self._enum = (enum,)+args + + def test(self, x): + return x in self._enum + +class SequenceOf(Validator): + def __init__(self,elemTest,name=None,emptyOK=1, NoneOK=0, lo=0,hi=0x7fffffff): + self._elemTest = elemTest + self._emptyOK = emptyOK + self._NoneOK = NoneOK + self._lo, self._hi = lo, hi + if name: self._str = name + + def test(self, x): + if type(x) not in _SequenceTypes: + if x is None: return self._NoneOK + return False + if x==[] or x==(): + return self._emptyOK + elif not self._lo<=len(x)<=self._hi: return False + for e in x: + if not self._elemTest(e): return False + return True + +class EitherOr(Validator): + def __init__(self,tests,name=None): + if type(tests) not in _SequenceTypes: tests = (tests,) + self._tests = tests + if name: self._str = name + + def test(self, x): + for t in self._tests: + if t(x): return True + return False + +class NoneOr(Validator): + def __init__(self,elemTest,name=None): + self._elemTest = elemTest + if name: self._str = name + + def test(self, x): + if x is None: return True + return self._elemTest(x) + +class Auto(Validator): + def __init__(self,**kw): + self.__dict__.update(kw) + + def test(self,x): + return x is self.__class__ or isinstance(x,self.__class__) + +class AutoOr(NoneOr): + def test(self,x): + return isAuto(x) or self._elemTest(x) + +class isInstanceOf(Validator): + def __init__(self,klass=None): + self._klass = klass + def test(self,x): + return isinstance(x,self._klass) + +class matchesPattern(Validator): + """Matches value, or its string representation, against regex""" + def __init__(self, pattern): + self._pattern = re.compile(pattern) + + def test(self,x): + print 'testing %s against %s' % (x, self._pattern) + if type(x) is StringType: + text = x + else: + text = str(x) + return (self._pattern.match(text) <> None) + +class DerivedValue: + """This is used for magic values which work themselves out. + An example would be an "inherit" property, so that one can have + + drawing.chart.categoryAxis.labels.fontName = inherit + + and pick up the value from the top of the drawing. + Validators will permit this provided that a value can be pulled + in which satisfies it. And the renderer will have special + knowledge of these so they can evaluate themselves. + """ + def getValue(self, renderer, attr): + """Override this. The renderers will pass the renderer, + and the attribute name. Algorithms can then backtrack up + through all the stuff the renderer provides, including + a correct stack of parent nodes.""" + return None + +class Inherit(DerivedValue): + def __repr__(self): + return "inherit" + + def getValue(self, renderer, attr): + return renderer.getStateValue(attr) + +inherit = Inherit() + + +isAuto = Auto() +isBoolean = _isBoolean() +isString = _isString() +isCodec = _isCodec() +isNumber = _isNumber() +isInt = _isInt() +isNoneOrInt = NoneOr(isInt,'isNoneOrInt') +isNumberOrNone = _isNumberOrNone() +isTextAnchor = OneOf('start','middle','end','boxauto') +isListOfNumbers = SequenceOf(isNumber,'isListOfNumbers') +isListOfNumbersOrNone = _isListOfNumbersOrNone() +isListOfShapes = _isListOfShapes() +isListOfStrings = SequenceOf(isString,'isListOfStrings') +isListOfStringsOrNone = _isListOfStringsOrNone() +isTransform = _isTransform() +isColor = _isColor() +isListOfColors = SequenceOf(isColor,'isListOfColors') +isColorOrNone = _isColorOrNone() +isShape = isValidChild = _isValidChild() +isNoneOrShape = isValidChildOrNone = _isValidChildOrNone() +isAnything = _isAnything() +isNothing = _isNothing() +isXYCoord = SequenceOf(isNumber,lo=2,hi=2,emptyOK=0) +isBoxAnchor = OneOf('nw','n','ne','w','c','e','sw','s','se', 'autox', 'autoy') +isNoneOrString = NoneOr(isString,'NoneOrString') +isNoneOrListOfNoneOrStrings=SequenceOf(isNoneOrString,'isNoneOrListOfNoneOrStrings',NoneOK=1) +isListOfNoneOrString=SequenceOf(isNoneOrString,'isListOfNoneOrString',NoneOK=0) +isNoneOrListOfNoneOrNumbers=SequenceOf(isNumberOrNone,'isNoneOrListOfNoneOrNumbers',NoneOK=1) +isCallable = _isCallable() +isStringOrCallable=EitherOr((isString,isCallable),'isStringOrCallable') +isStringOrCallableOrNone=NoneOr(isStringOrCallable,'isStringOrCallableNone') +isStringOrNone=NoneOr(isString,'isStringOrNone') diff --git a/bin/reportlab/lib/xmllib.py b/bin/reportlab/lib/xmllib.py new file mode 100644 index 00000000000..041646fbb42 --- /dev/null +++ b/bin/reportlab/lib/xmllib.py @@ -0,0 +1,769 @@ +# A parser for XML, using the derived class as static DTD. +# Author: Sjoerd Mullender. + +# sgmlop support added by fredrik@pythonware.com (May 19, 1998) + +import re +import string + +try: + import sgmlop # this works for both builtin on the path or relative +except ImportError: + sgmlop = None + +# standard entity defs + +ENTITYDEFS = { + 'lt': '<', + 'gt': '>', + 'amp': '&', + 'quot': '"', + 'apos': '\'' + } + +# XML parser base class -- find tags and call handler functions. +# Usage: p = XMLParser(); p.feed(data); ...; p.close(). +# The dtd is defined by deriving a class which defines methods with +# special names to handle tags: start_foo and end_foo to handle +# and , respectively. The data between tags is passed to the +# parser by calling self.handle_data() with some data as argument (the +# data may be split up in arbutrary chunks). Entity references are +# passed by calling self.handle_entityref() with the entity reference +# as argument. + +# -------------------------------------------------------------------- +# original re-based XML parser + +_S = '[ \t\r\n]+' +_opS = '[ \t\r\n]*' +_Name = '[a-zA-Z_:][-a-zA-Z0-9._:]*' +interesting = re.compile('[&<]') +incomplete = re.compile('&(' + _Name + '|#[0-9]*|#x[0-9a-fA-F]*)?|' + '<([a-zA-Z_:][^<>]*|' + '/([a-zA-Z_:][^<>]*)?|' + '![^<>]*|' + '\?[^<>]*)?') + +ref = re.compile('&(' + _Name + '|#[0-9]+|#x[0-9a-fA-F]+);?') +entityref = re.compile('&(?P' + _Name + ')[^-a-zA-Z0-9._:]') +charref = re.compile('&#(?P[0-9]+[^0-9]|x[0-9a-fA-F]+[^0-9a-fA-F])') +space = re.compile(_S) +newline = re.compile('\n') + +starttagopen = re.compile('<' + _Name) +endtagopen = re.compile('/?)>') +endbracket = re.compile('>') +tagfind = re.compile(_Name) +cdataopen = re.compile('') +special = re.compile('[^<>]*)>') +procopen = re.compile('<\?(?P' + _Name + ')' + _S) +procclose = re.compile('\?>') +commentopen = re.compile('') +doubledash = re.compile('--') +attrfind = re.compile( + _opS + '(?P' + _Name + ')' + '(' + _opS + '=' + _opS + + '(?P\'[^\']*\'|"[^"]*"|[-a-zA-Z0-9.:+*%?!()_#=~]+))') + +class SlowXMLParser: + + # Interface -- initialize and reset this instance + def __init__(self, verbose=0): + self.verbose = verbose + self.reset() + + # Interface -- reset this instance. Loses all unprocessed data + def reset(self): + self.rawdata = '' + self.stack = [] + self.lasttag = '???' + self.nomoretags = 0 + self.literal = 0 + self.lineno = 1 + + # For derived classes only -- enter literal mode (CDATA) till EOF + def setnomoretags(self): + self.nomoretags = self.literal = 1 + + # For derived classes only -- enter literal mode (CDATA) + def setliteral(self, *args): + self.literal = 1 + + # Interface -- feed some data to the parser. Call this as + # often as you want, with as little or as much text as you + # want (may include '\n'). (This just saves the text, all the + # processing is done by goahead().) + def feed(self, data): + self.rawdata = self.rawdata + data + self.goahead(0) + + # Interface -- handle the remaining data + def close(self): + self.goahead(1) + + # Interface -- translate references + def translate_references(self, data): + newdata = [] + i = 0 + while 1: + res = ref.search(data, i) + if res is None: + newdata.append(data[i:]) + return string.join(newdata, '') + if data[res.end(0) - 1] != ';': + self.syntax_error(self.lineno, + '; missing after entity/char reference') + newdata.append(data[i:res.start(0)]) + str = res.group(1) + if str[0] == '#': + if str[1] == 'x': + newdata.append(chr(string.atoi(str[2:], 16))) + else: + newdata.append(chr(string.atoi(str[1:]))) + else: + try: + newdata.append(self.entitydefs[str]) + except KeyError: + # can't do it, so keep the entity ref in + newdata.append('&' + str + ';') + i = res.end(0) + + # Internal -- handle data as far as reasonable. May leave state + # and data to be processed by a subsequent call. If 'end' is + # true, force handling all data as if followed by EOF marker. + def goahead(self, end): + rawdata = self.rawdata + i = 0 + n = len(rawdata) + while i < n: + if self.nomoretags: + data = rawdata[i:n] + self.handle_data(data) + self.lineno = self.lineno + string.count(data, '\n') + i = n + break + res = interesting.search(rawdata, i) + if res: + j = res.start(0) + else: + j = n + if i < j: + data = rawdata[i:j] + self.handle_data(data) + self.lineno = self.lineno + string.count(data, '\n') + i = j + if i == n: break + if rawdata[i] == '<': + if starttagopen.match(rawdata, i): + if self.literal: + data = rawdata[i] + self.handle_data(data) + self.lineno = self.lineno + string.count(data, '\n') + i = i+1 + continue + k = self.parse_starttag(i) + if k < 0: break + self.lineno = self.lineno + string.count(rawdata[i:k], '\n') + i = k + continue + if endtagopen.match(rawdata, i): + k = self.parse_endtag(i) + if k < 0: break + self.lineno = self.lineno + string.count(rawdata[i:k], '\n') + i = k + self.literal = 0 + continue + if commentopen.match(rawdata, i): + if self.literal: + data = rawdata[i] + self.handle_data(data) + self.lineno = self.lineno + string.count(data, '\n') + i = i+1 + continue + k = self.parse_comment(i) + if k < 0: break + self.lineno = self.lineno + string.count(rawdata[i:k], '\n') + i = k + continue + if cdataopen.match(rawdata, i): + k = self.parse_cdata(i) + if k < 0: break + self.lineno = self.lineno + string.count(rawdata[i:i], '\n') + i = k + continue + res = procopen.match(rawdata, i) + if res: + k = self.parse_proc(i, res) + if k < 0: break + self.lineno = self.lineno + string.count(rawdata[i:k], '\n') + i = k + continue + res = special.match(rawdata, i) + if res: + if self.literal: + data = rawdata[i] + self.handle_data(data) + self.lineno = self.lineno + string.count(data, '\n') + i = i+1 + continue + self.handle_special(res.group('special')) + self.lineno = self.lineno + string.count(res.group(0), '\n') + i = res.end(0) + continue + elif rawdata[i] == '&': + res = charref.match(rawdata, i) + if res is not None: + i = res.end(0) + if rawdata[i-1] != ';': + self.syntax_error(self.lineno, '; missing in charref') + i = i-1 + self.handle_charref(res.group('char')[:-1]) + self.lineno = self.lineno + string.count(res.group(0), '\n') + continue + res = entityref.match(rawdata, i) + if res is not None: + i = res.end(0) + if rawdata[i-1] != ';': + self.syntax_error(self.lineno, '; missing in entityref') + i = i-1 + self.handle_entityref(res.group('name')) + self.lineno = self.lineno + string.count(res.group(0), '\n') + continue + else: + raise RuntimeError, 'neither < nor & ??' + # We get here only if incomplete matches but + # nothing else + res = incomplete.match(rawdata, i) + if not res: + data = rawdata[i] + self.handle_data(data) + self.lineno = self.lineno + string.count(data, '\n') + i = i+1 + continue + j = res.end(0) + if j == n: + break # Really incomplete + self.syntax_error(self.lineno, 'bogus < or &') + data = res.group(0) + self.handle_data(data) + self.lineno = self.lineno + string.count(data, '\n') + i = j + # end while + if end and i < n: + data = rawdata[i:n] + self.handle_data(data) + self.lineno = self.lineno + string.count(data, '\n') + i = n + self.rawdata = rawdata[i:] + # XXX if end: check for empty stack + + # Internal -- parse comment, return length or -1 if not terminated + def parse_comment(self, i): + rawdata = self.rawdata + if rawdata[i:i+4] <> '