~ubuntu-branches/ubuntu/hardy/gnue-common/hardy

« back to all changes in this revision

Viewing changes to src/printing/pdftable/pdftable.py

  • Committer: Bazaar Package Importer
  • Author(s): Andrew Mitchell
  • Date: 2005-03-09 11:06:31 UTC
  • Revision ID: james.westby@ubuntu.com-20050309110631-8gvvn39q7tjz1kj6
Tags: upstream-0.5.14
ImportĀ upstreamĀ versionĀ 0.5.14

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
#
 
2
# This file is part of GNU Enterprise.
 
3
#
 
4
# GNU Enterprise is free software; you can redistribute it
 
5
# and/or modify it under the terms of the GNU General Public
 
6
# License as published by the Free Software Foundation; either
 
7
# version 2, or (at your option) any later version.
 
8
#
 
9
# GNU Enterprise is distributed in the hope that it will be
 
10
# useful, but WITHOUT ANY WARRANTY; without even the implied
 
11
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
 
12
# PURPOSE. See the GNU General Public License for more details.
 
13
#
 
14
# You should have received a copy of the GNU General Public
 
15
# License along with program; see the file COPYING. If not,
 
16
# write to the Free Software Foundation, Inc., 59 Temple Place
 
17
# - Suite 330, Boston, MA 02111-1307, USA.
 
18
#
 
19
# Copyright 2004-2005 Free Software Foundation
 
20
#
 
21
# FILE:
 
22
# pdftable/pdftable.py
 
23
#
 
24
# DESCRIPTION:
 
25
"""
 
26
A class that creates multisection tabular pdf reports.
 
27
"""
 
28
#
 
29
 
 
30
# ============================================================================
 
31
# Imports
 
32
# ============================================================================
 
33
# ----------------------------------------------------------------------------
 
34
# Python standard modules
 
35
# ----------------------------------------------------------------------------
 
36
import time
 
37
import sys
 
38
 
 
39
# ----------------------------------------------------------------------------
 
40
# Reportlab modules
 
41
# ----------------------------------------------------------------------------
 
42
from reportlab.lib import colors
 
43
from reportlab.lib.units import inch
 
44
from reportlab.lib.pagesizes import letter, landscape, portrait
 
45
from reportlab.pdfgen import canvas
 
46
from reportlab.pdfbase.pdfmetrics import getFont
 
47
 
 
48
# ============================================================================
 
49
# Module constants
 
50
# ============================================================================
 
51
# ----------------------------------------------------------------------------
 
52
# Text alignment constants
 
53
# ----------------------------------------------------------------------------
 
54
LEFT=0
 
55
RIGHT=1
 
56
CENTER=2
 
57
 
 
58
# ----------------------------------------------------------------------------
 
59
# Some sample font definitions 
 
60
#
 
61
# TODO: This really needs handled in a more flexible mannor.
 
62
# ----------------------------------------------------------------------------
 
63
                       # Font name       Pt Size Vert Spacing
 
64
dataFont =             ('Times-Roman',      10,    11)
 
65
subtotalFont =         ('Times-Bold',       10,    12)
 
66
tableHeaderFont =      ('Times-Bold',       10,    12)
 
67
 
 
68
titleFont =            ('Helvetica-Bold',   14,    14)
 
69
title2Font =           ('Helvetica',        12,    14)
 
70
title3Font =           ('Helvetica-Oblique',12,    14)
 
71
repeatTitleFont =      ('Helvetica-Oblique', 9,    10)
 
72
footerFont =           ('Times-Roman',       9,    10)
 
73
 
 
74
subtitleFont =         ('Times-Bold',       12,    13)
 
75
subtitleLabelFont =    ('Times-Roman',      12,    13)
 
76
subtitleContinueFont = ('Times-Italic',     10,    13)
 
77
 
 
78
# ----------------------------------------------------------------------------
 
79
# Sample Color settings
 
80
#
 
81
# TODO: This really needs handled in a more flexible mannor.
 
82
# ----------------------------------------------------------------------------
 
83
highlightColor = colors.HexColor("0xffffcc")
 
84
headingColor = subtotalColor = colors.HexColor("0xe6e6ff")
 
