Changeset 812

Show
Ignore:
Timestamp:
02/18/05 17:44:15 (4 years ago)
Author:
paul
Message:

Made the reportwriter into a class with properties and methods. Abstracted
out a bunch of defaults for the report object properties - someone could
subclass ReportWriter? to change the defaults if they wanted to. The defaults
apply when the report form doesn't specify them.

Began planning for an xml report form file but that isn't working yet - it
still needs the form file to be a python module.

Fixed some layout problems with hAnchor, vAnchor, and alignment.

Added a main() function at the bottom of reportWriter.py with some tweakable
options for testing.

Files:

Legend:

Unmodified
Added
Removed
Modified
Copied
Moved
  • trunk/reporting/reportWriter.py

    r806 r812  
    1 ##  reportWriter.py 
    2 ##  begun 2/14/2005 by pkm 
    3 ##  This is just a first pass, with ugly procedural code. When you run it by 
    4 ##  "python reportWriter.py", it'll read the samplespec.py file and create a 
    5 ##  test.pdf file. Eventually, you'll evoke an instance of the dReportWriter 
    6 ##  class, and feed it properties like OutputFilePath, Cursor, and ReportForm. 
    7  
    8 ##  Almost all values from the spec file are evaluated at runtime, allowing 
    9 ##  simple yet flexible redirection (a given prop can be gotten at runtime 
    10 ##  from a user-defined function). 
    11  
    12 import sys 
     1import os 
    132import reportlab.pdfgen.canvas as canvas 
    143import reportlab.graphics.shapes as shapes 
     
    176#import reportlab.lib.colors as colors 
    187 
    19 #form = sys.argv[1] 
    20 import samplespec as form 
    21  
    22  
    23 def getPt(val): 
    24     """Given a string or a number, convert the value into a numeric pt value. 
    25  
    26     Strings can have the unit appended, like "3.5 in", "2 cm", "3 pica", "10 mm". 
    27  
    28     > print getPt("1 in") 
    29     72 
    30     > print getPt("1") 
    31     1 
    32     > print getPt(1) 
    33     1 
     8 
     9class ReportWriter(object): 
     10    """Reads a report form specification, iterates over a data cursor, and 
     11    outputs a pdf file. Allows for lots of fine-tuned control over layout, and 
     12    dynamic evaluation of object properties. Works with the concept of bands, 
     13    letting the designer lay out the page header, footer, groups, and detail 
     14    separately.  
     15 
     16    At runtime, you feed ReportWriter a data cursor (a list of dictionaries 
     17    where each list index is a 'row' and each dictionary key is a 'field'.) 
     18    The detail band will print once for every row. 
     19 
     20    Define your properties in the report form specification file, which is 
     21    either xml or pure Python, depending on your preferences. There are (will 
     22    be) examples of both types of specification files here. In the future  
     23    there will be a Dabo Report Designer that will create the xml report form 
     24    specification files for you. 
     25 
     26    In the context of a running report, the property values of the specification 
     27    can refer to 'self', which is the ReportWriter instance. Thus, you can use 
     28    the self instance to get to whatever value you want for the property. 
     29 
     30    For example, to get the value of a field to print in your detail band, just  
     31    put a string object into the detail band, positioned and sized how you want, 
     32    and set the 'expr' property to refer to the field. If the field name is 
     33    'cArtist', the expr for the string object would be 'self.Record["cArtist"]'. 
     34 
     35    You'll need to craft denormalized data, as ReportWriter only wants to operate 
     36    on a single table and there is no provision for relating one table to another. 
     37    This is, IMO, the right way to go anyway, offering the most control and  
     38    flexibility yet still keeping it really simple. Just have the calling program 
     39    get the data denormalized into one cursor, and then call ReportWriter  
     40    feeding it the Cursor, Report Form, and OutputName. 
     41 
     42    More documentation will come. 
    3443    """ 
    35     if type(val) in (int, long, float): 
    36         # return as-is as the pt value. 
    37         return val 
    38     else: 
    39         # try to run it through reportlab's units.toLength() function: 
    40         return units.toLength(val) 
    41  
    42  
    43 #data = sys.argv[2] 
    44 data = [{"cArtist": "The Clash", "iid": 1}, 
    45         {"cArtist": "Queen", "iid": 2}, 
    46         {"cArtist": "Ben Harper and the Innocent Criminals", "iid":3}] 
    47  
    48 # The report dict contains information about the running report: 
    49 report = {} 
    50 report["bands"] = {} 
    51  
    52  
    53 # Draw a dotted rectangle around each band: 
    54 showBands = True 
    55  
    56 ## Set the Page Size: 
    57 # get the string pageSize value from the spec file: 
    58 pageSize = eval(form.Page["size"]) 
    59 # reportlab expects the pageSize to be upper case: 
    60 pageSize = pageSize.upper() 
    61 # convert to the reportlab pageSize value (tuple(width,height)): 
    62 pageSize = eval("pagesizes.%s" % pageSize) 
    63 # run it through the portrait/landscape filter: 
    64 try: 
    65     orientation = eval(form.Page["orientation"]) 
    66 except KeyError: 
    67     orientation = "portrait" 
    68 func = eval("pagesizes.%s" % orientation) 
    69 pageSize = func(pageSize) 
    70 pageWidth, pageHeight = pageSize 
    71 print "Page Size:", pageSize 
    72 ## end Page Size setting (refactor to a separate function) 
    73  
    74  
    75 # Create the report canvas and add it to the report dict: 
    76 c = canvas.Canvas("test.pdf", pagesize=pageSize) 
    77 report["canvas"] = c 
    78  
    79  
    80 # Get the page margins into variables: 
    81 ml = getPt(eval(form.Page["marginLeft"])) 
    82 mt = getPt(eval(form.Page["marginTop"])) 
    83 mr = getPt(eval(form.Page["marginRight"])) 
    84 mb = getPt(eval(form.Page["marginBottom"])) 
    85  
    86 # Page header/footer origins are needed in various places: 
    87 pageHeaderOrigin = (ml, pageHeight - mt  
    88                     - getPt(eval(form.PageHeader["height"]))) 
    89 pageFooterOrigin = (ml, mb) 
    90  
    91  
    92 def printBandOutline(band, x, y, width, height): 
    93         ## draw a dotted rectangle around the entire band, and type a small faded  
    94         ## caption at the origin of the band. 
     44 
     45    ## The following defaults will be used for properties that weren't defined in 
     46    ## the spec file (no need to explicitly define properties if you want the  
     47    ## defaults). Subclass to change defaults to your needs. 
     48    default_pageSize = "letter"            # you may want 'a4' outside of the US 
     49    default_pageOrientation = "portrait"   # the other option is "landscape" 
     50    default_width = 55 
     51    default_height = 18 
     52    default_x = 0 
     53    default_y = 0 
     54    default_rotation = 0                   # (0-359) 
     55    default_hAnchor = "left"               # hor. anchor (what x is relative to) 
     56    default_vAnchor = "bottom"             # vert. anchor (what y is relative to) 
     57    default_strokeWidth = 1                # the brush stroke width for shapes 
     58    default_fillColor = None               # None: transparent or (r,g,b) tuple 
     59    default_strokeColor = (0, 0, 0)        # (black) 
     60    default_strokeDashArray = None         # (use for dashed lines) 
     61    default_borderWidth = 0                # width of border around strings 
     62    default_borderColor = (0, 0, 0)        # color of border around strings 
     63    default_align = "left"                 # string alignment 
     64    default_fontName = "Helvetica" 
     65    default_fontSize = 10 
     66    default_fontColor = (0, 0, 0)       
     67    default_imageMask = None               # Transparency mask for images 
     68    default_scaleMode = "scale"            # "clip" or "scale" for images. 
     69 
     70 
     71    def draw(self, object, origin): 
     72        """Draw the given object on the Canvas. 
     73 
     74        The object is a dictionary containing properties, and   origin is the (x,y) 
     75        tuple where the object's (x,y) properties should be relative to. 
     76        """ 
     77        c = self.Canvas 
     78        x,y = origin 
     79 
    9580        c.saveState() 
    96         c.setLineWidth(0.1) 
    97         c.setStrokeColorRGB(0.8, 0.5, 0.7) 
    98         c.setDash(1, 2) 
    99         c.rect(x, y, width, height) 
    100         c.setFont("Helvetica", 8) 
    101         c.setFillColor((0.6, 0.8, 0.7)) 
    102         c.drawString(x, y, band) 
    103         c.restoreState() 
    104  
    105  
    106 def draw(object, x, y): 
    107     c.saveState() 
    108  
    109     try:  
    110         width = getPt(eval(object["width"])) 
    111     except (KeyError, TypeError, ValueError):  
    112         width = 55 
    113  
    114     try:  
    115         height = getPt(eval(object["height"])) 
    116     except (KeyError, TypeError, ValueError):  
    117         height = 10 
    118  
    119     try:  
    120         rotation = eval(object["rotation"]) 
    121     except KeyError: 
    122         rotation = 0 
    123  
    124     try: 
    125         hAnchor = eval(object["hAnchor"]) 
    126     except: 
    127         hAnchor = "left" 
    128  
    129     try: 
    130         vAnchor = eval(object["vAnchor"]) 
    131     except: 
    132         vAnchor = "bottom" 
    133  
    134     if hAnchor == "right": 
    135         x = x - width 
    136     elif hAnchor == "center": 
    137         x = x - (width / 2) 
    138      
    139     if vAnchor == "top": 
    140         y = y - height 
    141     elif vAnchor == "center": 
    142         y = y - (height / 2) 
    143  
    144     if object["type"] == "rect": 
    145         d = shapes.Drawing(width, height) 
    146         d.rotate(rotation) 
    147  
    148         props = {} 
    149         ## props available in reportlab that we use: 
    150         ##   x,y,width,height 
    151         ##   fillColor: None for transparent, or (r,g,b) 
    152         ##   strokeColor: None for transparent, or (r,g,b) 
    153         ##   strokeDashArray: None 
    154         ##   strokeWidth: 0.25 
    155  
    156         ## props available that we don't currently use: 
    157         ##   rx, ry 
    158         ##   strokeMiterLimit: 0 
    159         ##   strokeLineJoin: 0 
    160         ##   strokeLineCap: 0 
    161         ## 
    162  
    163         try: 
    164             props["strokeWidth"] = getPt(eval(object["strokeWidth"])) 
    165         except KeyError:  
    166             props["strokeWidth"] = 1 
    167  
    168         try: 
    169             props["fillColor"] = eval(object["fillColor"]) 
     81     
     82        try:  
     83            width = self.getPt(eval(object["width"])) 
     84        except (KeyError, TypeError, ValueError):  
     85            width = self.default_width 
     86     
     87        try:  
     88            height = self.getPt(eval(object["height"])) 
     89        except (KeyError, TypeError, ValueError):  
     90            height = self.default_height 
     91     
     92        try:  
     93            rotation = eval(object["rotation"]) 
    17094        except KeyError: 
    171             props["fillColor"] = None 
    172  
    173         try: 
    174             props["strokeColor"] = eval(object["strokeColor"]) 
    175         except KeyError: 
    176             props["strokeColor"] = (0, 0, 0) 
    177  
    178         try: 
    179             props["strokeDashArray"] = eval(object["strokeDashArray"]) 
    180         except KeyError: 
    181             props["strokeDashArray"] = None 
    182  
    183         r = shapes.Rect(0, 0, width, height) 
    184         r.setProperties(props) 
    185         d.add(r) 
    186         d.drawOn(c, x, y) 
    187  
    188     elif object["type"] == "string": 
    189         c.translate(x, y) 
    190         c.rotate(rotation) 
    191         try:  
    192             borderWidth = getPt(eval(object["borderWidth"])) 
    193         except KeyError:  
    194             borderWidth = 0 
    195  
    196         try:  
    197             borderColor = eval(object["borderColor"]) 
    198         except KeyError:  
    199             borderColor = (0, 0, 0) 
    200  
    201         c.setLineWidth(borderWidth) 
    202         c.setStrokeColor(borderColor) 
    203  
    204         if borderWidth > 0: 
    205             stroke = 1 
    206         else: 
    207             stroke = 0 
    208  
    209         if width is not None and height is not None: 
     95            rotation = self.default_rotation 
     96     
     97        try: 
     98            hAnchor = eval(object["hAnchor"]) 
     99        except: 
     100            hAnchor = self.default_hAnchor 
     101     
     102        try: 
     103            vAnchor = eval(object["vAnchor"]) 
     104        except: 
     105            vAnchor = self.default_vAnchor 
     106     
     107        if hAnchor == "right": 
     108            x = x - width 
     109        elif hAnchor == "center": 
     110            x = x - (width / 2) 
     111         
     112        if vAnchor == "top": 
     113            y = y - height 
     114        elif vAnchor == "center": 
     115            y = y - (height / 2) 
     116     
     117        if object["type"] == "rect": 
     118            d = shapes.Drawing(width, height) 
     119            d.rotate(rotation) 
     120     
     121            props = {} 
     122            ## props available in reportlab that we use: 
     123            ##   x,y,width,height 
     124            ##   fillColor: None for transparent, or (r,g,b) 
     125            ##   strokeColor: None for transparent, or (r,g,b) 
     126            ##   strokeDashArray: None 
     127            ##   strokeWidth: 0.25 
     128     
     129            ## props available that we don't currently use: 
     130            ##   rx, ry 
     131            ##   strokeMiterLimit: 0 
     132            ##   strokeLineJoin: 0 
     133            ##   strokeLineCap: 0 
     134            ## 
     135     
     136            try: 
     137                props["strokeWidth"] = self.getPt(eval(object["strokeWidth"])) 
     138            except KeyError:  
     139                props["strokeWidth"] = self.default_strokeWidth 
     140     
     141            try: 
     142                props["fillColor"] = eval(object["fillColor"]) 
     143            except KeyError: 
     144                props["fillColor"] = self.default_fillColor 
     145     
     146            try: 
     147                props["strokeColor"] = eval(object["strokeColor"]) 
     148            except KeyError: 
     149                props["strokeColor"] = self.default_strokeColor 
     150     
     151            try: 
     152                props["strokeDashArray"] = eval(object["strokeDashArray"]) 
     153            except KeyError: 
     154                props["strokeDashArray"] = self.default_strokeDashArray 
     155     
     156            r = shapes.Rect(0, 0, width, height) 
     157            r.setProperties(props) 
     158            d.add(r) 
     159            d.drawOn(c, x, y) 
     160     
     161        elif object["type"] == "string": 
     162            c.translate(x, y) 
     163            c.rotate(rotation) 
     164            try:  
     165                borderWidth = self.getPt(eval(object["borderWidth"])) 
     166            except KeyError:  
     167                borderWidth = self.default_borderWidth 
     168     
     169            try:  
     170                borderColor = eval(object["borderColor"]) 
     171            except KeyError:  
     172                borderColor = self.default_borderColor 
     173     
     174            c.setLineWidth(borderWidth) 
     175            c.setStrokeColor(borderColor) 
     176     
     177            if borderWidth > 0: 
     178                stroke = 1 
     179            else: 
     180                stroke = 0 
     181     
    210182            # clip the text to the specified width and height 
    211183            p = c.beginPath() 
     184     
     185            p.rect(0, 0, width, height) 
     186            c.clipPath(p, stroke=stroke) 
     187     
     188            try: 
     189                align = eval(object["align"]) 
     190            except KeyError: 
     191                align = self.default_align 
     192 
     193            funcs = {"center": c.drawCentredString, 
     194                  "right": c.drawRightString, 
     195                  "left": c.drawString} 
     196            func = funcs[align] 
    212197            if eval(object["align"]) == "center": 
    213198                posx = (width / 2) 
     
    216201            else: 
    217202                posx = 0 
    218  
    219             p.rect(posx, 0, width, height) 
     203     
     204            try: 
     205                fontName = eval(object["fontName"]) 
     206            except KeyError: 
     207                fontName = self.default_fontName 
     208     
     209            try: 
     210                fontSize = eval(object["fontSize"]) 
     211            except KeyError: 
     212                fontSize = self.default_fontSize 
     213     
     214            try: 
     215                fontColor = eval(object["fontColor"]) 
     216            except KeyError: 
     217                fontColor = self.default_fontColor 
     218     
     219            c.setFillColor(fontColor) 
     220            c.setFont(fontName, fontSize) 
     221     
     222            s = eval(object["expr"]) 
     223            func(posx,0,s) 
     224     
     225        elif object["type"] == "image": 
     226            c.translate(x, y) 
     227            c.rotate(rotation) 
     228            try:  
     229                borderWidth = self.getPt(eval(object["borderWidth"])) 
     230            except KeyError:  
     231                borderWidth = self.default_borderWidth 
     232     
     233            try:  
     234                borderColor = eval(object["borderColor"]) 
     235            except KeyError:  
     236                borderColor = self.default_borderColor 
     237     
     238            c.setLineWidth(borderWidth) 
     239            c.setStrokeColor(borderColor) 
     240     
     241            if borderWidth > 0: 
     242                stroke = 1 
     243            else: 
     244                stroke = 0 
     245     
     246            try:  
     247                mask = eval(object["imageMask"]) 
     248            except:  
     249                mask = self.default_imageMask 
     250     
     251            try: 
     252                mode = eval(object["scaleMode"]) 
     253            except: 
     254                mode = self.default_scaleMode 
     255     
     256            # clip around the outside of the image: 
     257            p = c.beginPath() 
     258            p.rect(-1, -1, width+2, height+2) 
    220259            c.clipPath(p, stroke=stroke) 
    221  
    222         funcs = {"center": c.drawCentredString, 
    223                  "right": c.drawRightString, 
    224                  "left": c.drawString} 
    225         func = funcs[eval(object["align"])] 
    226  
    227         try: 
    228             fontName = eval(object["fontName"]) 
     260     
     261            if mode == "clip": 
     262                # Need to set w,h to None for the drawImage, which will draw it in its 
     263                # "natural" state 1:1 pixel:point, which could flow out of the object's 
     264                # width/height, resulting in clipping. 
     265                width, height = None, None 
     266            c.drawImage(eval(object["expr"]), 0, 0, width, height, mask) 
     267        c.restoreState() 
     268 
     269 
     270    def getPt(self, val): 
     271        """Given a string or a number, convert the value into a numeric pt value. 
     272     
     273        Strings can have the unit appended, like "3.5 in", "2 cm", "3 pica", "10 mm". 
     274     
     275        > print self.getPt("1 in") 
     276        72 
     277        > print self.getPt("1") 
     278        1 
     279        > print self.getPt(1) 
     280        1 
     281        """ 
     282        if type(val) in (int, long, float): 
     283            # return as-is as the pt value. 
     284            return val 
     285        else: 
     286            # try to run it through reportlab's units.toLength() function: 
     287            return units.toLength(val) 
     288     
     289     
     290    def printBandOutline(self, band, x, y, width, height): 
     291            ## draw a dotted rectangle around the entire band, and type a small faded  
     292            ## caption at the origin of the band. 
     293            c = self.Canvas 
     294            c.saveState() 
     295            c.setLineWidth(0.1) 
     296            c.setStrokeColorRGB(0.8, 0.5, 0.7) 
     297            c.setDash(1, 2) 
     298            c.rect(x, y, width, height) 
     299            c.setFont("Helvetica", 8) 
     300            c.setFillColor((0.6, 0.8, 0.7)) 
     301            c.drawString(x, y, band) 
     302            c.restoreState() 
     303         
     304         
     305    def write(self):             
     306        _form = self.ReportForm 
     307        if _form is None: 
     308            raise ValueError, "ReportForm must be set first." 
     309 
     310        _outputName = self.OutputName 
     311        if _outputName is None: 
     312            raise ValueError, "OutputName must be set first." 
     313 
     314         
     315        ## Set the Page Size: 
     316        # get the string pageSize value from the spec file: 
     317        try: 
     318            pageSize = eval(_form.Page["size"]) 
    229319        except KeyError: 
    230             fontName = "Helvetica" 
    231  
    232         try: 
    233             fontSize = eval(object["fontSize"]) 
     320            pageSize = self.default_pageSize 
     321        # reportlab expects the pageSize to be upper case: 
     322        pageSize = pageSize.upper() 
     323        # convert to the reportlab pageSize value (tuple(width,height)): 
     324        pageSize = eval("pagesizes.%s" % pageSize) 
     325        # run it through the portrait/landscape filter: 
     326        try: 
     327            orientation = eval(_form.Page["orientation"]) 
    234328        except KeyError: 
    235             fontSize = 10 
    236  
    237         try: 
    238             fillColor = eval(object["fillColor"]) 
    239         except KeyError: 
    240             fillColor = (0, 0, 0) 
    241  
    242         c.setFillColor(fillColor) 
    243         c.setFont(fontName, fontSize) 
    244  
    245         func(0,0,eval(object["expr"])) 
    246  
    247     elif object["type"] == "image": 
    248         c.translate(x, y) 
    249         c.rotate(rotation) 
    250         try:  
    251             borderWidth = getPt(eval(object["borderWidth"])) 
    252         except KeyError:  
    253             borderWidth = 0 
    254  
    255         try:  
    256             borderColor = eval(object["borderColor"]) 
    257         except KeyError:  
    258             borderColor = (0, 0, 0) 
    259  
    260         c.setLineWidth(borderWidth) 
    261         c.setStrokeColor(borderColor) 
    262  
    263         if borderWidth > 0: 
    264             stroke = 1 
     329            orientation = self.default_pageOrientation 
     330        func = eval("pagesizes.%s" % orientation) 
     331        pageSize = func(pageSize) 
     332        pageWidth, pageHeight = pageSize 
     333        print "Page Size:", pageSize 
     334        ## end Page Size setting (refactor to a separate function) 
     335         
     336         
     337        # Create the reportlab canvas: 
     338        c = self._canvas = canvas.Canvas(_outputName, pagesize=pageSize) 
     339         
     340         
     341        # Get the page margins into variables: 
     342        ml = self.getPt(eval(_form.Page["marginLeft"])) 
     343        mt = self.getPt(eval(_form.Page["marginTop"])) 
     344        mr = self.getPt(eval(_form.Page["marginRight"])) 
     345        mb = self.getPt(eval(_form.Page["marginBottom"])) 
     346         
     347        # Page header/footer origins are needed in various places: 
     348        pageHeaderOrigin = (ml, pageHeight - mt  
     349                            - self.getPt(eval(_form.PageHeader["height"]))) 
     350        pageFooterOrigin = (ml, mb) 
     351         
     352         
     353         
     354         
     355        # Print the static bands: 
     356        for band in ("PageBackground", "PageHeader", "PageFooter"): 
     357            self.Bands[band] = {} 
     358            bandDict = eval("_form.%s" % band) 
     359         
     360            # Find out geometry of the band and fill into report["bands"][band] 
     361            x = ml 
     362            if band == "PageHeader": 
     363                y = pageHeaderOrigin[1] 
     364            elif band == "PageFooter": 
     365                y = pageFooterOrigin[1] 
     366            elif band == "PageBackground": 
     367                x,y = 0,1 
     368             
     369            if band == "PageBackground": 
     370                width, height = pageWidth-1, pageHeight-1 
     371            else: 
     372                width = pageWidth - ml - mr 
     373                height = self.getPt(eval(bandDict["height"])) 
     374         
     375            self.Bands[band]["x"] = x 
     376            self.Bands[band]["y"] = y 
     377            self.Bands[band]["width"] = width 
     378            self.Bands[band]["height"] = height 
     379         
     380            if self.ShowBandOutlines: 
     381                self.printBandOutline(band, x, y, width, height) 
     382         
     383            for object in bandDict["objects"]: 
     384         
     385                if bandDict == _form.PageHeader: 
     386                    origin = pageHeaderOrigin 
     387                elif bandDict == _form.PageFooter: 
     388                    origin = pageFooterOrigin 
     389                elif bandDict == _form.PageBackground: 
     390                    origin = (0,1) 
     391         
     392                x = self.getPt(eval(object["x"])) + origin[0] 
     393                y = origin[1] + self.getPt(eval(object["y"])) 
     394         
     395                self.draw(object, (x, y)) 
     396         
     397        # Print the dynamic bands (Detail, GroupHeader, GroupFooter): 
     398        y = pageHeaderOrigin[1] 
     399        groups = _form.Groups 
     400         
     401        self._recordNumber = 0 
     402        for record in self.Cursor: 
     403            self.Record = record 
     404            for band in ("Detail",): 
     405                bandDict = eval("_form.%s" % band) 
     406                self.Bands[band] = {} 
     407         
     408                x = ml 
     409                y = y - self.getPt(eval(bandDict["height"])) 
     410                width = pageWidth - ml - mr 
     411                height = self.getPt(eval(bandDict["height"])) 
     412         
     413                self.Bands[band]["x"] = x 
     414                self.Bands[band]["y"] = y 
     415                self.Bands[band]["width"] = width 
     416                self.Bands[band]["height"] = height 
     417         
     418                if self.ShowBandOutlines: 
     419                    self.printBandOutline("%s (record %s)" % (band, self.RecordNumber),  
     420                                                         x, y, width, height) 
     421                for object in bandDict["objects"]: 
     422                    x = ml + self.getPt(eval(object["x"])) 
     423                    y1 = y + self.getPt(eval(object["y"])) 
     424                    self.draw(object, (x, y1)) 
     425                         
     426                self._recordNumber += 1 
     427         
     428        c.save() 
     429 
     430 
     431 
     432    def _setFormFromXML(): 
     433        ## Called from _setReportFormFile() and _setReportFormXML(). 
     434        ## Interprets the xml in ReportFormXML into a Python module, and sets 
     435        ## ReportForm to that module. 
     436        pass 
     437 
     438 
     439    def _getBands(self): 
     440        try: 
     441            v = self._bands 
     442        except AttributeError: 
     443            v = self._bands = {} 
     444        return v 
     445 
     446    Bands = property(_getBands, None, None, 
     447        """Provides runtime access to bands of the currently running report.""") 
     448 
     449 
     450    def _getCanvas(self): 
     451        try: 
     452            v = self._canvas 
     453        except AttributeError: 
     454            v = self._canvas = None 
     455        return v 
     456 
     457    Canvas = property(_getCanvas, None, None, 
     458        """Returns a reference to the reportlab canvas object.""")   
     459 
     460 
     461    def _getCursor(self): 
     462        if self.UseTestCursor: 
     463            try: 
     464                v = self.ReportForm.TestCursor 
     465            except AttributeError: 
     466                v = [] 
    265467        else: 
    266             stroke = 0 
    267  
    268         try:  
    269             mask = eval(object["mask"]) 
    270         except:  
    271             mask = None 
    272  
    273         try: 
    274             mode = eval(object["mode"]) 
    275         except: 
    276             mode = "scale" 
    277  
    278         # clip around the outside of the image: 
    279         p = c.beginPath() 
    280         p.rect(-1, -1, width+2, height+2) 
    281         c.clipPath(p, stroke=stroke) 
    282  
    283         if mode == "clip": 
    284             # Need to set w,h to None for the drawImage, which will draw it in its 
    285             # "natural" state 1:1 pixel:point, which could flow out of the object's 
    286             # width/height, resulting in clipping. 
    287             width, height = None, None 
    288         c.drawImage(eval(object["expr"]), 0, 0, width, height, mask) 
    289     c.restoreState() 
    290  
    291  
    292 # Print the static bands (Page Header/Footer, Watermark): 
    293 for band in ("PageBackground", "PageHeader", "PageFooter"): 
    294     report["bands"][band] = {} 
    295     bandDict = eval("form.%s" % band) 
    296  
    297     # Find out geometry of the band and fill into report["bands"][band] 
    298     x = ml 
    299     if band == "PageHeader": 
    300         y = pageHeaderOrigin[1] 
    301     elif band == "PageFooter": 
    302         y = pageFooterOrigin[1] 
    303     elif band == "PageBackground": 
    304         x,y = 0,1 
    305      
    306     if band == "PageBackground": 
    307         width, height = pageWidth-1, pageHeight-1 
    308     else: 
    309         width = pageWidth - ml - mr 
    310         height = getPt(eval(bandDict["height"])) 
    311  
    312     report["bands"][band]["x"] = x 
    313     report["bands"][band]["y"] = y 
    314     report["bands"][band]["width"] = width 
    315     report["bands"][band]["height"] = height 
    316  
    317     if showBands: 
    318         printBandOutline(band, x, y, width, height) 
    319  
    320     for object in bandDict["objects"]: 
    321  
    322         if bandDict == form.PageHeader: 
    323             origin = pageHeaderOrigin 
    324         elif bandDict == form.PageFooter: 
    325             origin = pageFooterOrigin 
    326         elif bandDict == form.PageBackground: 
    327             origin = (0,1) 
    328  
    329         x = getPt(eval(object["x"])) + origin[0] 
    330         y = origin[1] + getPt(eval(object["y"])) 
    331  
    332         draw(object, x, y) 
    333  
    334 # Print the dynamic bands (Detail, GroupHeader, GroupFooter): 
    335 y = pageHeaderOrigin[1] 
    336 groups = form.Groups 
    337  
    338 recno = 0 
    339 for record in data: 
    340     for band in ("Detail",): 
    341         bandDict = eval("form.%s" % band) 
    342         report["bands"][band] = {} 
    343  
    344         x = ml 
    345         y = y - getPt(eval(bandDict["height"])) 
    346         width = pageWidth - ml - mr 
    347         height = getPt(eval(bandDict["height"])) 
    348  
    349         report["bands"][band]["x"] = x 
    350         report["bands"][band]["y"] = y 
    351         report["bands"][band]["width"] = width 
    352         report["bands"][band]["height"] = height 
    353  
    354         if showBands: 
    355             printBandOutline("%s (record %s)" % (band, recno), x, y, width, height) 
    356         for object in bandDict["objects"]: 
    357             x = ml + getPt(eval(object["x"])) 
    358             y1 = y + getPt(eval(object["y"])) 
    359             draw(object, x, y1) 
    360                  
    361         recno += 1 
    362  
    363 c.save() 
    364  
     468            try: 
     469                v = self._cursor 
     470            except AttributeError: 
     471                v = self._cursor = None 
     472        return v 
     473 
     474    def _setCursor(self, val): 
     475        self._cursor = val 
     476        self.UseTestCursor = False 
     477         
     478    Cursor = property(_getCursor, _setCursor, None,  
     479        """Specifies the data cursor that the report runs against.""") 
     480 
     481 
     482    def _getOutputName(self): 
     483        try: 
     484            v = self._outputName 
     485        except AttributeError: 
     486            v = self._outputName = None 
     487        return v 
     488         
     489    def _setOutputName(self, val): 
     490        s = os.path.split(val) 
     491        if os.path.exists(s[0]): 
     492            self._outputName = val 
     493        else: 
     494            raise ValueError, "Path '%s' doesn't exist." % s[0] 
     495 
     496    OutputName = property(_getOutputName, _setOutputName, None, 
     497        """Specifies the output file name, which will get written to in PDF format.""") 
     498 
     499 
     500    def _getRecord(self): 
     501        try: 
     502            v = self._record 
     503        except AttributeError: 
     504            v = self._record = {} 
     505        return v 
     506 
     507    def _setRecord(self, val): 
     508        self._record = val 
     509 
     510    Record = property(_getRecord, _setRecord, None, 
     511        """Specifies the dictionary that represents the current record. 
     512 
     513        The report writer will automatically fill this in during the running  
     514        of the report. Allows expressions in the report form like: 
     515 
     516            self.Record['cFirst'] 
     517        """) 
     518 
     519 
     520    def _getRecordNumber(self): 
     521        try: 
     522            v = self._recordNumber 
     523        except AttributeError: 
     524            v = self._recordNumber = None 
     525        return v 
     526 
     527    RecordNumber = property(_getRecordNumber, None, None, 
     528        """Returns the current record number of Cursor.""") 
     529 
     530 
     531    def _getReportForm(self): 
     532        try: 
     533            v = self._reportForm 
     534        except AttributeError: 
     535            v = self._reportForm = None 
     536        return v 
     537         
     538    def _setReportForm(self, val): 
     539        self._reportForm = val 
     540        self._reportFormXML = None 
     541        self._reportFormFile = None 
     542         
     543    ReportForm = property(_getReportForm, _setReportForm, None, 
     544        """Specifies the python report form as a Python module.""") 
     545     
     546 
     547    def _getReportFormFile(self): 
     548        try: 
     549            v = self._reportFormFile 
     550        except AttributeError: 
     551            v = self._reportFormFile = None 
     552        return v 
     553         
     554    def _setReportFormFile(self, val): 
     555        if os.path.exists(val): 
     556            ext = os.path.splitext(val)[1]  
     557            if ext == ".py": 
     558                # The file is a python module, import it: 
     559                s = os.path.split(val) 
     560                sys.path.append(s[0]) 
     561                exec("import %s as form" % s[1]) 
     562                sys.path.pop() 
     563                self._reportForm = form 
     564                self._reportFormXML = None 
     565                     
     566            elif ext == ".rfxml": 
     567                # The file is a report form xml file. Open it and set ReportFormXML: 
     568                self._reportFormXML = open(val, "r").read() 
     569                self._setFormFromXML() 
     570            else: 
     571                raise ValueError, "Invalid file type." 
     572            self._reportFormFile = val 
     573        else: 
     574                raise ValueError, "Specified file does not exist." 
     575         
     576    ReportFormFile = property(_getReportFormFile, _setReportFormFile, None, 
     577        """Specifies the path and filename of the report form spec file.""") 
     578         
     579 
     580    def _getReportFormXML(self): 
     581        try: 
     582            v = self._reportFormXML 
     583        except AttributeError: 
     584            v = self._reportFormXML = None 
     585        return v 
     586         
     587    def _setReportFormXML(self, val): 
     588        self._reportFormXML = val 
     589        self._reportFormFile = None 
     590        self._setFormFromXML() 
     591         
     592    ReportFormXML = property(_getReportFormXML, _setReportFormXML, None, 
     593        """Specifies the report format xml.""") 
     594 
     595 
     596    def _getShowBandOutlines(self): 
     597        try: 
     598            v = self._showBandOutlines 
     599        except AttributeError: 
     600            v = False 
     601        return v 
     602 
     603    def _setShowBandOutlines(self, val): 
     604        self._showBandOutlines = bool(val) 
     605 
     606    ShowBandOutlines = property(_getShowBandOutlines, _setShowBandOutlines, None, 
     607        """Specifies whether the report bands are printed with outlines for 
     608        debugging and informational purposes. In addition to the band, there is also 
     609        a caption with the band name at the x,y origin point for the band.""") 
     610         
     611 
     612    def _getUseTestCursor(self): 
     613        try: 
     614            v = self._useTestCursor 
     615        except AttributeError: 
     616            v = self._useTestCursor = False 
     617        return v 
     618 
     619    def _setUseTestCursor(self, val): 
     620        self._useTestCursor = bool(val) 
     621        if val: 
     622            self._cursor = None 
     623 
     624    UseTestCursor = property(_getUseTestCursor, _setUseTestCursor, None,  
     625        """Specifies whether the TestCursor in the spec file is used.""") 
     626             
     627             
     628if __name__ == "__main__": 
     629    rw = ReportWriter() 
     630    import samplespec 
     631    rw.ReportForm = samplespec 
     632    rw.OutputName = "./test.pdf" 
     633    #rw.Cursor = [{"cArtist": "Cornershop"}] 
     634    rw.ShowBandOutlines = True 
     635    rw.UseTestCursor = True 
     636    rw.write() 
  • trunk/reporting/samplespec.py

    r806 r812  
    77 
    88Most values are expressions to be evaluated at runtime (ie they are dynamic). 
     9 
     10'self' in expressions refer to the ReportWriter instance, which by default  
     11supplies the following: 
     12    Properties: 
     13        + Cursor: the data cursor running against this report form. 
     14        + Record: the dictionary of the current record in the data cursor. 
    915""" 
     16 
     17# If TestCursor is provided by the spec, the report writer will use it  
     18# as the Cursor if dReportWriter.UseTestCursor==True. This allows for easy  
     19# previewing in the designer without having to set up/tear down actual data  
     20# when all you want is to see how the report will look. 
     21TestCursor = [{"cArtist": "The Clash", "iid": 1}, 
     22              {"cArtist": "Queen", "iid": 2}, 
     23              {"cArtist": "Ben Harper and the Innocent Criminals", "iid":3}] 
    1024 
    1125Page = {"size": ''' "letter" ''', 
     
    1933 
    2034PageBackground = {"objects": [{"type": "string", 
    21                            "expr": ''' "Test" ''', 
     35                           "expr": ''' "test" ''', 
    2236                           "align": ''' "left" ''', 
    2337                           "rotation": ''' 55 ''', 
     
    2640                           "width": ''' "1 in" ''', 
    2741                           "fontName": ''' "Helvetica" ''', 
    28                            "fontSize": ''' 20 ''', 
     42                           "fontSize": ''' 7 ''', 
    2943                           "fillColor": ''' (.4, .1, .3) ''', 
    3044                           "borderWidth": ''' ".5 pt" ''', 
     
    4963                           "align": ''' "center" ''', 
    5064                           "x": ''' "3.75 in" ''', 
    51                            "y": ''' "0.3 in" ''', 
     65                           "hAnchor": ''' "center" ''', 
     66                           "y": ''' ".3 in" ''', 
    5267                           "width": ''' "6 in" ''', 
    5368                           "height": ''' ".25 in" ''', 
    5469                           "borderWidth": ''' "0 pt" ''', 
    55                            "fontFace": ''' "Helvetica" ''', 
     70                           "fontName": ''' "Helvetica" ''', 
    5671                           "fontSize": ''' 14 ''', 
    5772                          }] 
     
    6176              "objects": [{"type": "image", 
    6277                           "expr": ''' "../icons/dabo_lettering_250x100.png" ''', 
    63                            "x": ''' report["bands"]["PageFooter"]["width"]-1 ''', 
     78                           "x": ''' self.Bands["PageFooter"]["width"]-1 ''', 
    6479                           "y": ''' "1" ''', 
    6580                           "rotation": ''' 0 ''', 
     
    7792Detail = {"height": ''' ".25 in" ''', 
    7893          "objects": [{"type": "string", 
    79                        "expr": ''' record['cArtist'] ''', 
     94                       "expr": ''' self.Record['cArtist'] ''', 
    8095                       "align": ''' "left" ''', 
    8196                       "fontFace": ''' "Helvetica" ''', 
     
    96111 
    97112Groups = [{"name": "Group1", 
    98            "expr": ''' "record['cartist']" ''',} 
     113           "expr": ''' record['cartist'] ''',} 
    99114         ]