#
# This file is part of GNU Enterprise.
#
# GNU Enterprise is free software; you can redistribute it
# and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 2, or (at your option) any later version.
#
# GNU Enterprise is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied
# warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
# PURPOSE. See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with program; see the file COPYING. If not,
# write to the Free Software Foundation, Inc., 59 Temple Place
# - Suite 330, Boston, MA 02111-1307, USA.
#
# Copyright 2004-2007 Free Software Foundation
#
# FILE:
# pdftable/pdftable.py
#
# DESCRIPTION:
"""
A class that creates multisection tabular pdf reports.
"""
#

# ============================================================================
# Imports
# ============================================================================
# ----------------------------------------------------------------------------
# Python standard modules
# ----------------------------------------------------------------------------
import time
import sys

# ----------------------------------------------------------------------------
# Reportlab modules
# ----------------------------------------------------------------------------
from reportlab.lib import colors
from reportlab.lib.units import inch
from reportlab.lib.pagesizes import letter, landscape, portrait
from reportlab.pdfgen import canvas
from reportlab.pdfbase.pdfmetrics import getFont

# ============================================================================
# Module constants
# ============================================================================
# ----------------------------------------------------------------------------
# Text alignment constants
# ----------------------------------------------------------------------------
LEFT=0
RIGHT=1
CENTER=2

# ----------------------------------------------------------------------------
# Some sample font definitions 
# ----------------------------------------------------------------------------
fontDefs = {               # Font name       Pt Size  Vert Spacing
  'dataFont' :             ('Times-Roman',      10,        11),
  'subtotalFont' :         ('Times-Bold',       10,        12),
  'tableHeaderFont' :      ('Times-Bold',       10,        12),
  
  'titleFont' :            ('Helvetica-Bold',   14,        14),
  'title2Font' :           ('Helvetica',        12,        14),
  'title3Font' :           ('Helvetica-Oblique',12,        14),
  'repeatTitleFont' :      ('Helvetica-Oblique', 9,        10),
  'footerFont' :           ('Times-Roman',       9,        10),

  'subtitleFont' :         ('Times-Bold',       12,        13),
  'subtitleLabelFont' :    ('Times-Roman',      12,        13),
  'subtitleContinueFont' : ('Times-Italic',     10,        13),
}

# ----------------------------------------------------------------------------
# Sample Color settings
#
# TODO: This really needs handled in a more flexible mannor.
# ----------------------------------------------------------------------------


# ----------------------------------------------------------------------------
# Size constants
#
# TODO: This really needs handled in a more flexible mannor.
# ----------------------------------------------------------------------------

lineWidth = .25

# NOTE: I tried not to tie internal logic to the font/color/tracking
#       values that follow, so *theoretically* you can adjust them to
#       your liking.
leftmargin = .75*inch
topmargin = .75*inch

# This is nothing but voodoo guesses...
# Greatly depends on pointsize of self.fontDefs['dataFont']
# TODO: This should probably be computed based on width of a "0"
# TODO: and the width of the report.  But, eh, this works for now...
maxColsForPortraitNonscaled = 100   # Number of columns before we start scaling down
maxColsForPortraitScaled = 120      # Number of columns before we switch to landscape
maxColsForLandscapeNonscaled = 140  # Number of columns before landscape + scaling

# ============================================================================
# Class definition
# ============================================================================
class pdftable:
  fontDefs = fontDefs
  # ==========================================================================
  # Creation/closure functions
  # ==========================================================================
  
  # --------------------------------------------------------------------------
  # Initialize
  # --------------------------------------------------------------------------
  def __init__(self, destination, parameterBox = ()):    
    """
    A simple table based report generator.
    
    The class provides the following features:
      - Border support for columns, rows.
      - Automatic adjustments of the data when it is unable to fit on one page.
      - Multiple sections of a report handled automatically.
    
    @param destination: A python file handle used for output
    @param parameterBox: Unused?
    """
    self._titleList = ['Your Report','Your Report']
    
    self.destination = destination
