| 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 | |
|---|
| | 9 | class 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. |
|---|
| 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 | |
|---|
| 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"]) |
|---|
| 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 | |
|---|
| 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"]) |
|---|
| 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 = [] |
|---|
| 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 | |
|---|
| | 628 | if __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() |
|---|