85
 
 
86
 
 
87
# ----------------------------------------------------------------------------
 
88
# Size constants
 
89
#
 
90
# TODO: This really needs handled in a more flexible mannor.
 
91
# ----------------------------------------------------------------------------
 
92
 
 
93
lineWidth = .25
 
94
 
 
95
# NOTE: I tried not to tie internal logic to the font/color/tracking
 
96
#       values that follow, so *theoretically* you can adjust them to
 
97
#       your liking.
 
98
leftmargin = .75*inch
 
99
topmargin = .75*inch
 
100
 
 
101
# This is nothing but voodoo guesses...
 
102
# Greatly depends on pointsize of dataFont
 
103
# TODO: This should probably be computed based on width of a "0"
 
104
# TODO: and the width of the report.  But, eh, this works for now...
 
105
maxColsForPortraitNonscaled = 100   # Number of columns before we start scaling down
 
106
maxColsForPortraitScaled = 120      # Number of columns before we switch to landscape
 
107
maxColsForLandscapeNonscaled = 140  # Number of columns before landscape + scaling
 
108
 
 
109
# ============================================================================
 
110
# Class definition
 
111
# ============================================================================
 
112
class pdftable:
 
113
  
 
114
  # ==========================================================================
 
115
  # Creation/closure functions
 
116
  # ==========================================================================
 
117
  
 
118
  # --------------------------------------------------------------------------
 
119
  # Initialize
 
120
  # --------------------------------------------------------------------------
 
121
  def __init__(self, file, parameterBox = ()):    
 
122
    """
 
123
    A simple table based report generator.
 
124
    
 
125
    The class provides the following features.
 
126
      Border support for columns, rows.
 
127
      Automatic adjustments of the data when it is unable to fit on one page.
 
128
      Multiple sections of a report handled automatically.
 
129
    
 
130
    @param file: A python file handle used for output
 
131
    @param parameterBox: Unused?
 
132
    """
 
133
    self._titleList = ['Your Report','Your Report']
 
134
    
 
135
    self.file = file
 
136
#     self.parameterBox = parameterBox
 
137
 
 
138
    self.highlightIndex = 0
 
139
    self.init = 1
 
140
    self._scalingComplete = 0
 
141
    self.page = 0
 
142
    self.continuing = 0
 
143
 
 
144
    # Time of report session
 
145
    self.timestamp = time.strftime("%m/%d/%Y %I:%M%p", time.localtime())
 
146
 
 
147
    # Will be set by _beginData()
 
148
    self.height = 0
 
149
    self.width = 0
 
150
    self.scale = 1.0
 
151
 
 
152
    self._currentSectionType = "default"
 
153
    self._currentSection = {}
 
154
    self.definedSectionTypes = {}
 
155
    
 
156
    self.columnGap = 6 # in pts, Amount of gap to leave between columns... doesn't need to be too much
 
157
 
 
158
  # --------------------------------------------------------------------------
 
159
  # Finish up the output
 
160
  # --------------------------------------------------------------------------
 
161
  def close(self):
 
162
    """
 
163
    Finalize the report.
 
164
    
 
165
    Should be called after all data was sent to the report.
 
166
    """
 
167
    self.canvas.showPage()
 
168
    self.canvas.save()
 
169
 
 
170
    
 
171
  # ==========================================================================
 
172
  # Functions that effect report wide settings
 
173
  # ==========================================================================
 
174
  
 
175
  # --------------------------------------------------------------------------
 
176
  # Sets the title of the report
 
177
  # --------------------------------------------------------------------------    
 
178
  def setFullTitle(self,titleList):
 
179
    """
 
180
    Sets the title of the report
 
181
    
 
182
    @param titleList: A list containing tuples of the following format
 
183
                      (text, font defintion)
 
184
                      Font defintions are also tuples in the format
 
185
                      (Font name, Pt Size, Vert Spacing) such as...
 
186
                      ('Times-Roman',      10,    11)
 
187
    """
 
188
    self._titleList = titleList
 
189
  
 
190
  # --------------------------------------------------------------------------
 
191
  # Define a column on the report
 
192
  # --------------------------------------------------------------------------
 
