[reportlab-users] Inheritance for ParagraphStyles

Luc Saffre reportlab-users@reportlab.com
Fri, 07 Mar 2003 16:25:40 +0200


This is a multi-part message in MIME format.
--------------000007020601010100000400
Content-Type: text/plain; charset=us-ascii; format=flowed
Content-Transfer-Encoding: 7bit

Hello,

I modified PropertySet in reportlab/lib/styles.py because I wanted true 
inheritance for ParagraphStyles.

- If, after creation of a StyleSheet1 using getSampleStyleSheet I
   modify the textColor attribute of Style "Normal" to blue, then the
   normal behaviour should be that also all children of "Normal" change
   their textColor to blue (except if they assigned themselves an
   explicit textColor). This is the main reason for my change.

- Besides this I thought it useful that one can also access the Styles
   in a StyleSheet1 using "attribute" syntax. For example on can now
   write::

      stylesheet.Normal.spaceAfter = 6

   which is equivament to the classic syntax::

      stylesheet["Normal"].spaceAfter = 6

If there are application where this change could cause problems, please 
let me know. I am willing to continue working on this project.

Andy, please consider taking my changes into the standard distribution 
of the toolkit.

The current implementation has some possible drawbacks:

- Attribute lookup costs (theoretically) more at runtime. Before
   starting a frenetic discussion about this, somebody should make some
   performance tests so that we know about what we discuss.

- The "name" and "parent" instance attributes of PropertySet have been
   renamed to _name and _parent. Code which accessed these attributes
   must be modified.

- The refresh() method no longer exists. Those who used this method
   will hopefully be glad about my changes.

Best regards
Luc


--------------000007020601010100000400
Content-Type: text/plain;
 name="styles.py"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="styles.py"

#copyright ReportLab Inc. 2000
#see license.txt for license details
#history http://cvs.sourceforge.net/cgi-bin/cvsweb.cgi/reportlab/lib/styles.py?cvsroot=reportlab
#$Header: /cvsroot/reportlab/reportlab/lib/styles.py,v 1.15 2002/07/24 19:56:37 andy_robinson Exp $
__version__=''' $Id: styles.py,v 1.15 2002/07/24 19:56:37 andy_robinson Exp $ '''

from reportlab.lib.colors import white, black
from reportlab.lib.enums import TA_LEFT, TA_CENTER



"""
Changes made by Luc Saffre <luc.saffre@gmx.net> on 2003-03-07 :

I changed PropertySet because I wanted true inheritance.

- If, after creation of a StyleSheet1 using getSampleStyleSheet I
  modify the textColor attribute of Style "Normal" to blue, then the
  normal behaviour should be that also all children of "Normal" change
  their textColor to blue (except if they assigned themselves an
  explicit textColor). This is the main reason for my change.

- Besides this I thought it useful that one can also access the Styles
  in a StyleSheet1 using "attribute" syntax. For example on can now
  write::

     stylesheet.Normal.spaceAfter = 6

  which is equivament to the classic syntax::

     stylesheet["Normal"].spaceAfter = 6

- The un-elegant limit that PropertySet may not contain a property
  called "name" or "parent" has been removed.


The current implementation has two possible drawbacks:

- Attribute lookup costs (theoretically) more at runtime. Before
  starting a frenetic discussion about this, somebody should make some
  performance tests so that we know about what we discuss.
  
- The "name" and "parent" instance attributes of PropertySet have been
  renamed to _name and _parent. Code which accessed these attributes
  must be modified.

- The refresh() method no longer exists. Those who used this method
  will hopefully be glad about my changes...

"""

###########################################################
# This class provides an 'instance inheritance'
# mechanism for its descendants, simpler than acquisition
# but not as far-reaching
###########################################################
class PropertySet:
    defaults = {}

    def __init__(self, name, parent=None, **kw):
        """When initialized, it copies the class defaults;
        then takes a copy of the attributes of the parent
        if any.  All the work is done in init - styles
        should cost little to use at runtime."""
        # step one - validate the hell out of it
        #assert not self.defaults.has_key('name'), "Class Defaults may not contain a 'name' attribute"
        #assert not self.defaults.has_key('parent'), "Class Defaults may not contain a 'parent' attribute"
        if parent:
            assert parent.__class__ == self.__class__, "Parent style must have same class as new style"

        #step two
        self._name = name
        self._parent = parent
        self._props = kw



    def __repr__(self):
        return "<%s '%s'>" % (self.__class__.__name__, self._name)

    def __getattr__(self,name):
        try:
           return self.__dict__["_props"][name]
        except KeyError,e:
           pass
        
        if self.__dict__["_parent"] is not None:
           try:
              return getattr(self.__dict__["_parent"],name)
           except KeyError,e:
              pass
           
        return self.defaults[name]
      


    def listAttrs(self, indent=''):
        print indent + 'name =', self._name
        print indent + 'parent =', self._parent
        keylist = self._props.keys()
        keylist.sort()
        for key in keylist:
            value = self._props.get(key, None)
            print indent + '%s = %s' % (key, value)

class ParagraphStyle(PropertySet):
    defaults = {
        'fontName':'Times-Roman',
        'fontSize':10,
        'leading':12,
        'leftIndent':0,
        'rightIndent':0,
        'firstLineIndent':0,
        'alignment':TA_LEFT,
        'spaceBefore':0,
        'spaceAfter':0,
        'bulletFontName':'Times-Roman',
        'bulletFontSize':10,
        'bulletIndent':0,
        'textColor': black,
        'backColor':None
        }

