422 lines
16 KiB
Python
422 lines
16 KiB
Python
#Copyright ReportLab Europe Ltd. 2000-2017
|
|
#see license.txt for license details
|
|
#history https://hg.reportlab.com/hg-public/reportlab/log/tip/src/reportlab/lib/styles.py
|
|
__version__='3.3.0'
|
|
__doc__='''Classes for ParagraphStyle and similar things.
|
|
|
|
A style is a collection of attributes, but with some extra features
|
|
to allow 'inheritance' from a parent, and to ensure nobody makes
|
|
changes after construction.
|
|
|
|
ParagraphStyle shows all the attributes available for formatting
|
|
paragraphs.
|
|
|
|
getSampleStyleSheet() returns a stylesheet you can use for initial
|
|
development, with a few basic heading and text styles.
|
|
'''
|
|
__all__=(
|
|
'PropertySet',
|
|
'ParagraphStyle',
|
|
'LineStyle',
|
|
'ListStyle',
|
|
'StyleSheet1',
|
|
'getSampleStyleSheet',
|
|
)
|
|
from reportlab.lib.colors import white, black
|
|
from reportlab.lib.enums import TA_LEFT, TA_CENTER
|
|
from reportlab.lib.fonts import tt2ps
|
|
from reportlab.rl_config import canvas_basefontname as _baseFontName, \
|
|
underlineWidth as _baseUnderlineWidth, \
|
|
underlineOffset as _baseUnderlineOffset, \
|
|
underlineGap as _baseUnderlineGap, \
|
|
strikeWidth as _baseStrikeWidth, \
|
|
strikeOffset as _baseStrikeOffset, \
|
|
strikeGap as _baseStrikeGap, \
|
|
spaceShrinkage as _spaceShrinkage, \
|
|
platypus_link_underline as _platypus_link_underline, \
|
|
hyphenationLang as _hyphenationLang, \
|
|
hyphenationMinWordLength as _hyphenationMinWordLength, \
|
|
uriWasteReduce as _uriWasteReduce, \
|
|
embeddedHyphenation as _embeddedHyphenation
|
|
_baseFontNameB = tt2ps(_baseFontName,1,0)
|
|
_baseFontNameI = tt2ps(_baseFontName,0,1)
|
|
_baseFontNameBI = tt2ps(_baseFontName,1,1)
|
|
|
|
###########################################################
|
|
# 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 'name' not in self.defaults, "Class Defaults may not contain a 'name' attribute"
|
|
assert 'parent' not in self.defaults, "Class Defaults may not contain a 'parent' attribute"
|
|
if parent:
|
|
assert parent.__class__ == self.__class__, "Parent style %s must have same class as new style %s" % (parent.__class__.__name__,self.__class__.__name__)
|
|
|
|
#step two
|
|
self.name = name
|
|
self.parent = parent
|
|
self.__dict__.update(self.defaults)
|
|
|
|
#step two - copy from parent if any. Try to be
|
|
# very strict that only keys in class defaults are
|
|
# allowed, so they cannot inherit
|
|
self.refresh()
|
|
self._setKwds(**kw)
|
|
|
|
def _setKwds(self,**kw):
|
|
#step three - copy keywords if any
|
|
for key, value in kw.items():
|
|
self.__dict__[key] = value
|
|
|
|
def __repr__(self):
|
|
return "<%s '%s'>" % (self.__class__.__name__, self.name)
|
|
|
|
def refresh(self):
|
|
"""re-fetches attributes from the parent on demand;
|
|
use if you have been hacking the styles. This is
|
|
used by __init__"""
|
|
if self.parent:
|
|
for key, value in self.parent.__dict__.items():
|
|
if (key not in ['name','parent']):
|
|
self.__dict__[key] = value
|
|
|
|
def listAttrs(self, indent=''):
|
|
print(indent + 'name =', self.name)
|
|
print(indent + 'parent =', self.parent)
|
|
keylist = list(self.__dict__.keys())
|
|
keylist.sort()
|
|
keylist.remove('name')
|
|
keylist.remove('parent')
|
|
for key in keylist:
|
|
value = self.__dict__.get(key, None)
|
|
print(indent + '%s = %s' % (key, value))
|
|
|
|
def clone(self, name, parent=None, **kwds):
|
|
r = self.__class__(name,parent)
|
|
r.__dict__ = self.__dict__.copy()
|
|
r.name = name
|
|
r.parent = parent is None and self or parent
|
|
r._setKwds(**kwds)
|
|
return r
|
|
|
|
class ParagraphStyle(PropertySet):
|
|
defaults = {
|
|
'fontName':_baseFontName,
|
|
'fontSize':10,
|
|
'leading':12,
|
|
'leftIndent':0,
|
|
'rightIndent':0,
|
|
'firstLineIndent':0,
|
|
'alignment':TA_LEFT,
|
|
'spaceBefore':0,
|
|
'spaceAfter':0,
|
|
'bulletFontName':_baseFontName,
|
|
'bulletFontSize':10,
|
|
'bulletIndent':0,
|
|
#'bulletColor':black,
|
|
'textColor': black,
|
|
'backColor':None,
|
|
'wordWrap':None, #None means do nothing special
|
|
#CJK use Chinese Line breaking
|
|
#LTR RTL use left to right / right to left
|
|
#with support from pyfribi2 if available
|
|
'borderWidth': 0,
|
|
'borderPadding': 0,
|
|
'borderColor': None,
|
|
'borderRadius': None,
|
|
'allowWidows': 1,
|
|
'allowOrphans': 0,
|
|
'textTransform':None, #uppercase lowercase (captitalize not yet) or None or absent
|
|
'endDots':None, #dots on the last line of left/right justified paras
|
|
#string or object with text and optional fontName, fontSize, textColor & backColor
|
|
#dy
|
|
'splitLongWords':1, #make best efforts to split long words
|
|
'underlineWidth': _baseUnderlineWidth, #underline width
|
|
'bulletAnchor': 'start', #where the bullet is anchored ie start, middle, end or numeric
|
|
'justifyLastLine': 0, #n allow justification on the last line for more than n words 0 means don't bother
|
|
'justifyBreaks': 0, #justify lines broken with <br/>
|
|
'spaceShrinkage': _spaceShrinkage, #allow shrinkage of percentage of space to fit on line
|
|
'strikeWidth': _baseStrikeWidth, #stroke width
|
|
'underlineOffset': _baseUnderlineOffset, #fraction of fontsize to offset underlines
|
|
'underlineGap': _baseUnderlineGap, #gap for double/triple underline
|
|
'strikeOffset': _baseStrikeOffset, #fraction of fontsize to offset strikethrough
|
|
'strikeGap': _baseStrikeGap, #gap for double/triple strike
|
|
'linkUnderline': _platypus_link_underline,
|
|
#'underlineColor': None,
|
|
#'strikeColor': None,
|
|
'hyphenationLang': _hyphenationLang,
|
|
#'hyphenationMinWordLength': _hyphenationMinWordLength,
|
|
'embeddedHyphenation': _embeddedHyphenation,
|
|
'uriWasteReduce': _uriWasteReduce,
|
|
}
|
|
|
|
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 ListStyle(PropertySet):
|
|
defaults = dict(
|
|
leftIndent=18,
|
|
rightIndent=0,
|
|
bulletAlign='left',
|
|
bulletType='1',
|
|
bulletColor=black,
|
|
bulletFontName='Helvetica',
|
|
bulletFontSize=12,
|
|
bulletOffsetY=0,
|
|
bulletDedent='auto',
|
|
bulletDir='ltr',
|
|
bulletFormat=None,
|
|
start=None, #starting value for a list; if a list then the start sequence
|
|
)
|
|
|
|
_stylesheet1_undefined = object()
|
|
|
|
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 get(self,key,default=_stylesheet1_undefined):
|
|
try:
|
|
return self[key]
|
|
except KeyError:
|
|
if default!=_stylesheet1_undefined: return default
|
|
raise
|
|
|
|
def __contains__(self, key):
|
|
return key in self.byAlias or key in self.byName
|
|
|
|
def has_key(self,key):
|
|
return key in self
|
|
|
|
def add(self, style, alias=None):
|
|
key = style.name
|
|
if key in self.byName:
|
|
raise KeyError("Style '%s' already defined in stylesheet" % key)
|
|
if key in self.byAlias:
|
|
raise KeyError("Style name '%s' is already an alias in stylesheet" % key)
|
|
|
|
if alias:
|
|
if alias in self.byName:
|
|
raise KeyError("Style '%s' already defined in stylesheet" % alias)
|
|
if alias in self.byAlias:
|
|
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 = list(self.byName.items())
|
|
styles.sort()
|
|
alii = {}
|
|
for (alias, style) in list(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 = _baseFontName
|
|
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=_baseFontName,
|
|
fontSize=10,
|
|
leading=12)
|
|
)
|
|
|
|
stylesheet.add(ParagraphStyle(name='BodyText',
|
|
parent=stylesheet['Normal'],
|
|
spaceBefore=6)
|
|
)
|
|
stylesheet.add(ParagraphStyle(name='Italic',
|
|
parent=stylesheet['BodyText'],
|
|
fontName = _baseFontNameI)
|
|
)
|
|
|
|
stylesheet.add(ParagraphStyle(name='Heading1',
|
|
parent=stylesheet['Normal'],
|
|
fontName = _baseFontNameB,
|
|
fontSize=18,
|
|
leading=22,
|
|
spaceAfter=6),
|
|
alias='h1')
|
|
|
|
stylesheet.add(ParagraphStyle(name='Title',
|
|
parent=stylesheet['Normal'],
|
|
fontName = _baseFontNameB,
|
|
fontSize=18,
|
|
leading=22,
|
|
alignment=TA_CENTER,
|
|
spaceAfter=6),
|
|
alias='title')
|
|
|
|
stylesheet.add(ParagraphStyle(name='Heading2',
|
|
parent=stylesheet['Normal'],
|
|
fontName = _baseFontNameB,
|
|
fontSize=14,
|
|
leading=18,
|
|
spaceBefore=12,
|
|
spaceAfter=6),
|
|
alias='h2')
|
|
|
|
stylesheet.add(ParagraphStyle(name='Heading3',
|
|
parent=stylesheet['Normal'],
|
|
fontName = _baseFontNameBI,
|
|
fontSize=12,
|
|
leading=14,
|
|
spaceBefore=12,
|
|
spaceAfter=6),
|
|
alias='h3')
|
|
|
|
stylesheet.add(ParagraphStyle(name='Heading4',
|
|
parent=stylesheet['Normal'],
|
|
fontName = _baseFontNameBI,
|
|
fontSize=10,
|
|
leading=12,
|
|
spaceBefore=10,
|
|
spaceAfter=4),
|
|
alias='h4')
|
|
|
|
stylesheet.add(ParagraphStyle(name='Heading5',
|
|
parent=stylesheet['Normal'],
|
|
fontName = _baseFontNameB,
|
|
fontSize=9,
|
|
leading=10.8,
|
|
spaceBefore=8,
|
|
spaceAfter=4),
|
|
alias='h5')
|
|
|
|
stylesheet.add(ParagraphStyle(name='Heading6',
|
|
parent=stylesheet['Normal'],
|
|
fontName = _baseFontNameB,
|
|
fontSize=7,
|
|
leading=8.4,
|
|
spaceBefore=6,
|
|
spaceAfter=2),
|
|
alias='h6')
|
|
|
|
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=_baseFontNameBI),
|
|
alias='df')
|
|
|
|
stylesheet.add(ParagraphStyle(name='Code',
|
|
parent=stylesheet['Normal'],
|
|
fontName='Courier',
|
|
fontSize=8,
|
|
leading=8.8,
|
|
firstLineIndent=0,
|
|
leftIndent=36,
|
|
hyphenationLang=''))
|
|
|
|
stylesheet.add(ListStyle(name='UnorderedList',
|
|
parent=None,
|
|
leftIndent=18,
|
|
rightIndent=0,
|
|
bulletAlign='left',
|
|
bulletType='1',
|
|
bulletColor=black,
|
|
bulletFontName='Helvetica',
|
|
bulletFontSize=12,
|
|
bulletOffsetY=0,
|
|
bulletDedent='auto',
|
|
bulletDir='ltr',
|
|
bulletFormat=None,
|
|
#start='circle square blackstar sparkle disc diamond'.split(),
|
|
start=None,
|
|
),
|
|
alias='ul')
|
|
|
|
stylesheet.add(ListStyle(name='OrderedList',
|
|
parent=None,
|
|
leftIndent=18,
|
|
rightIndent=0,
|
|
bulletAlign='left',
|
|
bulletType='1',
|
|
bulletColor=black,
|
|
bulletFontName='Helvetica',
|
|
bulletFontSize=12,
|
|
bulletOffsetY=0,
|
|
bulletDedent='auto',
|
|
bulletDir='ltr',
|
|
bulletFormat=None,
|
|
#start='1 a A i I'.split(),
|
|
start=None,
|
|
),
|
|
alias='ol')
|
|
return stylesheet
|