193
  def addColumn(self, align, minSize, overflow="", highlight=None, 
 
194
                leftBorder=0, rightBorder=0, sectionType="default"):    
 
195
    """
 
196
    Defines a column on the record for the specified section
 
197
    
 
198
    @param minSize: The minimum size in points of the column.
 
199
    @param overflow: The text that should be printed if the contents of a column are too 
 
200
                     large for the column width.
 
201
    @param highlight: The color of background to use in this column.
 
202
    @param leftBorder: The width of the left side border in points.
 
203
    @param rightBorder: The width of the right side border in points.   
 
204
    @param sectionType: The name of the section to which this column should be added.
 
205
    """
 
206
    try:
 
207
      secType = self.definedSectionTypes[sectionType]
 
208
    except KeyError:
 
209
      self.definedSectionTypes[sectionType] = {'columnSizes'      :[],
 
210
                                       'columnAligns'     :[],
 
211
                                       'columnOverflow'   :[],
 
212
                                       'columnHighlight'  :[],
 
213
                                       'columnCoords'     :[],
 
214
                                       'columnLeftBorder' :[],
 
215
                                       'columnRightBorder':[],
 
216
                                       'headerList':    [[]],                                    
 
217
                                    }
 
218
      secType = self.definedSectionTypes[sectionType]
 
219
      
 
220
    secType['columnSizes'].append(minSize)
 
221
    secType['columnAligns'].append(align)
 
222
    secType['columnOverflow'].append(overflow)
 
223
    secType['columnHighlight'].append(highlight)
 
224
    secType['columnLeftBorder'].append(leftBorder)
 
225
    secType['columnRightBorder'].append(rightBorder)
 
226
 
 
227
  # ==========================================================================
 
228
  # Section level functions
 
229
  # ==========================================================================
 
230
  
 
231
  # --------------------------------------------------------------------------
 
232
  # Define a section header
 
233
  # --------------------------------------------------------------------------
 
234
  def addHeader(self, heading, align, startColumn, endColumn, 
 
235
                leftBorder=0, rightBorder=0, sectionType="default"):
 
236
    """
 
237
    Adds a column header to one or more columns
 
238
    
 
239
    @param heading: The text to display
 
240
    @param align:   The alignment to apply to the header text 
 
241
                    LEFT, RIGHT, CENTER are defined in this module
 
242
    @param startColumn: The starting column number (starts at 0) for the header
 
243
    @param endColumn: The ending column number for the header
 
244
    @param leftBorder: The width of the left side border in points.
 
245
    @param rightBorder: The width of the right side border in points.   
 
246
    @param sectionType: The name of the section to which this header should be added.    
 
247
    """
 
248
    secType = self.definedSectionTypes[sectionType]
 
249
    if endColumn > len(secType['columnSizes'])-1:
 
250
      print "endColumn longer than defined columns"
 
251
      sys.exit()
 
252
          
 
253
    heading = {'text':heading,
 
254
               'align':align,
 
255
               'start':startColumn,
 
256
               'end':endColumn,
 
257
               'leftBorder':leftBorder,
 
258
               'rightBorder':rightBorder,
 
259
               }
 
260
    secType['headerList'][-1].append(heading)
 
261
  
 
262
  # --------------------------------------------------------------------------
 
263
  # Add a row to a header
 
264
  # --------------------------------------------------------------------------  
 
265
  def addHeaderRow(self, sectionType="default"):
 
266
    """
 
267
    Adds a new row to the header.  Subsequent calls to addHeader will now
 
268
    apply to this new row.
 
269
    
 
270
    @param sectionType: The name of the section to which this header row will be added.    
 
271
    """
 
272
    secType = self.definedSectionTypes[sectionType]
 
273
    secType['headerList'].append([])
 
274
    
 
275
  # --------------------------------------------------------------------------
 
276
  # Inform the writer to switch to a new section
 
277
  # --------------------------------------------------------------------------
 
278
  def startNewSection(self, subtitle, sectionType="default", newPage=1):
 
279
    """
 
280
    Begins a new report section.
 
281
  
 
282
    @param subtitle: The subtitle to display above the section
 
283
    @param sectionType: The name of the previous defined section to use.
 
284
    @param newPage: If 0 then the new page will be supressed.
 
285
                    If 1 (the default) then a new page will be output prior
 
286
                    to starting the section.
 
287
    """
 