class LineStyle(PropertySet):
    defaults = {
        'width':1,
        'color': black
        }
    def prepareCanvas(self, canvas):
        """You can ask a LineStyle to set up the canvas for drawing
        the lines."""
        canvas.setLineWidth(1)
        #etc. etc.

class StyleSheet1:
    """This may or may not be used.  The idea is to
    1. slightly simplify construction of stylesheets;
    2. enforce rules to validate styles when added
       (e.g. we may choose to disallow having both
       'heading1' and 'Heading1' - actual rules are
       open to discussion);
    3. allow aliases and alternate style lookup
       mechanisms
    4. Have a place to hang style-manipulation
       methods (save, load, maybe support a GUI
       editor)
       Access is via getitem, so they can be
       compatible with plain old dictionaries.
       """
    def __init__(self):
        self.byName = {}
        self.byAlias = {}


    def __getitem__(self, key):
        try:
            return self.byAlias[key]
        except KeyError:
            try:
                return self.byName[key]
            except KeyError:
                raise KeyError, "Style '%s' not found in stylesheet" % key

    def __getattr__(self,name):
        return self.__getitem__(name)
      
    def has_key(self, key):
        if self.byAlias.has_key(key):
            return 1
        elif self.byName.has_key(key):
            return 1
        else:
            return 0

    def add(self, style, alias=None):
        key = style._name
        if self.byName.has_key(key):
            raise KeyError, "Style '%s' already defined in stylesheet" % key
        if self.byAlias.has_key(key):
            raise KeyError, "Style name '%s' is already an alias in stylesheet" % key

        if alias:
            if self.byName.has_key(alias):
                raise KeyError, "Style '%s' already defined in stylesheet" % alias
            if self.byAlias.has_key(alias):
                raise KeyError, "Alias name '%s' is already an alias in stylesheet" % alias
        #passed all tests?  OK, add it
        self.byName[key] = style
        if alias:
            self.byAlias[alias] = style

    def list(self):
        styles = self.byName.items()
        styles.sort()
        alii = {}
        for (alias, style) in self.byAlias.items():
            alii[style] = alias
        for (name, style) in styles:
            alias = alii.get(style, None)
            print name, alias
            style.listAttrs('    ')
            print




def testStyles():
    pNormal = ParagraphStyle('Normal',None)
    pNormal.fontName = 'Times-Roman'
    pNormal.fontSize = 12
    pNormal.leading = 14.4

    pNormal.listAttrs()
    print
    pPre = ParagraphStyle('Literal', pNormal)
    pPre.fontName = 'Courier'
    pPre.listAttrs()
    return pNormal, pPre

def getSampleStyleSheet():
    """Returns a stylesheet object"""
    stylesheet = StyleSheet1()

    stylesheet.add(ParagraphStyle(name='Normal',
                                  fontName='Times-Roman',
                                  fontSize=10,
                                  leading=12)
                   )

    stylesheet.add(ParagraphStyle(name='BodyText',
                                  parent=stylesheet['Normal'],
                                  spaceBefore=6)
                   )
    stylesheet.add(ParagraphStyle(name='Italic',
                                  parent=stylesheet['BodyText'],
                                  fontName = 'Times-Italic')
                   )

    stylesheet.add(ParagraphStyle(name='Heading1',
                                  parent=stylesheet['Normal'],
                                  fontName = 'Times-Bold',
                                  fontSize=18,
                                  leading=22,
                                  spaceAfter=6),
                   alias='h1')

    stylesheet.add(ParagraphStyle(name='Title',
                                  parent=stylesheet['Normal'],
                                  fontName = 'Times-Bold',
                                  fontSize=18,
                                  leading=22,
                                  alignment=TA_CENTER,
                                  spaceAfter=6),
                   alias='title')

    stylesheet.add(ParagraphStyle(name='Heading2',
                                  parent=stylesheet['Normal'],
                                  fontName = 'Times-Bold',
                                  fontSize=14,
                                  leading=18,
                                  spaceBefore=12,
                                  spaceAfter=6),
                   alias='h2')

    stylesheet.add(ParagraphStyle(name='Heading3',
                                  parent=stylesheet['Normal'],
                                  fontName = 'Times-BoldItalic',
                                  fontSize=12,
                                  leading=14,
                                  spaceBefore=12,
                                  spaceAfter=6),
                   alias='h3')

    stylesheet.add(ParagraphStyle(name='Bullet',
                                  parent=stylesheet['Normal'],
                                  firstLineIndent=0,
                                  spaceBefore=3),
                   alias='bu')

    stylesheet.add(ParagraphStyle(name='Definition',
                                  parent=stylesheet['Normal'],
                                  firstLineIndent=0,
                                  leftIndent=36,
                                  bulletIndent=0,
                                  spaceBefore=6,
                                  bulletFontName='Times-BoldItalic'),
                   alias='df')

    stylesheet.add(ParagraphStyle(name='Code',
                                  parent=stylesheet['Normal'],
                                  fontName='Courier',
                                  fontSize=8,
                                  leading=8.8,
                                  firstLineIndent=0,
                                  leftIndent=36))


    return stylesheet

--------------000007020601010100000400--