#     self.parameterBox = parameterBox

    self.highlightColor = colors.HexColor("0xffffcc")
    self.headingColor = self.subtotalColor = colors.HexColor("0xe6e6ff")

    self.highlightIndex = 0
    self.init = 1
    self._scalingComplete = 0
    self.page = 0
    self.continuing = 0

    # Time of report session
    self.timestamp = time.strftime("%m/%d/%Y %I:%M%p", time.localtime())

    # Will be set by _beginData()
    self.height = 0
    self.width = 0
    self.scale = 1.0

    self._currentSectionType = "default"
    self._currentSection = {}
    self.definedSectionTypes = {}
    
    self.columnGap = 6 # in pts, Amount of gap to leave between columns... doesn't need to be too much

  # --------------------------------------------------------------------------
  # Finish up the output
  # --------------------------------------------------------------------------
  def close(self):
    """
    Finalize the report.
    
    Should be called after all data was sent to the report.
    """
    self.canvas.showPage()
    self.canvas.save()

    
  # ==========================================================================
  # Functions that effect report wide settings
  # ==========================================================================
  
  # --------------------------------------------------------------------------
  # Sets the title of the report
  # --------------------------------------------------------------------------    
  def setFullTitle(self,titleList):
    """
    Sets the title of the report
    
    @param titleList: A list containing tuples of the following format
                      (text, font defintion)
                      Font defintions are also tuples in the format
                      (Font name, Pt Size, Vert Spacing) such as...
                      ('Times-Roman',      10,    11)
    """
    self._titleList = titleList
  
  # --------------------------------------------------------------------------
  # Define a column on the report
  # --------------------------------------------------------------------------
  def addColumn(self, align, minSize, overflow="", highlight=None, 
                leftBorder=0, rightBorder=0, topBorder=0, bottomBorder=0,
                sectionType="default"):    
    """
    Defines a column on the record for the specified section
    
    @param minSize: The minimum size in points of the column.
    @param overflow: The text that should be printed if the contents of a column are too 
                     large for the column width.
    @param highlight: The color of background to use in this column.
    @param leftBorder: The width of the left side border in points.
    @param rightBorder: The width of the right side border in points.
    @param topBorder: The width of the top side border in points.
    @param bottomBorder: The width of the bottom side border in points.           
    @param sectionType: The name of the section to which this column should be added.
    """
    try:
      secType = self.definedSectionTypes[sectionType]
    except KeyError:
      self.definedSectionTypes[sectionType] = {'columnSizes'      :[],
                                       'columnAligns'     :[],
                                       'columnOverflow'   :[],
                                       'columnHighlight'  :[],
                                       'columnCoords'     :[],
                                       'columnLeftBorder' :[],
                                       'columnRightBorder':[],
                                       'columnTopBorder'  :[],
                                       'columnBottomBorder':[],
                                       'headerList':    [[]],                                    
                                    }
      secType = self.definedSectionTypes[sectionType]
      
    secType['columnSizes'].append(minSize)
    secType['columnAligns'].append(align)
    secType['columnOverflow'].append(overflow)
    secType['columnHighlight'].append(highlight)
    secType['columnLeftBorder'].append(leftBorder)
    secType['columnRightBorder'].append(rightBorder)
    secType['columnTopBorder'].append(topBorder)
    secType['columnBottomBorder'].append(bottomBorder)

  # ==========================================================================
  # Section level functions
  # ==========================================================================
  
  # --------------------------------------------------------------------------
  # Define a section header
  # --------------------------------------------------------------------------
  def addHeader(self, heading, align, startColumn, endColumn, 
                leftBorder=0, rightBorder=0, sectionType="default"):
    """
    Adds a column header to one or more columns
    
    @param heading: The text to display
    @param align:   The alignment to apply to the header text 
                    LEFT, RIGHT, CENTER are defined in this module
    @param startColumn: The starting column number (starts at 0) for the header
    @param endColumn: The ending column number for the header
    @param leftBorder: The width of the left side border in points.
    @param rightBorder: The width of the right side border in points.   
    @param sectionType: The name of the section to which this header should be added.    
    """
    secType = self.definedSectionTypes[sectionType]
    if endColumn > len(secType['columnSizes'])-1:
      print "endColumn longer than defined columns"
      sys.exit()
          
    heading = {'text':heading,
               'align':align,
               'start':startColumn,
               'end':endColumn,
               'leftBorder':leftBorder,
               'rightBorder':rightBorder,
               }
    secType['headerList'][-1].append(heading)
  
  # --------------------------------------------------------------------------
  # Add a row to a header
  # --------------------------------------------------------------------------  
  def addHeaderRow(self, sectionType="default"):
    """
    Adds a new row to the header.  Subsequent calls to addHeader will now
    apply to this new row.
    
    @param sectionType: The name of the section to which this header row will be added.    
    """
    secType = self.definedSectionTypes[sectionType]
    secType['headerList'].append([])
    
  # --------------------------------------------------------------------------
  # Inform the writer to switch to a new section
  # --------------------------------------------------------------------------
  def startNewSection(self, subtitle, sectionType="default", newPage=1):
    """
    Begins a new report section.
  
    @param subtitle: The subtitle to display above the section
    @param sectionType: The name of the previous defined section to use.
    @param newPage: If 0 then the new page will be supressed.
                    If 1 (the default) then a new page will be output prior
                    to starting the section.
    """
    if sectionType != self._currentSectionType:
      self.init = 1
    self._currentSection = self.definedSectionTypes[sectionType]   
    self._currentSectionType = sectionType    
    self.subtitle = subtitle
    if newPage:
      self.highlightIndex = 0 
    if self.init:
      self.init = 0
      self.beginData()
    self.continued = 0
    if newPage:
      self.newPage()
    else:
      self.drawSectionHeading()
      self.drawTableHeader()

  # ==========================================================================
  # Data functions 
  # ==========================================================================
  
  # --------------------------------------------------------------------------
  # Add data row
  # --------------------------------------------------------------------------
  def addRow(self, data, style="Data", displayBorders=True):
    """
    Adds a row of data to the current section
    
    @param data: A list of strings containing the data to add to the current section
    @param style: The format style to use to render the row.
                  These are currently hardcoded into this class and include
                  Data (default), Subtotal, Total
    @param displayBorders: Boolean that controls if a borders will be drawn for this
                           row.  Makes it easy to produce a "blank" row for spacing
                           output
    """
    canvas = self.canvas

    if style == "Total":
      self.y -= 4 * self.scale

    if style in ("Subtotal","Total"):
      font, size, tracking = self.fontDefs['subtotalFont']
      fontWidth = self.subtotalFontWidth
    else:
      font, size, tracking = self.fontDefs['dataFont']
      fontWidth = self.dataFontWidth

    size = size * self.scale
    tracking = tracking * self.scale

    if self.y - tracking < self.miny:
       self.newPage()

    highlighted = 0
    if style == "Data":
      highlighted = divmod((self.highlightIndex),4)[1]>1
      self.highlightIndex += 1
    else:
      self.highlightIndex = 0  # Reset highlighting after subtotals

    boxy = self.y - (tracking-size)*1.5
    boxh = tracking

    if style in ("Subtotal","Total"):
      self.drawHorizBorderedBox(leftmargin, boxy, self.width-leftmargin*2,
                                boxh, self.subtotalColor, lines=lineWidth * self.scale)
    elif highlighted:
      # If there is a bottom border we need to not draw over the top
      # of the previous rows border.  Currently minimum must be 1 point      
      adjustment = max([self._currentSection['columnBottomBorder'][0],1])
      self.drawHorizBorderedBox(leftmargin, boxy, self.width-leftmargin*2,
                                boxh-adjustment, self.highlightColor, lines=0)

    canvas.setFont(font, size)

    i = 0
    while i < len(data):
      col = str(data[i])
      # Find the hlx values (used for column highlight and borders)   
      if i > 0:
        hlx1 = self._currentSection['columnCoords'][i-1][1]+self.columnGap/2.0
      else:
        hlx1 = leftmargin
      
      if i < len(self._currentSection['columnCoords'])-1:
        hlx2 = self._currentSection['columnCoords'][i+1][0]-self.columnGap/2.0
      else:
        hlx2 = self.width-leftmargin
      
      # Column highlight support          
      highlightColumn = self._currentSection['columnHighlight'][i]
      if highlightColumn and not style in ("Subtotal","Total"):
        if self.highlightIndex==1: # We're on the first column (not 0 due to += above)
          adjust = lineWidth#*self.scale
        else:
          adjust = 0
        if highlighted:
          color = colors.Blacker(self.highlightColor,.98)
        else:
          color = highlightColumn
        
        self.drawHorizBorderedBox(hlx1, boxy - adjust, 
                                  hlx2-hlx1, boxh, 
                                  color, lines=0)
      
      # Column border support
      if displayBorders:
        leftBorder = self._currentSection['columnLeftBorder'][i]      
        if leftBorder:
          canvas.setLineWidth(leftBorder*self.scale)
          canvas.line(hlx1, boxy, hlx1, boxy + boxh)
          
        rightBorder =self._currentSection['columnRightBorder'][i]
        if rightBorder:
          canvas.setLineWidth(rightBorder*self.scale)
          canvas.line(hlx2, boxy, hlx2, boxy + boxh)
  
        topBorder =self._currentSection['columnTopBorder'][i]
        if topBorder:
          canvas.setLineWidth(topBorder*self.scale)
          canvas.line(hlx1, boxy + boxh, hlx2, boxy + boxh)
  
        bottomBorder = self._currentSection['columnBottomBorder'][i]
        if bottomBorder:
          canvas.setLineWidth(bottomBorder*self.scale)
          canvas.line(hlx1, boxy, hlx2, boxy)
                      
      if col:      
        align= self._currentSection['columnAligns'][i]
        x1, x2 = self._currentSection['columnCoords'][i]

        # Clip text, if needed
        restore = 0
        if fontWidth(col, size) > x2-x1:
          if self._currentSection['columnOverflow'][i]:
            col = self._currentSection['columnOverflow'][i]
          else:
            restore = 1
            canvas.saveState()
            path = canvas.beginPath()
            # Vertical is overkill, but only interested in horizontal
            path.rect(x1,self.y-tracking, x2-x1, tracking*3)
            canvas.clipPath(path, stroke=0, fill=0)

        if align == LEFT:
          canvas.drawString(x1,self.y,col)
        elif align == RIGHT:
          canvas.drawRightString(x2,self.y,col)
        elif align == CENTER:
          canvas.drawCentredString(x1+(x2-x1)/2.0,self.y,col)

        # Restore from clipping
        if restore:
          canvas.restoreState()
      i += 1

    self.y -= tracking

  def addLine(self, string, borders=None):
    """
    Adds a single line of text instead of a row to a table.
    
    Usefull in reproducing a check register style printout.
    
    @param string: The string to print
    @param borders: A list containing the border widths to use on the line.
                    Sequence is in the order Top, Right, Bottom, Left (TRBL)
                    which I believe is how CSS does it
    """
    canvas = self.canvas

    font, size, tracking = self.fontDefs['dataFont']
    fontWidth = self.dataFontWidth

    size = size * self.scale
    tracking = tracking * self.scale

    if self.y - tracking < self.miny:
       self.newPage()

    boxy = self.y - (tracking-size)*1.5
    boxh = tracking
    
    # Use the same highlighting as the pervious row printed
    highlighted = divmod((self.highlightIndex-1),4)[1]>1 # Use the same highlighting 
    if highlighted:
      # If there is a bottom border we need to not draw over the top
      # of the previous rows border.  Currently minimum must be 1 point      
      adjustment = max([self._currentSection['columnBottomBorder'][0],1])
      self.drawHorizBorderedBox(leftmargin, boxy, self.width-leftmargin*2,
                                boxh-adjustment, self.highlightColor, lines=0)

    # Place a border around the memo line
    if borders:
      topBorder, rightBorder, bottomBorder, leftBorder = borders

      # Top
      if topBorder:
        canvas.setLineWidth(topBorder*self.scale)
        canvas.line(leftmargin, boxy + boxh, self.width-leftmargin, boxy + boxh)
        
      #Right
      if rightBorder:
        canvas.setLineWidth(rightBorder*self.scale)
        canvas.line(self.width-leftmargin, boxy, self.width-leftmargin, boxy + boxh)
        
      # Bottom
      if bottomBorder:
        canvas.setLineWidth(bottomBorder*self.scale)
        canvas.line(leftmargin, boxy, self.width-leftmargin, boxy)
      # Left
      if leftBorder:
        canvas.setLineWidth(leftBorder*self.scale)
        canvas.line(leftmargin, boxy, leftmargin, boxy + boxh)

    canvas.setFont(font, size)
    hlx1 = leftmargin

    if string:
      # Clip text, if needed
      restore = False
      if fontWidth(string, size) > self.width-leftmargin*2:
        restore = True
        canvas.saveState()
        path = canvas.beginPath()
        # Vertical is overkill, but only interested in horizontal
        path.rect(leftmargin,self.y-tracking, self.width-leftmargin*2, tracking*3)
        canvas.clipPath(path, stroke=0, fill=0)

      canvas.drawString(leftmargin,self.y,string)

      # Restore from clipping
      if restore:
        canvas.restoreState()

    self.y -= tracking


  # ==========================================================================
  # Private functions
  # ==========================================================================

  # --------------------------------------------------------------------------
  # Begin a new page
  # --------------------------------------------------------------------------
  def newPage(self):
    """
    Private function that creates a new page.
    """
    if self.page:
      self.canvas.showPage()

    self.page += 1
    self.y = self.height - topmargin

    if self.page == 1:
      self.drawLargeTitle()
    else:
      self.drawSmallTitle()

    self.drawTableHeader()
    self.footer()

    self.continued = 1


  # --------------------------------------------------------------------------
  # Draw footer
  # --------------------------------------------------------------------------
  def footer(self):
    """
    Private function that creates the footer containing the time/page #
    """
    canvas = self.canvas
    font, size, tracking = self.fontDefs['footerFont']
    canvas.setFont(font, size)
    canvas.drawString(leftmargin, topmargin, self.timestamp)
    canvas.drawRightString(self.width - leftmargin, topmargin, "Page %s" % self.page)
    self.miny = topmargin + tracking*2

  # --------------------------------------------------------------------------
  # Draw full (first-page) header (Title)
  # --------------------------------------------------------------------------
  def drawLargeTitle(self):
    """
    Private function that creates a full (first page) header on a new page.
    """
    canvas = self.canvas
    self.y -= self.fontDefs['titleFont'][2]
    for text, fontspec in self._titleList:
      if text:
        font, size, tracking = fontspec
        canvas.setFont(font, size)
        canvas.drawCentredString(self.width/2.0, self.y, text)
        self.y -= tracking
    self.drawSectionHeading()

  # --------------------------------------------------------------------------
  # Draw short (non-first-page) header (Title)
  # --------------------------------------------------------------------------
  def drawSmallTitle(self):
    """
    Private function that creates a short ( non first page) header on a new page.
    """
    canvas = self.canvas
    font, size, tracking = self.fontDefs['repeatTitleFont']
    self. y -= size
    canvas.setFont(font, size)
    canvas.drawString(leftmargin, self.y, self._titleList[0][0])
    canvas.drawRightString(self.width - leftmargin, self.y, self._titleList[1][0])
    self.y -= tracking
    self.drawSectionHeading()
    
  # --------------------------------------------------------------------------
  # Draw the section header
  # --------------------------------------------------------------------------
  def drawSectionHeading(self):
    """
    Draws the text that preceeds the section's table.
    """
    canvas = self.canvas

    if not self.subtitle:
      return

    self.y -= self.fontDefs['subtitleFont'][2]

    font, size, tracking = self.fontDefs['subtitleLabelFont']

    text = canvas.beginText(leftmargin, self.y)
    for l in self.subtitle.split():
      boldOff = 0
      if l[0] == '*':
        l = l[1:]
        font, size, tracking = self.fontDefs['subtitleFont']

      if l[-1] == '*':
        boldOff = 1
        l = l[:-1]

      text.setFont(font, size)
      text.textOut(l+ ' ')
      if boldOff:
        font, size, tracking = self.fontDefs['subtitleLabelFont']
        text.textOut(' ')

    if self.continued:
      font2, size2, tracking2 = self.fontDefs['subtitleContinueFont']
      text.setFont(font2, size2)
      text.textOut("(Continued)")

    canvas.drawText(text)
    self.y -= tracking*2


  # --------------------------------------------------------------------------
  # Draw table header
  # --------------------------------------------------------------------------
  def drawTableHeader(self):
    """
    Generates a section's table header.
    """
    canvas = self.canvas

    numRows = len(self._currentSection['headerList'])

    font, size, tracking = self.fontDefs['tableHeaderFont']
    size = size * self.scale
    tracking = tracking * self.scale
    canvas.setFont(font, size)

    boxy = self.y + tracking - (tracking-size)/2.0
    boxh = -tracking*numRows - (tracking-size)/2.0 - lineWidth*self.scale
    self.drawHorizBorderedBox(leftmargin, boxy, self.width-leftmargin*2,
                              boxh, self.headingColor)

    for list in self._currentSection['headerList']:
      for header in list:
        c1 = header['start']
        c2 = header['end']
        x1 = self._currentSection['columnCoords'][c1][0]
        x2 = self._currentSection['columnCoords'][c2][1]
        align = header['align']        
        text = header['text']
        
        canvas.saveState()
        path = canvas.beginPath()
        # Vertical is overkill, but only interested in horizontal
        path.rect(x1,self.y-tracking, x2-x1, tracking*3)
        canvas.clipPath(path, stroke=0, fill=0)

        if align == LEFT:
          canvas.drawString(x1,self.y,text)
        elif align == RIGHT:
          canvas.drawRightString(x2,self.y,text)
        elif align == CENTER:
          canvas.drawCentredString(x1+(x2-x1)/2.0,self.y,text)
        canvas.restoreState()
        
        leftBorder = header['leftBorder']
        if leftBorder:
          canvas.setLineWidth(leftBorder*self.scale)
          canvas.line(x1-self.columnGap/2.0, boxy, x1-self.columnGap/2.0, boxy + boxh)
          
        rightBorder = header['rightBorder']
        if rightBorder:
          canvas.setLineWidth(rightBorder*self.scale)
          canvas.line(x2+self.columnGap/2.0, boxy, x2+self.columnGap/2.0, boxy + boxh)
      self.y -= tracking
      


  # --------------------------------------------------------------------------
  # Draws a box w/shading and a top/bottom border
  # --------------------------------------------------------------------------
  def drawHorizBorderedBox(self, x, y, w, h, color, lines=lineWidth):
    canvas = self.canvas
    canvas.saveState()
    canvas.setFillColor(color)
    canvas.rect(x,y,w,h, stroke=0,fill=1)
    if lines:
      canvas.setLineWidth(lines)
      canvas.line(x,y,x+w,y)
      canvas.line(x,y+h,x+w,y+h)
    canvas.restoreState()

  # --------------------------------------------------------------------------
  # Initialize report section
  # --------------------------------------------------------------------------
  def beginData(self):
    """
    Prepares the class to begin drawing a section on the report.  Figures out 
    the required orientation of the report as well as any scaling that is required
    """
    # Calculate column sizes
    totalCols = 0
    for cs in self._currentSection['columnSizes']:
      totalCols += cs

    # Figure out the page orientation/scaling
    if not self._scalingComplete:
      self._scalingComplete = 1
      self.pageSize = letter
      self.scale = 1.0
      if totalCols < maxColsForPortraitNonscaled:    # Guestimate of max # cols we can get on portrait
        self.pageOrient = portrait
        self.width, self.height = letter
      elif totalCols < maxColsForPortraitScaled:
        self.pageOrient = portrait
        self.width, self.height = letter
        self.scale = maxColsForPortraitNonscaled / float(totalCols)
      elif totalCols < maxColsForLandscapeNonscaled:
        self.pageOrient = landscape
        self.height, self.width = letter
      else:
        self.pageOrient = landscape
        self.height, self.width = letter
        self.scale = maxColsForLandscapeNonscaled / float(totalCols)
  
      if self.scale < 1:
        print "Scaling to %.2f%%" % (self.scale*100)
  
      # in pts, Amount of gap to leave between columns... 
      # doesn't need to be too much
      self.columnGap = self.columnGap * self.scale

      if isinstance(self.destination, canvas.Canvas): 
          self.canvas = self.destination
      else: 
          self.canvas = canvas.Canvas(self.destination, 
              pagesize=self.pageOrient(self.pageSize))
  
      font, size, leading = self.fontDefs['dataFont']
      self.dataFontWidth = getFont(font).stringWidth
  
      font, size, leading = self.fontDefs['subtotalFont']
      self.subtotalFontWidth = getFont(font).stringWidth
      
    # This is not scaled down according to self.scale...
    # we'll instead scale the point sizes down when we do setFont
    usableHorizSpace = (self.width - 2*leftmargin - self.columnGap*(len(self._currentSection['columnSizes'])))
    x = leftmargin + self.columnGap/2.0
    for i in range(len(self._currentSection['columnSizes'])):
      colSize = (self._currentSection['columnSizes'][i] / float(totalCols) * usableHorizSpace)
      self._currentSection['columnCoords'].append ( ( x, x+colSize ) )
      x += colSize + self.columnGap

  def setHighlightColorHex(self, hexColor):
    self.highlightColor = colors.HexColor(hexColor)
    
  def setFont(self, fontStyle, settings):
    assert fontStyle in self.fontDefs.keys(), 'Invalid font style: %s'
    
    self.fontDefs[fontStyle] = settings