288
    if sectionType != self._currentSectionType:
 
289
      self.init = 1
 
290
    self._currentSection = self.definedSectionTypes[sectionType]   
 
291
    self._currentSectionType = sectionType    
 
292
    self.subtitle = subtitle
 
293
    if newPage:
 
294
      self.highlightIndex = 0 
 
295
    if self.init:
 
296
      self.init = 0
 
297
      self.beginData()
 
298
    self.continued = 0
 
299
    if newPage:
 
300
      self.newPage()
 
301
    else:
 
302
      self.drawSectionHeading()
 
303
      self.drawTableHeader()
 
304
 
 
305
  # ==========================================================================
 
306
  # Data functions 
 
307
  # ==========================================================================
 
308
  
 
309
  # --------------------------------------------------------------------------
 
310
  # Add data row
 
311
  # --------------------------------------------------------------------------
 
312
  def addRow(self, data, style="Data"):
 
313
    """
 
314
    Adds a row of data to the current section
 
315
    
 
316
    @param data: A list of strings containing the data to add to the current section
 
317
    @param style: The format style to use to render the row.
 
318
                  These are currently hardcoded into this class and include
 
319
                  Data (default), Subtotal, Total
 
320
    """
 
321
    canvas = self.canvas
 
322
 
 
323
    if style == "Total":
 
324
      self.y -= 4 * self.scale
 
325
 
 
326
    if style in ("Subtotal","Total"):
 
327
      font, size, tracking = subtotalFont
 
328
      fontWidth = self.subtotalFontWidth
 
329
    else:
 
330
      font, size, tracking = dataFont
 
331
      fontWidth = self.dataFontWidth
 
332
 
 
333
    size = size * self.scale
 
334
    tracking = tracking * self.scale
 
335
 
 
336
    if self.y - tracking < self.miny:
 
337
       self.newPage()
 
338
 
 
339
    highlighted = 0
 
340
    if style == "Data":
 
341
      highlighted = divmod((self.highlightIndex),4)[1]>1
 
342
      self.highlightIndex += 1
 
343
    else:
 
344
      self.highlightIndex = 0  # Reset highlighting after subtotals
 
345
 
 
346
    boxy = self.y - (tracking-size)*1.5
 
347
    boxh = tracking
 
348
 
 
349
    if style in ("Subtotal","Total"):
 
350
      self.drawHorizBorderedBox(leftmargin, boxy, self.width-leftmargin*2,
 
351
                                boxh, subtotalColor, lines=lineWidth * self.scale)
 
352
    elif highlighted:
 
353
      self.drawHorizBorderedBox(leftmargin, boxy, self.width-leftmargin*2,
 
354
                                boxh, highlightColor, lines=0)
 
355
 
 
356
    canvas.setFont(font, size)
 
357
 
 
358
    i = 0
 
359
    while i < len(data):
 
360
      col = data[i]
 
361
      # Find the hlx values (used for column highlight and borders)   
 
362
      if i > 0:
 
363
        hlx1 = self._currentSection['columnCoords'][i-1][1]+self.columnGap/2.0
 
364
      else:
 
365
        hlx1 = leftmargin
 
366
      
 
367
      if i < len(self._currentSection['columnCoords'])-1:
 
368
        hlx2 = self._currentSection['columnCoords'][i+1][0]-self.columnGap/2.0
 
369
      else:
 
370
        hlx2 = self.width-leftmargin*2
 
371
      
 
372
      # Column highlight support          
 
373
      highlightColumn = self._currentSection['columnHighlight'][i]
 
374
      if highlightColumn and not style in ("Subtotal","Total"):
 
375
        if self.highlightIndex==1: # We're on the first column (not 0 due to += above)
 
376
          adjust = lineWidth#*self.scale
 
377
        else:
 
378
          adjust = 0
 
379
        if highlighted:
 
380
          color = colors.Blacker(highlightColor,.98)
 
381
        else:
 
382
          color = highlightColumn
 
383
          
 
384
        self.drawHorizBorderedBox(hlx1, boxy - adjust, hlx2-hlx1, boxh, 
 
385
                                  color, lines=0)
 
