SP/web2py/gluon/contrib/pyrtf/Elements.py
Saturneic 064f602b1a Add.
2018-10-25 23:33:13 +08:00

758 lines
32 KiB
Python

from __future__ import print_function
from types import IntType, FloatType, LongType, StringTypes
from copy import deepcopy
from binascii import hexlify
from Constants import *
from Styles import *
class UnhandledParamError( Exception ) :
def __init__( self, param ) :
Exception.__init__( self, "Don't know what to do with param %s" % param )
# red green blue
StandardColours = Colours()
StandardColours.append( Colour( 'Black', 0, 0, 0 ) )
StandardColours.append( Colour( 'Blue', 0, 0, 255 ) )
StandardColours.append( Colour( 'Turquoise', 0, 255, 255 ) )
StandardColours.append( Colour( 'Green', 0, 255, 0 ) )
StandardColours.append( Colour( 'Pink', 255, 0, 255 ) )
StandardColours.append( Colour( 'Red', 255, 0, 0 ) )
StandardColours.append( Colour( 'Yellow', 255, 255, 0 ) )
StandardColours.append( Colour( 'White', 255, 255, 255 ) )
StandardColours.append( Colour( 'Blue Dark', 0, 0, 128 ) )
StandardColours.append( Colour( 'Teal', 0, 128, 128 ) )
StandardColours.append( Colour( 'Green Dark', 0, 128, 0 ) )
StandardColours.append( Colour( 'Violet', 128, 0, 128 ) )
StandardColours.append( Colour( 'Red Dark', 128, 0, 0 ) )
StandardColours.append( Colour( 'Yellow Dark', 128, 128, 0 ) )
StandardColours.append( Colour( 'Grey Dark', 128, 128, 128 ) )
StandardColours.append( Colour( 'Grey', 192, 192, 192 ) )
StandardFonts = Fonts()
StandardFonts.append( Font( 'Arial' , 'swiss' , 0, 2, '020b0604020202020204' ) )
StandardFonts.append( Font( 'Arial Black' , 'swiss' , 0, 2, '020b0a04020102020204' ) )
StandardFonts.append( Font( 'Arial Narrow' , 'swiss' , 0, 2, '020b0506020202030204' ) )
StandardFonts.append( Font( 'Bitstream Vera Sans Mono', 'modern', 0, 1, '020b0609030804020204' ) )
StandardFonts.append( Font( 'Bitstream Vera Sans' , 'swiss' , 0, 2, '020b0603030804020204' ) )
StandardFonts.append( Font( 'Bitstream Vera Serif' , 'roman' , 0, 2, '02060603050605020204' ) )
StandardFonts.append( Font( 'Book Antiqua' , 'roman' , 0, 2, '02040602050305030304' ) )
StandardFonts.append( Font( 'Bookman Old Style' , 'roman' , 0, 2, '02050604050505020204' ) )
StandardFonts.append( Font( 'Castellar' , 'roman' , 0, 2, '020a0402060406010301' ) )
StandardFonts.append( Font( 'Century Gothic' , 'swiss' , 0, 2, '020b0502020202020204' ) )
StandardFonts.append( Font( 'Comic Sans MS' , 'script', 0, 2, '030f0702030302020204' ) )
StandardFonts.append( Font( 'Courier New' , 'modern', 0, 1, '02070309020205020404' ) )
StandardFonts.append( Font( 'Franklin Gothic Medium' , 'swiss' , 0, 2, '020b0603020102020204' ) )
StandardFonts.append( Font( 'Garamond' , 'roman' , 0, 2, '02020404030301010803' ) )
StandardFonts.append( Font( 'Georgia' , 'roman' , 0, 2, '02040502050405020303' ) )
StandardFonts.append( Font( 'Haettenschweiler' , 'swiss' , 0, 2, '020b0706040902060204' ) )
StandardFonts.append( Font( 'Impact' , 'swiss' , 0, 2, '020b0806030902050204' ) )
StandardFonts.append( Font( 'Lucida Console' , 'modern', 0, 1, '020b0609040504020204' ) )
StandardFonts.append( Font( 'Lucida Sans Unicode' , 'swiss' , 0, 2, '020b0602030504020204' ) )
StandardFonts.append( Font( 'Microsoft Sans Serif' , 'swiss' , 0, 2, '020b0604020202020204' ) )
StandardFonts.append( Font( 'Monotype Corsiva' , 'script', 0, 2, '03010101010201010101' ) )
StandardFonts.append( Font( 'Palatino Linotype' , 'roman' , 0, 2, '02040502050505030304' ) )
StandardFonts.append( Font( 'Papyrus' , 'script', 0, 2, '03070502060502030205' ) )
StandardFonts.append( Font( 'Sylfaen' , 'roman' , 0, 2, '010a0502050306030303' ) )
StandardFonts.append( Font( 'Symbol' , 'roman' , 2, 2, '05050102010706020507' ) )
StandardFonts.append( Font( 'Tahoma' , 'swiss' , 0, 2, '020b0604030504040204' ) )
StandardFonts.append( Font( 'Times New Roman' , 'roman' , 0, 2, '02020603050405020304' ) )
StandardFonts.append( Font( 'Trebuchet MS' , 'swiss' , 0, 2, '020b0603020202020204' ) )
StandardFonts.append( Font( 'Verdana' , 'swiss' , 0, 2, '020b0604030504040204' ) )
StandardFonts.Castellar.SetAlternate( StandardFonts.Georgia )
"""
Found the following definition at http://www.pbdr.com/vbtips/gen/convtwip.htm
Twips are screen-independent units used to ensure that the placement and
proportion of screen elements in your screen application are the same on all
display systems. A twip is a unit of screen measurement equal to 1/20 of a
printer's point. The conversion between twips and
inches/centimeters/millimeters is as follows:
There are approximately 1440 twips to a inch (the length of a screen item
measuring one inch when printed).
As there are 2.54 centimeters to 1 inch, then there are approximately 567
twips to a centimeter (the length of a screen item measuring one centimeter
when printed).
Or in millimeters, as there are 25.4 millimeters to 1 inch, therefore there
are approximately 56.7 twips to a millimeter (the length of a screen item
measuring one millimeter when printed)."""
# Width default is 12240, Height default is 15840
StandardPaper = Papers()
StandardPaper.append( Paper( 'LETTER' , 1, 'Letter 8 1/2 x 11 in' , 12240, 15840 ) )
StandardPaper.append( Paper( 'LETTERSMALL' , 2, 'Letter Small 8 1/2 x 11 in' , 12240, 15840 ) )
StandardPaper.append( Paper( 'TABLOID' , 3, 'Tabloid 11 x 17 in' , 15840, 24480 ) )
StandardPaper.append( Paper( 'LEDGER' , 4, 'Ledger 17 x 11 in' , 24480, 15840 ) )
StandardPaper.append( Paper( 'LEGAL' , 5, 'Legal 8 1/2 x 14 in' , 12240, 20160 ) )
StandardPaper.append( Paper( 'STATEMENT' , 6, 'Statement 5 1/2 x 8 1/2 in' , 7920, 12240 ) )
StandardPaper.append( Paper( 'EXECUTIVE' , 7, 'Executive 7 1/4 x 10 1/2 in' , 10440, 15120 ) )
StandardPaper.append( Paper( 'A3' , 8, 'A3 297 x 420 mm' , 16838, 23811 ) )
StandardPaper.append( Paper( 'A4' , 9, 'A4 210 x 297 mm' , 11907, 16838 ) )
StandardPaper.append( Paper( 'A4SMALL' , 10, 'A4 Small 210 x 297 mm' , 11907, 16838 ) )
StandardPaper.append( Paper( 'A5' , 11, 'A5 148 x 210 mm' , 8391, 11907 ) )
StandardPaper.append( Paper( 'B4' , 12, 'B4 (JIS) 250 x 354' , 14175, 20072 ) )
StandardPaper.append( Paper( 'B5' , 13, 'B5 (JIS) 182 x 257 mm' , 10319, 14572 ) )
StandardPaper.append( Paper( 'FOLIO' , 14, 'Folio 8 1/2 x 13 in' , 12240, 18720 ) )
StandardPaper.append( Paper( 'QUARTO' , 15, 'Quarto 215 x 275 mm' , 12191, 15593 ) )
StandardPaper.append( Paper( '10X14' , 16, '10x14 in' , 14400, 20160 ) )
StandardPaper.append( Paper( '11X17' , 17, '11x17 in' , 15840, 24480 ) )
StandardPaper.append( Paper( 'NOTE' , 18, 'Note 8 1/2 x 11 in' , 12240, 15840 ) )
StandardPaper.append( Paper( 'ENV_9' , 19, 'Envelope #9 3 7/8 x 8 7/8' , 5580, 12780 ) )
StandardPaper.append( Paper( 'ENV_10' , 20, 'Envelope #10 4 1/8 x 9 1/2' , 5940, 13680 ) )
StandardPaper.append( Paper( 'ENV_11' , 21, 'Envelope #11 4 1/2 x 10 3/8' , 6480, 14940 ) )
StandardPaper.append( Paper( 'ENV_12' , 22, 'Envelope #12 4 3/4 x 11' , 6840, 15840 ) )
StandardPaper.append( Paper( 'ENV_14' , 23, 'Envelope #14 5 x 11 1/2' , 7200, 16560 ) )
StandardPaper.append( Paper( 'CSHEET' , 24, 'C size sheet 18 x 24 in' , 29520, 34560 ) )
StandardPaper.append( Paper( 'DSHEET' , 25, 'D size sheet 22 x 34 in' , 31680, 48960 ) )
StandardPaper.append( Paper( 'ESHEET' , 26, 'E size sheet 34 x 44 in' , 48960, 63360 ) )
StandardPaper.append( Paper( 'ENV_DL' , 27, 'Envelope DL 110 x 220mm' , 6237, 12474 ) )
StandardPaper.append( Paper( 'ENV_C5' , 28, 'Envelope C5 162 x 229 mm' , 9185, 12984 ) )
StandardPaper.append( Paper( 'ENV_C3' , 29, 'Envelope C3 324 x 458 mm' , 18371, 25969 ) )
StandardPaper.append( Paper( 'ENV_C4' , 30, 'Envelope C4 229 x 324 mm' , 12984, 18371 ) )
StandardPaper.append( Paper( 'ENV_C6' , 31, 'Envelope C6 114 x 162 mm' , 6464, 9185 ) )
StandardPaper.append( Paper( 'ENV_C65' , 32, 'Envelope C65 114 x 229 mm' , 6464, 12984 ) )
StandardPaper.append( Paper( 'ENV_B4' , 33, 'Envelope B4 250 x 353 mm' , 14175, 20015 ) )
StandardPaper.append( Paper( 'ENV_B5' , 34, 'Envelope B5 176 x 250 mm' , 9979, 14175 ) )
StandardPaper.append( Paper( 'ENV_B6' , 35, 'Envelope B6 176 x 125 mm' , 9979, 7088 ) )
StandardPaper.append( Paper( 'ENV_ITALY' , 36, 'Envelope 110 x 230 mm' , 6237, 13041 ) )
StandardPaper.append( Paper( 'ENV_MONARCH' , 37, 'Envelope Monarch 3.875 x 7.5 in' , 5580, 10800 ) )
StandardPaper.append( Paper( 'ENV_PERSONAL' , 38, '6 3/4 Envelope 3 5/8 x 6 1/2 in' , 5220, 9360 ) )
StandardPaper.append( Paper( 'FANFOLD_US' , 39, 'US Std Fanfold 14 7/8 x 11 in' , 21420, 15840 ) )
StandardPaper.append( Paper( 'FANFOLD_STD_GERMAN' , 40, 'German Std Fanfold 8 1/2 x 12 in' , 12240, 17280 ) )
StandardPaper.append( Paper( 'FANFOLD_LGL_GERMAN' , 41, 'German Legal Fanfold 8 1/2 x 13 in' , 12240, 18720 ) )
#
# Finally a StyleSheet in which all of this stuff is put together
#
class StyleSheet :
def __init__( self, colours=None, fonts=None ) :
self.Colours = colours or deepcopy( StandardColours )
self.Fonts = fonts or deepcopy( StandardFonts )
self.TextStyles = AttributedList()
self.ParagraphStyles = AttributedList()
class Section( list ) :
NONE = 1
COLUMN = 2
PAGE = 3
EVEN = 4
ODD = 5
BREAK_TYPES = [ NONE, COLUMN, PAGE, EVEN, ODD ]
def __init__( self, paper=None, margins=None, break_type=None, headery=None, footery=None, landscape=None, first_page_number=None ) :
super( Section, self ).__init__()
self.Paper = paper or StandardPaper.A4
self.SetMargins( margins )
self.Header = []
self.Footer = []
self.FirstHeader = []
self.FirstFooter = []
self.SetBreakType( break_type or self.NONE )
self.SetHeaderY( headery )
self.SetFooterY( footery )
self.SetLandscape( landscape )
self.SetFirstPageNumber( first_page_number )
def TwipsToRightMargin( self ) :
return self.Paper.Width - ( self.Margins.Left + self.Margins.Right )
def SetMargins( self, value ) :
self.Margins = value or MarginsPropertySet( top=1000, left=1200, bottom=1000, right=1200 )
self.Width = self.Paper.Width - ( self.Margins.Left + self.Margins.Right )
def SetBreakType( self, value ) :
assert value in self.BREAK_TYPES
self.BreakType = value
return self
def SetHeaderY( self, value ) :
self.HeaderY = value
return self
def SetFooterY( self, value ) :
self.FooterY = value
return self
def SetLandscape( self, value ) :
self.Landscape = False
if value : self.Landscape = True
return self
def SetFirstPageNumber( self, value ) :
self.FirstPageNumber = value
return self
def MakeDefaultStyleSheet( ) :
result = StyleSheet()
NormalText = TextStyle( TextPropertySet( result.Fonts.Arial, 22 ) )
ps = ParagraphStyle( 'Normal',
NormalText.Copy(),
ParagraphPropertySet( space_before = 60,
space_after = 60 ) )
result.ParagraphStyles.append( ps )
ps = ParagraphStyle( 'Normal Short',
NormalText.Copy() )
result.ParagraphStyles.append( ps )
NormalText.TextPropertySet.SetSize( 32 )
ps = ParagraphStyle( 'Heading 1',
NormalText.Copy(),
ParagraphPropertySet( space_before = 240,
space_after = 60 ) )
result.ParagraphStyles.append( ps )
NormalText.TextPropertySet.SetSize( 24 ).SetBold( True )
ps = ParagraphStyle( 'Heading 2',
NormalText.Copy(),
ParagraphPropertySet( space_before = 240,
space_after = 60 ) )
result.ParagraphStyles.append( ps )
# Add some more in that are based on the normal template but that
# have some indenting set that makes them suitable for doing numbered
normal_numbered = result.ParagraphStyles.Normal.Copy()
normal_numbered.SetName( 'Normal Numbered' )
normal_numbered.ParagraphPropertySet.SetFirstLineIndent( TabPropertySet.DEFAULT_WIDTH * -1 )
normal_numbered.ParagraphPropertySet.SetLeftIndent ( TabPropertySet.DEFAULT_WIDTH )
result.ParagraphStyles.append( normal_numbered )
normal_numbered2 = result.ParagraphStyles.Normal.Copy()
normal_numbered2.SetName( 'Normal Numbered 2' )
normal_numbered2.ParagraphPropertySet.SetFirstLineIndent( TabPropertySet.DEFAULT_WIDTH * -1 )
normal_numbered2.ParagraphPropertySet.SetLeftIndent ( TabPropertySet.DEFAULT_WIDTH * 2 )
result.ParagraphStyles.append( normal_numbered2 )
## LIST STYLES
for idx, indent in [ (1, TabPS.DEFAULT_WIDTH ),
(2, TabPS.DEFAULT_WIDTH * 2),
(3, TabPS.DEFAULT_WIDTH * 3) ] :
indent = TabPropertySet.DEFAULT_WIDTH
ps = ParagraphStyle( 'List %s' % idx,
TextStyle( TextPropertySet( result.Fonts.Arial, 22 ) ),
ParagraphPropertySet( space_before = 60,
space_after = 60,
first_line_indent = -indent,
left_indent = indent) )
result.ParagraphStyles.append( ps )
return result
class TAB : pass
class LINE : pass
class RawCode :
def __init__( self, data ) :
self.Data = data
PAGE_NUMBER = RawCode( r'{\field{\fldinst page}}' )
TOTAL_PAGES = RawCode( r'{\field{\fldinst numpages}}' )
SECTION_PAGES = RawCode( r'{\field{\fldinst sectionpages}}' )
ARIAL_BULLET = RawCode( r'{\f2\'95}' )
def _get_jpg_dimensions( fin ):
"""
converted from: http://dev.w3.org/cvsweb/Amaya/libjpeg/rdjpgcom.c?rev=1.2
"""
M_SOF0 = chr( 0xC0 ) # /* Start Of Frame N */
M_SOF1 = chr( 0xC1 ) # /* N indicates which compression process */
M_SOF2 = chr( 0xC2 ) # /* Only SOF0-SOF2 are now in common use */
M_SOF3 = chr( 0xC3 ) #
M_SOF5 = chr( 0xC5 ) # /* NB: codes C4 and CC are NOT SOF markers */
M_SOF6 = chr( 0xC6 ) #
M_SOF7 = chr( 0xC7 ) #
M_SOF9 = chr( 0xC9 ) #
M_SOF10 = chr( 0xCA ) #
M_SOF11 = chr( 0xCB ) #
M_SOF13 = chr( 0xCD ) #
M_SOF14 = chr( 0xCE ) #
M_SOF15 = chr( 0xCF ) #
M_SOI = chr( 0xD8 ) # /* Start Of Image (beginning of datastream) */
M_EOI = chr( 0xD9 ) # /* End Of Image (end of datastream) */
M_FF = chr( 0xFF )
MARKERS = [ M_SOF0, M_SOF1, M_SOF2, M_SOF3,
M_SOF5, M_SOF6, M_SOF7, M_SOF9,
M_SOF10,M_SOF11, M_SOF13, M_SOF14,
M_SOF15 ]
def get_length() :
b1 = fin.read( 1 )
b2 = fin.read( 1 )
return (ord(b1) << 8) + ord(b2)
def next_marker() :
# markers come straight after an 0xFF so skip everything
# up to the first 0xFF that we find
while fin.read(1) != M_FF :
pass
# there can be more than one 0xFF as they can be used
# for padding so we are now looking for the first byte
# that isn't an 0xFF, this will be the marker
while True :
result = fin.read(1)
if result != M_FF :
return result
raise Exception( 'Invalid JPEG' )
# BODY OF THE FUNCTION
if not ((fin.read(1) == M_FF) and (fin.read(1) == M_SOI)) :
raise Exception( 'Invalid Jpeg' )
while True :
marker = next_marker()
# the marker is always followed by two bytes representing the length of the data field
length = get_length ()
if length < 2 : raise Exception( "Erroneous JPEG marker length" )
# if it is a compression process marker then it will contain the dimension of the image
if marker in MARKERS :
# the next byte is the data precision, just skip it
fin.read(1)
# bingo
image_height = get_length()
image_width = get_length()
return image_width, image_height
# just skip whatever data it contains
fin.read( length - 2 )
raise Exception( 'Invalid JPEG, end of stream reached' )
_PNG_HEADER = '\x89\x50\x4e'
def _get_png_dimensions( data ) :
if data[0:3] != _PNG_HEADER :
raise Exception( 'Invalid PNG image' )
width = (ord(data[18]) * 256) + (ord(data[19]))
height = (ord(data[22]) * 256) + (ord(data[23]))
return width, height
def _get_emf_dimensions( fin ):
import struct
def get_DWORD():
return struct.unpack("<L",fin.read(4))[0]
def get_LONG():
return struct.unpack("<l",fin.read(4))[0]
def get_WORD():
return struct.unpack("<H",fin.read(2))[0]
class Empty:
pass
header = Empty()
header.RecordType = get_DWORD() # Record type
header.RecordSize = get_DWORD() # Size of the record in bytes
header.BoundsLeft = get_LONG() # Left inclusive bounds
header.BoundsTop = get_LONG() # Top inclusive bounds
header.BoundsRight = get_LONG() # Right inclusive bounds
header.BoundsBottom = get_LONG() # Bottom inclusive bounds
header.FrameLeft = get_LONG() # Left side of inclusive picture frame
header.FrameTop = get_LONG() # Top side of inclusive picture frame
header.FrameRight = get_LONG() # Right side of inclusive picture frame
header.FrameBottom = get_LONG() # Bottom side of inclusive picture frame
header.Signature = get_DWORD() # Signature ID (always 0x464D4520)
header.Version = get_DWORD() # Version of the metafile
header.Size = get_DWORD() # Size of the metafile in bytes
header.NumOfRecords = get_DWORD() # Number of records in the metafile
header.NumOfHandles = get_WORD() # Number of handles in the handle table
header.Reserved = get_WORD() # Not used (always 0)
header.SizeOfDescrip = get_DWORD() # Size of description string in WORDs
header.OffsOfDescrip = get_DWORD() # Offset of description string in metafile
header.NumPalEntries = get_DWORD() # Number of color palette entries
header.WidthDevPixels = get_LONG() # Width of reference device in pixels
header.HeightDevPixels = get_LONG() # Height of reference device in pixels
header.WidthDevMM = get_LONG() # Width of reference device in millimeters
header.HeightDevMM = get_LONG() # Height of reference device in millimeters
if 0:
klist = sorted(header.__dict__.keys())
for k in klist:
print("%20s:%s" % (k,header.__dict__[k]))
dw = header.FrameRight-header.FrameLeft
dh = header.FrameBottom-header.FrameTop
# convert from 0.01mm units to 1/72in units
return int(dw * 72.0/2540.0), int(dh * 72.0/2540.0)
class Image( RawCode ) :
# Need to add in the width and height in twips as it crashes
# word xp with these values. Still working out the most
# efficient way of getting these values.
# \picscalex100\picscaley100\piccropl0\piccropr0\piccropt0\piccropb0
# picwgoal900\pichgoal281
PNG_LIB = 'pngblip'
JPG_LIB = 'jpegblip'
EMF_LIB = 'emfblip'
PICT_TYPES = { 'png' : PNG_LIB,
'jpg' : JPG_LIB,
'emf' : EMF_LIB}
def __init__( self, infile, **kwargs ) :
if hasattr( infile, 'read' ):
fin = infile
if 'datatype' not in kwargs.keys():
msg = "If passing in a file object, you must also specify type='xxx' where xxx is one of %s" % self.PICT_TYPES.keys()
raise ValueError(msg)
file_name = kwargs.pop('datatype')
else:
fin = file( infile, 'rb' )
file_name = infile
pict_type = self.PICT_TYPES[ file_name[ -3 : ].lower() ]
if pict_type == self.PNG_LIB :
width, height = _get_png_dimensions( fin.read( 100 ) )
elif pict_type == self.JPG_LIB :
width, height = _get_jpg_dimensions( fin )
elif pict_type == self.EMF_LIB :
width, height = _get_emf_dimensions( fin )
# if user specified height or width but not both, then
# scale unspecified dimension to maintain aspect ratio
if ('width' in kwargs) and ('height' not in kwargs):
height = int(height * float(kwargs['width'])/width)
elif ('height' in kwargs) and ('width' not in kwargs):
width = int(width * float(kwargs['height'])/height)
width = kwargs.pop('width',width)
height = kwargs.pop('height', height)
codes = [ pict_type,
'picwgoal%s' % (width * 20),
'pichgoal%s' % (height * 20) ]
# let user specify global scaling
scale = kwargs.pop('scale',100)
for kwarg, code, default in [ ( 'scale_x', 'scalex', scale ),
( 'scale_y', 'scaley', scale ),
( 'crop_left', 'cropl', '0' ),
( 'crop_right', 'cropr', '0' ),
( 'crop_top', 'cropt', '0' ),
( 'crop_bottom', 'cropb', '0' ) ] :
codes.append( 'pic%s%s' % ( code, kwargs.pop( kwarg, default ) ) )
# reset back to the start of the file to get all of it and now
# turn it into hex.
fin.seek( 0, 0 )
image = hexlify( fin.read() )
fin.close()
data = []
for i in range( 0, len( image ), 128 ) :
data.append( image[ i : i + 128 ] )
data = r'{\pict{\%s}%s}' % ( '\\'.join( codes ), '\n'.join( data ) )
RawCode.__init__( self, data )
def ToRawCode( self, var_name ) :
return '%s = RawCode( """%s""" )' % ( var_name, self.Data )
class Text :
def __init__( self, *params ) :
self.Data = None
self.Style = None
self.Properties = None
self.Shading = None
for param in params :
if isinstance( param, TextStyle ) : self.Style = param
elif isinstance( param, TextPS ) : self.Properties = param
elif isinstance( param, ShadingPS ) : self.Shading = param
else :
# otherwise let the rendering custom handler sort it out itself
self.Data = param
def SetData( self, value ) :
self.Data = value
class Inline( list ) :
def __init__( self, *params ) :
super( Inline, self ).__init__()
self.Style = None
self.Properties = None
self.Shading = None
self._append = super( Inline, self ).append
for param in params :
if isinstance( param, TextStyle ) : self.Style = param
elif isinstance( param, TextPS ) : self.Properties = param
elif isinstance( param, ShadingPS ) : self.Shading = param
else :
# otherwise we add to it to our list of elements and let
# the rendering custom handler sort it out itself.
self.append( param )
def append( self, *params ) :
# filter out any that are explicitly None
[ self._append( param ) for param in params if param is not None ]
class Paragraph( list ) :
def __init__( self, *params ) :
super( Paragraph, self ).__init__()
self.Style = None
self.Properties = None
self.Frame = None
self.Shading = None
self._append = super( Paragraph, self ).append
for param in params :
if isinstance( param, ParagraphStyle ) : self.Style = param
elif isinstance( param, ParagraphPS ) : self.Properties = param
elif isinstance( param, FramePS ) : self.Frame = param
elif isinstance( param, ShadingPS ) : self.Shading = param
else :
# otherwise we add to it to our list of elements and let
# the rendering custom handler sort it out itself.
self.append( param )
def append( self, *params ) :
# filter out any that are explicitly None
[ self._append( param ) for param in params if param is not None ]
def insert( self, index, value ) :
if value is not None :
super( Paragraph, self ).insert( index, value )
class Table :
LEFT = 1
RIGHT = 2
CENTER = 3
ALIGNMENT = [ LEFT, RIGHT, CENTER ]
NO_WRAPPING = 1
WRAP_AROUND = 2
WRAPPING = [ NO_WRAPPING, WRAP_AROUND ]
# trrh height of row, 0 means automatically adjust, use negative for an absolute
# trgaph is half of the space between a table cell in width, reduce this one
# to get a really tiny column
def __init__( self, *column_widths, **kwargs ) :
self.Rows = []
self.SetAlignment ( kwargs.pop( 'alignment', self.LEFT ) )
self.SetLeftOffset ( kwargs.pop( 'left_offset', None ) )
self.SetGapBetweenCells( kwargs.pop( 'gap_between_cells', None ) )
self.SetColumnWidths ( *column_widths )
assert not kwargs, 'invalid keyword args %s' % kwargs
def SetAlignment( self, value ) :
assert value is None or value in self.ALIGNMENT
self.Alignment = value or self.LEFT
return self
def SetLeftOffset( self, value ) :
self.LeftOffset = value
return self
def SetGapBetweenCells( self, value ) :
self.GapBetweenCells = value
return self
def SetColumnWidths( self, *column_widths ) :
self.ColumnWidths = column_widths
self.ColumnCount = len( column_widths )
return self
def AddRow( self, *cells ) :
height = None
if isinstance( cells[ 0 ], (IntType, FloatType, LongType) ):
height = int( cells[ 0 ] )
cells = cells[ 1 : ]
# make sure all of the spans add up to the number of columns
# otherwise the table will get corrupted
if self.ColumnCount != sum( [ cell.Span for cell in cells ] ) :
raise Exception( 'ColumnCount != the total of this row\'s cell.Spans.' )
self.Rows.append( ( height, cells ) )
append = AddRow
class Cell( list ) :
"""
\clvertalt Text is top-aligned in cell (the default).
\clvertalc Text is centered vertically in cell.
\clvertalb Text is bottom-aligned in cell.
\cltxlrtb Vertical text aligned left (direction bottom up).
\cltxtbrl Vertical text aligned right (direction top down).
"""
ALIGN_TOP = 1
ALIGN_CENTER = 2
ALIGN_BOTTOM = 3
FLOW_LR_TB = 1
FLOW_RL_TB = 2
FLOW_LR_BT = 3
FLOW_VERTICAL_LR_TB = 4
FLOW_VERTICAL_TB_RL = 5
def __init__( self, *params, **kwargs ) :
super( Cell, self ).__init__()
self.SetFrame ( None )
self.SetMargins( None )
self.SetAlignment( kwargs.get( 'alignment', self.ALIGN_TOP ) )
self.SetFlow ( kwargs.get( 'flow' , self.FLOW_LR_TB ) )
self.SetSpan ( kwargs.get( 'span', 1 ) )
self.SetStartVerticalMerge( kwargs.get( 'start_vertical_merge', False ) )
self.SetVerticalMerge ( kwargs.get( 'vertical_merge', False ) )
self._append = super( Cell, self ).append
for param in params :
if isinstance( param, StringType ) : self.append ( param )
elif isinstance( param, Paragraph ) : self.append ( param )
elif isinstance( param, FramePS ) : self.SetFrame ( param )
elif isinstance( param, MarginsPS ) : self.SetMargins( param )
def SetFrame( self, value ) :
self.Frame = value
return self
def SetMargins( self, value ) :
self.Margins = value
return self
def SetAlignment( self, value ) :
assert value in [ self.ALIGN_TOP, self.ALIGN_CENTER, self.ALIGN_BOTTOM ] #, self.ALIGN_TEXT_TOP_DOWN, self.ALIGN_TEXT_BOTTOM_UP ]
self.Alignment = value
def SetFlow( self, value ) :
assert value in [ self.FLOW_LR_TB, self.FLOW_RL_TB, self.FLOW_LR_BT, self.FLOW_VERTICAL_LR_TB, self.FLOW_VERTICAL_TB_RL ]
self.Flow = value
def SetSpan( self, value ) :
# must be a positive integer
self.Span = int( max( value, 1 ) )
return self
def SetStartVerticalMerge( self, value ) :
self.StartVerticalMerge = False
if value :
self.StartVerticalMerge = True
return self
def SetVerticalMerge( self, value ) :
self.VerticalMerge = False
if value :
self.VerticalMerge = True
return self
def append( self, *params ) :
[ self._append( param ) for param in params ]
class Document :
def __init__( self, style_sheet=None, default_language=None, view_kind=None, view_zoom_kind=None, view_scale=None ) :
self.StyleSheet = style_sheet or MakeDefaultStyleSheet()
self.Sections = AttributedList( Section )
self.SetTitle( None )
self.DefaultLanguage = default_language or Languages.DEFAULT
self.ViewKind = view_kind or ViewKind.DEFAULT
self.ViewZoomKind = view_zoom_kind
self.ViewScale = view_scale
def NewSection( self, *params, **kwargs ) :
result = Section( *params, **kwargs )
self.Sections.append( result )
return result
def SetTitle( self, value ) :
self.Title = value
return self
def Copy( self ) :
result = Document( style_sheet = self.StyleSheet.Copy(),
default_language = self.DefaultLanguage,
view_kind = self.ViewKind,
view_zoom_kind = self.ViewZoomKind,
view_scale = self.ViewScale )
result.SetTitle( self.Title )
result.Sections = self.Sections.Copy()
return result
def TEXT( *params, **kwargs ) :
text_props = TextPropertySet()
text_props.SetFont ( kwargs.get( 'font', None ) )
text_props.SetSize ( kwargs.get( 'size', None ) )
text_props.SetBold ( kwargs.get( 'bold', False ) )
text_props.SetItalic ( kwargs.get( 'italic', False ) )
text_props.SetUnderline( kwargs.get( 'underline', False ) )
text_props.SetColour ( kwargs.get( 'colour', None ) )
if len( params ) == 1 :
return Text( params[ 0 ], text_props )
result = Inline( text_props )
result.append(*params)
return result
def B( *params ) :
text_props = TextPropertySet( bold=True )
if len( params ) == 1 :
return Text( params[ 0 ], text_props )
result = Inline( text_props )
result.append(*params)
return result
def I( *params ) :
text_props = TextPropertySet( italic=True )
if len( params ) == 1 :
return Text( params[ 0 ], text_props )
result = Inline( text_props )
result.append(*params)
return result
def U( *params ) :
text_props = TextPropertySet( underline=True )
if len( params ) == 1 :
return Text( params[ 0 ], text_props )
result = Inline( text_props )
result.append(*params)
return result