386
      
 
387
      # Column border support
 
388
      leftBorder = self._currentSection['columnLeftBorder'][i]
 
389
      
 
390
      if leftBorder:
 
391
        canvas.setLineWidth(leftBorder*self.scale)
 
392
        canvas.line(hlx1, boxy, hlx1, boxy + boxh)
 
393
        
 
394
      rightBorder =self._currentSection['columnRightBorder'][i]
 
395
      if rightBorder:
 
396
        canvas.setLineWidth(rightBorder*self.scale)
 
397
        canvas.line(hlx2, boxy, hlx2, boxy + boxh)
 
398
      
 
399
      if col:      
 
400
        align= self._currentSection['columnAligns'][i]
 
401
        x1, x2 = self._currentSection['columnCoords'][i]
 
402
 
 
403
        # Clip text, if needed
 
404
        restore = 0
 
405
        if fontWidth(col, size) > x2-x1:
 
406
          if self._currentSection['columnOverflow'][i]:
 
407
            col = self._currentSection['columnOverflow'][i]
 
408
          else:
 
409
            restore = 1
 
410
            canvas.saveState()
 
411
            path = canvas.beginPath()
 
412
            # Vertical is overkill, but only interested in horizontal
 
413
            path.rect(x1,self.y-tracking, x2-x1, tracking*3)
 
414
            canvas.clipPath(path, stroke=0, fill=0)
 
415
 
 
416
        if align == LEFT:
 
417
          canvas.drawString(x1,self.y,col)
 
418
        elif align == RIGHT:
 
419
          canvas.drawRightString(x2,self.y,col)
 
420
        elif align == CENTER:
 
421
          canvas.drawCentredString(x1+(x2-x1)/2.0,self.y,col)
 
422
 
 
423
        # Restore from clipping
 
424
        if restore:
 
425
          canvas.restoreState()
 
426
      i += 1
 
427
 
 
428
    self.y -= tracking
 
429
 
 
430
  # ==========================================================================
 
431
  # Private functions
 
432
  # ==========================================================================
 
433
 
 
434
  # --------------------------------------------------------------------------
 
435
  # Begin a new page
 
436
  # --------------------------------------------------------------------------
 
437
  def newPage(self):
 
438
    """
 
439
    Private function that creates a new page.
 
440
    """
 
441
    if self.page:
 
442
      self.canvas.showPage()
 
443
 
 
444
    self.page += 1
 
445
    self.y = self.height - topmargin
 
446
 
 
447
    if self.page == 1:
 
448
      self.drawLargeTitle()
 
449
    else:
 
450
      self.drawSmallTitle()
 
451
 
 
452
    self.drawTableHeader()
 
453
    self.footer()
 
454
 
 
455
    self.continued = 1
 
456
 
 
457
 
 
458
  # --------------------------------------------------------------------------
 
459
  # Draw footer
 
460
  # --------------------------------------------------------------------------
 
461
  def footer(self):
 
462
    """
 
463
    Private function that creates the footer containing the time/page #
 
464
    """
 
465
    canvas = self.canvas
 
466
    font, size, tracking = footerFont
 
467
    canvas.setFont(font, size)
 
468
    canvas.drawString(leftmargin, topmargin, self.timestamp)
 
469
    canvas.drawRightString(self.width - leftmargin, topmargin, "Page %s" % self.page)
 
470
    self.miny = topmargin + tracking*2
 
471
 
 
472
  # --------------------------------------------------------------------------
 
473
  # Draw full (first-page) header (Title)
 
474
  # --------------------------------------------------------------------------
 
475
  def drawLargeTitle(self):
 
476
    """
 
477
    Private function that creates a full (first page) header on a new page.
 
478
    """
 
479
    canvas = self.canvas
 
480
    self.y -= titleFont[2]
 
481
    for text, fontspec in self._titleList:
 
482
      if text:
 
483
        font, size, tracking = fontspec
 
484
        canvas.setFont(font, size)
 
485
        canvas.drawCentredString(self.width/2.0, self.y, text)
 
486
        self.y -= tracking
 
487
    self.drawSectionHeading()
 
488
 
 
489
  # --------------------------------------------------------------------------
 
490
  # Draw short (non-first-page) header (Title)
 
491
  # --------------------------------------------------------------------------
 
492
  def drawSmallTitle(self):
 
493
    """
 
494
    Private function that creates a short ( non first page) header on a new page.
 
495
    """
 
496
    canvas = self.canvas
 
497
    font, size, tracking = repeatTitleFont
 
498
    self. y -= size
 
499
    canvas.setFont(font, size)
 
500
    canvas.drawString(leftmargin, self.y, self._titleList[0][0])
 
501
    canvas.drawRightString(self.width - leftmargin, self.y, self._titleList[1][0])
 
502
    self.y -= tracking
 
503
    self.drawSectionHeading()
 
504
    
 
505
  # --------------------------------------------------------------------------
 
506
  # Draw the section header
 
507
  # --------------------------------------------------------------------------
 
508
  def drawSectionHeading(self):
 
509
    """
 
510
    Draws the text that preceeds the section's table.
 
511
    """
 
512
    canvas = self.canvas
 
513
 
 
514
    if not self.subtitle:
 
515
      return
 
516
 
 
517
    self.y -= subtitleFont[2]
 
518
 
 
519
    font, size, tracking = subtitleLabelFont
 
520
 
 
521
    text = canvas.beginText(leftmargin, self.y)
 
522
    for l in self.subtitle.split():
 
523
      boldOff = 0
 
524
      if l[0] == '*':
 
525
        l = l[1:]
 
526
        font, size, tracking = subtitleFont
 
527
 
 
528
      if l[-1] == '*':
 
529
        boldOff = 1
 
530
        l = l[:-1]
 
531
 
 
532
      text.setFont(font, size)
 
533
      text.textOut(l+ ' ')
 
534
      if boldOff:
 
535
        font, size, tracking = subtitleLabelFont
 
536
        text.textOut(' ')
 
537
 
 
538
    if self.continued:
 
539
      font2, size2, tracking2 = subtitleContinueFont
 
540
      text.setFont(font2, size2)
 
541
      text.textOut("(Continued)")
 
542
 
 
543
    canvas.drawText(text)
 
544
    self.y -= tracking*2
 
545
 
 
546
 
 
547
  # --------------------------------------------------------------------------
 
548
  # Draw table header
 
549
  # --------------------------------------------------------------------------
 
550
  def drawTableHeader(self):
 
551
    """
 
552
    Generates a section's table header.
 
553
    """
 
554
    canvas = self.canvas
 
555
 
 
556
    numRows = len(self._currentSection['headerList'])
 
557
 
 
558
    font, size, tracking = tableHeaderFont
 
559
    size = size * self.scale
 
560
    tracking = tracking * self.scale
 
561
    canvas.setFont(font, size)
 
562
 
 
563
    boxy = self.y + tracking - (tracking-size)/2.0
 
564
    boxh = -tracking*numRows - (tracking-size)/2.0 - lineWidth*self.scale
 
565
    self.drawHorizBorderedBox(leftmargin, boxy, self.width-leftmargin*2,
 
566
                              boxh, headingColor)
 
567
 
 
568
    for list in self._currentSection['headerList']:
 
569
      for header in list:
 
570
        c1 = header['start']
 
571
        c2 = header['end']
 
572
        x1 = self._currentSection['columnCoords'][c1][0]
 
573
        x2 = self._currentSection['columnCoords'][c2][1]
 
574
        align = header['align']        
 
575
        text = header['text']
 
576
        
 
577
        canvas.saveState()
 
578
        path = canvas.beginPath()
 
579
        # Vertical is overkill, but only interested in horizontal
 
580
        path.rect(x1,self.y-tracking, x2-x1, tracking*3)
 
581
        canvas.clipPath(path, stroke=0, fill=0)
 
582
 
 
583
        if align == LEFT:
 
584
          canvas.drawString(x1,self.y,text)
 
585
        elif align == RIGHT:
 
586
          canvas.drawRightString(x2,self.y,text)
 
587
        elif align == CENTER:
 
588
          canvas.drawCentredString(x1+(x2-x1)/2.0,self.y,text)
 
589
        canvas.restoreState()
 
590
        
 
591
        leftBorder = header['leftBorder']
 
592
        if leftBorder:
 
593
          canvas.setLineWidth(leftBorder*self.scale)
 
594
          canvas.line(x1-self.columnGap/2.0, boxy, x1-self.columnGap/2.0, boxy + boxh)
 
595
          
 
596
        rightBorder = header['rightBorder']
 
597
        if rightBorder:
 
598
          canvas.setLineWidth(rightBorder*self.scale)
 
599
          canvas.line(x2+self.columnGap/2.0, boxy, x2+self.columnGap/2.0, boxy + boxh)
 
600
      self.y -= tracking
 
601
      
 
602
 
 
603
 
 
604
  # --------------------------------------------------------------------------
 
605
  # Draws a box w/shading and a top/bottom border
 
606
  # --------------------------------------------------------------------------
 
607
  def drawHorizBorderedBox(self, x, y, w, h, color, lines=lineWidth):
 
608
    canvas = self.canvas
 
609
    canvas.saveState()
 
610
    canvas.setFillColor(color)
 
611
    canvas.rect(x,y,w,h, stroke=0,fill=1)
 
612
    if lines:
 
613
      canvas.setLineWidth(lines)
 
614
      canvas.line(x,y,x+w,y)
 
615
      canvas.line(x,y+h,x+w,y+h)
 
616
    canvas.restoreState()
 
617
 
 
618
  # --------------------------------------------------------------------------
 
619
  # Initialize report section
 
620
  # --------------------------------------------------------------------------
 
621
  def beginData(self):
 
622
    """
 
623
    Prepares the class to begin drawing a section on the report.  Figures out 
 
624
    the required orientation of the report as well as any scaling that is required
 
625
    """
 
626
    # Calculate column sizes
 
627
    totalCols = 0
 
628
    for cs in self._currentSection['columnSizes']:
 
629
      totalCols += cs
 
630
 
 
631
    # Figure out the page orientation/scaling
 
632
    if not self._scalingComplete:
 
633
      self._scalingComplete = 1
 
634
      self.pageSize = letter
 
635
      self.scale = 1.0
 
636
      if totalCols < maxColsForPortraitNonscaled:    # Guestimate of max # cols we can get on portrait
 
637
        self.pageOrient = portrait
 
638
        self.width, self.height = letter
 
639
      elif totalCols < maxColsForPortraitScaled:
 
640
        self.pageOrient = portrait
 
641
        self.width, self.height = letter
 
642
        self.scale = maxColsForPortraitNonscaled / float(totalCols)
 
643
      elif totalCols < maxColsForLandscapeNonscaled:
 
644
        self.pageOrient = landscape
 
645
        self.height, self.width = letter
 
646
      else:
 
647
        self.pageOrient = landscape
 
648
        self.height, self.width = letter
 
649
        self.scale = maxColsForLandscapeNonscaled / float(totalCols)
 
650
  
 
651
      if self.scale < 1:
 
652
        print "Scaling to %.2f%%" % (self.scale*100)
 
653
  
 
654
      # in pts, Amount of gap to leave between columns... doesn't need to be too much
 
655
      self.columnGap = self.columnGap * self.scale
 
656
 
 
657
      self.canvas = canvas.Canvas(self.file, pagesize=self.pageOrient(self.pageSize))
 
658
  
 
659
      font, size, leading = dataFont
 
660
      self.dataFontWidth = getFont(font).stringWidth
 
661
  
 
662
      font, size, leading = subtotalFont
 
663
      self.subtotalFontWidth = getFont(font).stringWidth
 
664
      
 
665
    # This is not scaled down according to self.scale...
 
666
    # we'll instead scale the point sizes down when we do setFont
 
667
    usableHorizSpace = (self.width - 2*leftmargin - self.columnGap*(len(self._currentSection['columnSizes'])))
 
668
    x = leftmargin + self.columnGap/2.0
 
669
    for i in range(len(self._currentSection['columnSizes'])):
 
670
      colSize = (self._currentSection['columnSizes'][i] / float(totalCols) * usableHorizSpace)
 
671
      self._currentSection['columnCoords'].append ( ( x, x+colSize ) )
 
672
      x += colSize + self.columnGap
 
673