├── examples ├── image.jpg ├── img1.png ├── img2.png ├── img3.png ├── img4.png ├── examples2.py ├── merged_cells.py └── examples.py ├── PyRTF ├── __init__.py ├── Styles.py ├── Constants.py ├── PropertySets.py ├── Renderer.py └── Elements.py ├── README ├── PKG-INFO └── setup.py /examples/image.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/pyrtf/master/examples/image.jpg -------------------------------------------------------------------------------- /examples/img1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/pyrtf/master/examples/img1.png -------------------------------------------------------------------------------- /examples/img2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/pyrtf/master/examples/img2.png -------------------------------------------------------------------------------- /examples/img3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/pyrtf/master/examples/img3.png -------------------------------------------------------------------------------- /examples/img4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/grangier/pyrtf/master/examples/img4.png -------------------------------------------------------------------------------- /PyRTF/__init__.py: -------------------------------------------------------------------------------- 1 | from PropertySets import * 2 | from Elements import * 3 | from Styles import * 4 | from Renderer import * 5 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Version 0.45 2 | 3 | Finally, image support!!! Handles PNGs and JPGs. 4 | 5 | See examples2.py for the gory details. 6 | 7 | Simon Cusack, scusack@sourceforge.net 8 | -------------------------------------------------------------------------------- /PKG-INFO: -------------------------------------------------------------------------------- 1 | Metadata-Version: 1.0 2 | Name: PyRTF 3 | Version: 0.45 4 | Summary: PyRTF - Rich Text Format Document Generation 5 | Home-page: http://pyrtf.sourceforge.net/ 6 | Author: Simon Cusack 7 | Author-email: scusack@sourceforge.net 8 | License: http://www.gnu.org/licenses/gpl.html 9 | Description: PyRTF is a pure python module for the efficient generation of rich text format 10 | documents. Supports styles, tables, cell merging, jpg and png images and tries 11 | to maintain compatibility with as many RTF readers as possible. 12 | Keywords: RTF,Rich Text,Rich Text Format,documentation,reports 13 | Platform: Any 14 | Classifier: Development Status :: 4 - Beta 15 | Classifier: Topic :: Text Editors :: Text Processing 16 | Classifier: Topic :: Software Development :: Libraries :: Python Modules 17 | Classifier: Intended Audience :: Developers 18 | Classifier: Programming Language :: Python 19 | Classifier: License :: OSI Approved :: GNU General Public License (GPL) 20 | Classifier: License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) 21 | -------------------------------------------------------------------------------- /examples/examples2.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | sys.path.append( '../' ) 4 | 5 | from PyRTF import * 6 | 7 | def MakeExample1() : 8 | doc = Document() 9 | ss = doc.StyleSheet 10 | section = Section() 11 | doc.Sections.append( section ) 12 | 13 | # text can be added directly to the section 14 | # a paragraph object is create as needed 15 | section.append( 'Image Example 1' ) 16 | 17 | section.append( 'You can add images in one of two ways, either converting the ' 18 | 'image each and every time like;' ) 19 | 20 | image = Image( 'image.jpg' ) 21 | section.append( Paragraph( image ) ) 22 | 23 | section.append( 'Or you can use the image object to convert the image and then ' 24 | 'save it to a raw code element that can be included later.' ) 25 | 26 | fout = file( 'image_tmp.py', 'w' ) 27 | print >> fout, 'from PyRTF import RawCode' 28 | print >> fout 29 | fout.write( image.ToRawCode( 'TEST_IMAGE' ) ) 30 | fout.close() 31 | 32 | import image_tmp 33 | section.append( Paragraph( image_tmp.TEST_IMAGE ) ) 34 | section.append( 'Have a look in image_tmp.py for the converted RawCode.' ) 35 | 36 | section.append( 'here are some png files' ) 37 | for f in [ 'img1.png', 38 | 'img2.png', 39 | 'img3.png', 40 | 'img4.png' ] : 41 | section.append( Paragraph( Image( f ) ) ) 42 | 43 | return doc 44 | 45 | def OpenFile( name ) : 46 | return file( '%s.rtf' % name, 'w' ) 47 | 48 | if __name__ == '__main__' : 49 | DR = Renderer() 50 | 51 | doc1 = MakeExample1() 52 | 53 | DR.Write( doc1, OpenFile( 'Image1' ) ) 54 | 55 | print "Finished" 56 | 57 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | """PyRTF - Rich Text Format Document Generation 2 | 3 | PyRTF is a pure python module for the efficient generation of rich text format 4 | documents. Supports styles, tables, cell merging, jpg and png images and tries 5 | to maintain compatibility with as many RTF readers as possible. """ 6 | 7 | classifiers = """\ 8 | Development Status :: 4 - Beta 9 | Topic :: Text Editors :: Text Processing 10 | Topic :: Software Development :: Libraries :: Python Modules 11 | Intended Audience :: Developers 12 | Programming Language :: Python 13 | License :: OSI Approved :: GNU General Public License (GPL) 14 | License :: OSI Approved :: GNU Library or Lesser General Public License (LGPL) 15 | """ 16 | 17 | import sys 18 | from distutils.core import setup 19 | 20 | if sys.version_info < (2, 3): 21 | _setup = setup 22 | def setup(**kwargs): 23 | if kwargs.has_key("classifiers"): 24 | del kwargs["classifiers"] 25 | _setup(**kwargs) 26 | 27 | doclines = __doc__.split("\n") 28 | 29 | setup( name = 'PyRTF', 30 | version = '0.45', 31 | author = 'Simon Cusack', 32 | author_email = 'scusack@sourceforge.net', 33 | url = 'http://pyrtf.sourceforge.net/', 34 | license = 'http://www.gnu.org/licenses/gpl.html', 35 | platforms = [ 'Any' ], 36 | description = doclines[0], 37 | classifiers = filter( None, classifiers.split( '\n' ) ), 38 | long_description = '\n'.join( doclines[2:] ), 39 | keywords = ( 'RTF', 40 | 'Rich Text', 41 | 'Rich Text Format', 42 | 'documentation', 43 | 'reports' ), 44 | packages = [ 'PyRTF', ], 45 | package_dir = { '' : '.' } ) 46 | -------------------------------------------------------------------------------- /examples/merged_cells.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append( '..' ) 3 | 4 | from PyRTF import * 5 | 6 | def MergedCells( ) : 7 | # another test for the merging of cells in a document 8 | doc = Document() 9 | section = Section() 10 | doc.Sections.append( section ) 11 | 12 | # create the table that will get used for all of the "bordered" content 13 | 14 | col1 = 1000 15 | col2 = 1000 16 | col3 = 1000 17 | col4 = 2000 18 | 19 | section.append( 'Table Two' ) 20 | 21 | table = Table( col1, col2, col3 ) 22 | table.AddRow( Cell( 'A-one' ), Cell( 'A-two' ), Cell( 'A-three' ) ) 23 | table.AddRow( Cell( 'A-one' ), Cell( 'A-two', span=2 ) ) 24 | table.AddRow( Cell( 'A-one', span=3 ) ) 25 | table.AddRow( Cell( 'A-one' ), Cell( 'A-two' ), Cell( 'A-three' ) ) 26 | table.AddRow( Cell( 'A-one', span=2 ), Cell( 'A-two' ) ) 27 | section.append( table ) 28 | 29 | section.append( 'Table Two' ) 30 | 31 | table = Table( col1, col2, col3 ) 32 | table.AddRow( Cell( 'A-one' ), Cell( 'A-two', vertical_merge=True ), Cell( 'A-three' ) ) 33 | table.AddRow( Cell( 'A-one' ), Cell( vertical_merge=True ), Cell( 'A-three' ) ) 34 | table.AddRow( Cell( 'A-one' ), Cell( 'A-two', start_vertical_merge=True ), Cell( 'A-three' ) ) 35 | table.AddRow( Cell( 'A-one' ), Cell( vertical_merge=True ), Cell( 'A-three' ) ) 36 | 37 | table.AddRow( Cell( Paragraph( ParagraphPS( alignment=ParagraphPS.CENTER ), 'SPREAD' ), 38 | span=3 ) ) 39 | 40 | table.AddRow( Cell( 'A-one' ), Cell( 'A-two', vertical_merge=True ), Cell( 'A-three' ) ) 41 | table.AddRow( Cell( 'A-one' ), Cell( vertical_merge=True ), Cell( 'A-three' ) ) 42 | table.AddRow( Cell( 'A-one' ), Cell( 'A-two', start_vertical_merge=True ), Cell( 'A-three' ) ) 43 | table.AddRow( Cell( 'A-one' ), Cell( vertical_merge=True ), Cell( 'A-three' ) ) 44 | 45 | section.append( table ) 46 | 47 | # 48 | section.append( 'Table Three' ) 49 | table = Table( col1, col2, col3, col4 ) 50 | table.AddRow( Cell( 'This is pretty amazing', flow=Cell.FLOW_LR_BT, start_vertical_merge=True ), 51 | Cell( 'one' ), Cell( 'two' ), Cell( 'three' ) ) 52 | 53 | for i in range( 10 ) : 54 | table.AddRow( Cell( vertical_merge=True ), 55 | Cell( 'one' ), Cell( 'two' ), Cell( 'three' ) ) 56 | section.append( table ) 57 | 58 | section.append( 'Table Four' ) 59 | table = Table( col4, col1, col2, col3 ) 60 | table.AddRow( Cell( 'one' ), Cell( 'two' ), Cell( 'three' ), 61 | Cell( 'This is pretty amazing', flow=Cell.FLOW_RL_TB, start_vertical_merge=True ) ) 62 | 63 | for i in range( 10 ) : 64 | table.AddRow( Cell( 'one' ), Cell( 'two' ), Cell( 'three' ), 65 | Cell( vertical_merge=True )) 66 | section.append( table ) 67 | 68 | 69 | return doc 70 | 71 | if __name__ == '__main__' : 72 | renderer = Renderer() 73 | 74 | renderer.Write( MergedCells(), file( 'MergedCells.rtf', 'w' ) ) 75 | 76 | print "Finished" 77 | 78 | 79 | -------------------------------------------------------------------------------- /PyRTF/Styles.py: -------------------------------------------------------------------------------- 1 | """ 2 | A Styles is a collection of PropertySets that can be applied to a particular RTF element. 3 | 4 | At present there are only two, Text and Paragraph but ListStyles will be added soon too. 5 | 6 | 7 | """ 8 | 9 | from PropertySets import * 10 | 11 | class TextStyle : 12 | def __init__( self, text_props, name=None, shading_props=None ) : 13 | self.SetTextPropertySet ( text_props ) 14 | self.SetName ( name ) 15 | self.SetShadingPropertySet( shading_props ) 16 | 17 | def Copy( self ) : 18 | return deepcopy( self ) 19 | 20 | def SetName( self, value ) : 21 | self.Name = value 22 | return self 23 | 24 | def SetTextPropertySet( self, value ) : 25 | assert isinstance( value, TextPropertySet ) 26 | self.TextPropertySet = value 27 | return self 28 | 29 | def SetShadingPropertySet( self, value ) : 30 | assert value is None or isinstance( value, ShadingPropertySet ) 31 | self.ShadingPropertySet = value or ShadingPropertySet() 32 | return self 33 | 34 | class ParagraphStyle : 35 | def __init__( self, name, text_style, paragraph_props=None, frame_props=None, shading_props=None ) : 36 | 37 | # A style must have Font and a Font Size but the Text property set doesn't 38 | # make these mandatory so that they can be used for overrides so at this point 39 | # we need to make sure that that we have these values set 40 | if not text_style.TextPropertySet.Font : raise Exception( 'Paragraph Styles must have a Font specified.' ) 41 | if not text_style.TextPropertySet.Size : raise Exception( 'Paragraph Styles must have a Font Size specified.' ) 42 | 43 | self.SetName ( name ) 44 | self.SetTextStyle ( text_style ) 45 | self.SetParagraphPropertySet( paragraph_props ) 46 | self.SetFramePropertySet ( frame_props ) 47 | self.SetShadingPropertySet ( shading_props ) 48 | 49 | self.SetBasedOn( None ) 50 | self.SetNext ( None ) 51 | 52 | def Copy( self ) : 53 | return deepcopy( self ) 54 | 55 | def SetName( self, value ) : 56 | self.Name = value 57 | return self 58 | 59 | def SetTextStyle( self, value ) : 60 | assert isinstance( value, TextStyle ) 61 | self.TextStyle = value 62 | return self 63 | 64 | def SetParagraphPropertySet( self, value ) : 65 | assert value is None or isinstance( value, ParagraphPropertySet ) 66 | self.ParagraphPropertySet = value or ParagraphPropertySet() 67 | return self 68 | 69 | def SetFramePropertySet( self, value ) : 70 | assert value is None or isinstance( value, FramePropertySet ) 71 | self.FramePropertySet = value or FramePropertySet() 72 | return self 73 | 74 | def SetShadingPropertySet( self, value ) : 75 | """Set the background shading for the paragraph.""" 76 | 77 | assert value is None or isinstance( value, ShadingPropertySet ) 78 | self.ShadingPropertySet = value or ShadingPropertySet() 79 | return self 80 | 81 | def SetBasedOn( self, value ) : 82 | """Set the Paragraph Style that this one is based on.""" 83 | 84 | assert not value or isinstance( value, ParagraphStyle ) 85 | self.BasedOn = value 86 | return self 87 | 88 | def SetNext( self, value ) : 89 | """Set the Paragraph Style that should follow this one.""" 90 | 91 | assert not value or isinstance( value, ParagraphStyle ) 92 | self.Next = value 93 | return self 94 | -------------------------------------------------------------------------------- /PyRTF/Constants.py: -------------------------------------------------------------------------------- 1 | class ViewKind : 2 | """An integer (0-5) that represents the view mode of the document.""" 3 | 4 | NONE = 0 5 | PageLayout = 1 6 | Outline = 2 7 | MasterDocument = 3 8 | Normal = 4 9 | OnlineLayout = 5 10 | 11 | DEFAULT = PageLayout 12 | 13 | def _IsValid( cls, value ) : 14 | return value in [ 0, 1, 2, 3, 4, 5 ] 15 | IsValid = classmethod( _IsValid ) 16 | 17 | class ViewScale : 18 | """Zoom level of the document; the N argument is a value representing a percentage (the default is 100).""" 19 | 20 | def _IsValid( cls, value ) : 21 | return value is None or (0 < value < 101) 22 | IsValid = classmethod( _IsValid ) 23 | 24 | class ViewZoomKind : 25 | """An integer (0 to 2) that represents the zoom kind of the document.""" 26 | 27 | NONE = 0 28 | FullPage = 1 29 | BestFit = 2 30 | 31 | def _IsValid( cls, value ) : 32 | return value in [ None, 0, 1, 2 ] 33 | IsValid = classmethod( _IsValid ) 34 | 35 | 36 | class Languages : 37 | NoLanguage = 1024 38 | Albanian = 1052 39 | Arabic = 1025 40 | Bahasa = 1057 41 | BelgianDutch = 2067 42 | BelgianFrench = 2060 43 | BrazilianPortuguese = 1046 44 | Bulgarian = 1026 45 | Catalan = 1027 46 | CroatoSerbianLatin = 1050 47 | Czech = 1029 48 | Danish = 1030 49 | Dutch = 1043 50 | EnglishAustralian = 3081 51 | EnglishUK = 2057 52 | EnglishUS = 1033 53 | Finnish = 1035 54 | French = 1036 55 | FrenchCanadian = 3084 56 | German = 1031 57 | Greek = 1032 58 | Hebrew = 1037 59 | Hungarian = 1038 60 | Icelandic = 1039 61 | Italian = 1040 62 | Japanese = 1041 63 | Korean = 1042 64 | NorwegianBokmal = 1044 65 | NorwegianNynorsk = 2068 66 | Polish = 1045 67 | Portuguese = 2070 68 | RhaetoRomanic = 1047 69 | Romanian = 1048 70 | Russian = 1049 71 | SerboCroatianCyrillic = 2074 72 | SimplifiedChinese = 2052 73 | Slovak = 1051 74 | SpanishCastilian = 1034 75 | SpanishMexican = 2058 76 | Swedish = 1053 77 | SwissFrench = 4108 78 | SwissGerman = 2055 79 | SwissItalian = 2064 80 | Thai = 1054 81 | TraditionalChinese = 1028 82 | Turkish = 1055 83 | Urdu = 1056 84 | SesothoSotho = 1072 85 | Afrikaans = 1078 86 | Zulu = 1077 87 | Xhosa = 1076 88 | Venda = 1075 89 | Tswana = 1074 90 | Tsonga = 1073 91 | FarsiPersian = 1065 92 | 93 | Codes = [ 1024, 94 | 1052, 95 | 1025, 96 | 1057, 97 | 2067, 98 | 2060, 99 | 1046, 100 | 1026, 101 | 1027, 102 | 1050, 103 | 1029, 104 | 1030, 105 | 1043, 106 | 3081, 107 | 2057, 108 | 1033, 109 | 1035, 110 | 1036, 111 | 3084, 112 | 1031, 113 | 1032, 114 | 1037, 115 | 1038, 116 | 1039, 117 | 1040, 118 | 1041, 119 | 1042, 120 | 1044, 121 | 2068, 122 | 1045, 123 | 2070, 124 | 1047, 125 | 1048, 126 | 1049, 127 | 2074, 128 | 2052, 129 | 1051, 130 | 1034, 131 | 2058, 132 | 1053, 133 | 4108, 134 | 2055, 135 | 2064, 136 | 1054, 137 | 1028, 138 | 1055, 139 | 1056, 140 | 1072, 141 | 1078, 142 | 1077, 143 | 1076, 144 | 1075, 145 | 1074, 146 | 1073, 147 | 1065 ] 148 | 149 | # make it Australian as that is what I use most of the time 150 | DEFAULT = EnglishAustralian 151 | 152 | def _IsValid( cls, value ) : 153 | return value in cls.Codes 154 | IsValid = classmethod( _IsValid ) 155 | 156 | if __name__ == '__main__' : 157 | PrintHexTable() -------------------------------------------------------------------------------- /examples/examples.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | import sys 4 | sys.path.append( '../' ) 5 | 6 | from PyRTF import * 7 | 8 | SAMPLE_PARA = """The play opens one year after the death of Richard II, and 9 | King Henry is making plans for a crusade to the Holy Land to cleanse himself 10 | of the guilt he feels over the usurpation of Richard's crown. But the crusade 11 | must be postponed when Henry learns that Welsh rebels, led by Owen Glendower, 12 | have defeated and captured Mortimer. Although the brave Henry Percy, nicknamed 13 | Hotspur, has quashed much of the uprising, there is still much trouble in 14 | Scotland. King Henry has a deep admiration for Hotspur and he longs for his 15 | own son, Prince Hal, to display some of Hotspur's noble qualities. Hal is more 16 | comfortable in a tavern than on the battlefield, and he spends his days 17 | carousing with riff-raff in London. But King Henry also has his problems with 18 | the headstrong Hotspur, who refuses to turn over his prisoners to the state as 19 | he has been so ordered. Westmoreland tells King Henry that Hotspur has many of 20 | the traits of his uncle, Thomas Percy, the Earl of Worcester, and defying 21 | authority runs in the family.""" 22 | 23 | def MakeExample1() : 24 | doc = Document() 25 | ss = doc.StyleSheet 26 | section = Section() 27 | doc.Sections.append( section ) 28 | 29 | # text can be added directly to the section 30 | # a paragraph object is create as needed 31 | section.append( 'Example 1' ) 32 | 33 | # blank paragraphs are just empty strings 34 | section.append( '' ) 35 | 36 | # a lot of useful documents can be created 37 | # with little more than this 38 | section.append( 'A lot of useful documents can be created ' 39 | 'in this way, more advance formating is available ' 40 | 'but a lot of users just want to see their data come out ' 41 | 'in something other than a text file.' ) 42 | return doc 43 | 44 | def MakeExample2() : 45 | doc = Document() 46 | ss = doc.StyleSheet 47 | section = Section() 48 | doc.Sections.append( section ) 49 | 50 | # things really only get interesting after you 51 | # start to use styles to spice things up 52 | p = Paragraph( ss.ParagraphStyles.Heading1 ) 53 | p.append( 'Example 2' ) 54 | section.append( p ) 55 | 56 | p = Paragraph( ss.ParagraphStyles.Normal ) 57 | p.append( 'In this case we have used a two styles. ' 58 | 'The first paragraph is marked with the Heading1 style so that it ' 59 | 'will appear differently to the user. ') 60 | section.append( p ) 61 | 62 | p = Paragraph() 63 | p.append( 'Notice that after I have changed the style of the paragraph ' 64 | 'all subsequent paragraphs have that style automatically. ' 65 | 'This saves typing and is the default behaviour for RTF documents.' ) 66 | section.append( p ) 67 | 68 | p = Paragraph() 69 | p.append( 'I also happen to like Arial so our base style is Arial not Times New Roman.' ) 70 | section.append( p ) 71 | 72 | p = Paragraph() 73 | p.append( 'It is also possible to provide overrides for element of a style. ', 74 | 'For example I can change just the font ', 75 | TEXT( 'size', size=48 ), 76 | ' or ', 77 | TEXT( 'typeface', font=ss.Fonts.Impact ) , 78 | '.' ) 79 | section.append( p ) 80 | 81 | p = Paragraph() 82 | p.append( 'The paragraph itself can also be overridden in lots of ways, tabs, ' 83 | 'borders, alignment, etc can all be modified either in the style or as an ' 84 | 'override during the creation of the paragraph. ' 85 | 'The next paragraph demonstrates custom tab widths and embedded ' 86 | 'carriage returns, ie new line markers that do not cause a paragraph break.' ) 87 | section.append( p ) 88 | 89 | # ParagraphPS is an alias for ParagraphPropertySet 90 | para_props = ParagraphPS( tabs = [ TabPS( width=TabPS.DEFAULT_WIDTH ), 91 | TabPS( width=TabPS.DEFAULT_WIDTH * 2 ), 92 | TabPS( width=TabPS.DEFAULT_WIDTH ) ] ) 93 | p = Paragraph( ss.ParagraphStyles.Normal, para_props ) 94 | p.append( 'Left Word', TAB, 'Middle Word', TAB, 'Right Word', LINE, 95 | 'Left Word', TAB, 'Middle Word', TAB, 'Right Word' ) 96 | section.append( p ) 97 | 98 | section.append( 'The alignment of tabs and style can also be controlled. ' 99 | 'The following paragraph demonstrates how to use flush right tabs' 100 | 'and leader dots.' ) 101 | 102 | para_props = ParagraphPS( tabs = [ TabPS( section.TwipsToRightMargin(), 103 | alignment = TabPS.RIGHT, 104 | leader = TabPS.DOTS ) ] ) 105 | p = Paragraph( ss.ParagraphStyles.Normal, para_props ) 106 | p.append( 'Before Dots', TAB, 'After Dots' ) 107 | section.append( p ) 108 | 109 | section.append( 'Paragraphs can also be indented, the following is all at the ' 110 | 'same indent level and the one after it has the first line ' 111 | 'at a different indent to the rest. The third has the ' 112 | 'first line going in the other direction and is also separated ' 113 | 'by a page break. Note that the ' 114 | 'FirstLineIndent is defined as being the difference from the LeftIndent.' ) 115 | 116 | section.append( 'The following text was copied from http://www.shakespeare-online.com/plots/1kh4ps.html.' ) 117 | 118 | para_props = ParagraphPS() 119 | para_props.SetLeftIndent( TabPropertySet.DEFAULT_WIDTH * 3 ) 120 | p = Paragraph( ss.ParagraphStyles.Normal, para_props ) 121 | p.append( SAMPLE_PARA ) 122 | section.append( p ) 123 | 124 | para_props = ParagraphPS() 125 | para_props.SetFirstLineIndent( TabPropertySet.DEFAULT_WIDTH * -2 ) 126 | para_props.SetLeftIndent( TabPropertySet.DEFAULT_WIDTH * 3 ) 127 | p = Paragraph( ss.ParagraphStyles.Normal, para_props ) 128 | p.append( SAMPLE_PARA ) 129 | section.append( p ) 130 | 131 | # do a page 132 | para_props = ParagraphPS() 133 | para_props.SetPageBreakBefore( True ) 134 | para_props.SetFirstLineIndent( TabPropertySet.DEFAULT_WIDTH ) 135 | para_props.SetLeftIndent( TabPropertySet.DEFAULT_WIDTH ) 136 | p = Paragraph( ss.ParagraphStyles.Normal, para_props ) 137 | p.append( SAMPLE_PARA ) 138 | section.append( p ) 139 | 140 | return doc 141 | 142 | 143 | def MakeExample3() : 144 | doc = Document() 145 | ss = doc.StyleSheet 146 | section = Section() 147 | doc.Sections.append( section ) 148 | 149 | p = Paragraph( ss.ParagraphStyles.Heading1 ) 150 | p.append( 'Example 3' ) 151 | section.append( p ) 152 | 153 | # changes what is now the default style of Heading1 back to Normal 154 | p = Paragraph( ss.ParagraphStyles.Normal ) 155 | p.append( 'Example 3 demonstrates tables, tables represent one of the ' 156 | 'harder things to control in RTF as they offer alot of ' 157 | 'flexibility in formatting and layout.' ) 158 | section.append( p ) 159 | 160 | section.append( 'Table columns are specified in widths, the following example ' 161 | 'consists of a table with 3 columns, the first column is ' 162 | '7 tab widths wide, the next two are 3 tab widths wide. ' 163 | 'The widths chosen are arbitrary, they do not have to be ' 164 | 'multiples of tab widths.' ) 165 | 166 | table = Table( TabPS.DEFAULT_WIDTH * 7, 167 | TabPS.DEFAULT_WIDTH * 3, 168 | TabPS.DEFAULT_WIDTH * 3 ) 169 | c1 = Cell( Paragraph( 'Row One, Cell One' ) ) 170 | c2 = Cell( Paragraph( 'Row One, Cell Two' ) ) 171 | c3 = Cell( Paragraph( 'Row One, Cell Three' ) ) 172 | table.AddRow( c1, c2, c3 ) 173 | 174 | c1 = Cell( Paragraph( ss.ParagraphStyles.Heading2, 'Heading2 Style' ) ) 175 | c2 = Cell( Paragraph( ss.ParagraphStyles.Normal, 'Back to Normal Style' ) ) 176 | c3 = Cell( Paragraph( 'More Normal Style' ) ) 177 | table.AddRow( c1, c2, c3 ) 178 | 179 | c1 = Cell( Paragraph( ss.ParagraphStyles.Heading2, 'Heading2 Style' ) ) 180 | c2 = Cell( Paragraph( ss.ParagraphStyles.Normal, 'Back to Normal Style' ) ) 181 | c3 = Cell( Paragraph( 'More Normal Style' ) ) 182 | table.AddRow( c1, c2, c3 ) 183 | 184 | section.append( table ) 185 | section.append( 'Different frames can also be specified for each cell in the table ' 186 | 'and each frame can have a different width and style for each border.' ) 187 | 188 | thin_edge = BorderPS( width=20, style=BorderPS.SINGLE ) 189 | thick_edge = BorderPS( width=80, style=BorderPS.SINGLE ) 190 | 191 | thin_frame = FramePS( thin_edge, thin_edge, thin_edge, thin_edge ) 192 | thick_frame = FramePS( thick_edge, thick_edge, thick_edge, thick_edge ) 193 | mixed_frame = FramePS( thin_edge, thick_edge, thin_edge, thick_edge ) 194 | 195 | table = Table( TabPS.DEFAULT_WIDTH * 3, TabPS.DEFAULT_WIDTH * 3, TabPS.DEFAULT_WIDTH * 3 ) 196 | c1 = Cell( Paragraph( 'R1C1' ), thin_frame ) 197 | c2 = Cell( Paragraph( 'R1C2' ) ) 198 | c3 = Cell( Paragraph( 'R1C3' ), thick_frame ) 199 | table.AddRow( c1, c2, c3 ) 200 | 201 | c1 = Cell( Paragraph( 'R2C1' ) ) 202 | c2 = Cell( Paragraph( 'R2C2' ) ) 203 | c3 = Cell( Paragraph( 'R2C3' ) ) 204 | table.AddRow( c1, c2, c3 ) 205 | 206 | c1 = Cell( Paragraph( 'R3C1' ), mixed_frame ) 207 | c2 = Cell( Paragraph( 'R3C2' ) ) 208 | c3 = Cell( Paragraph( 'R3C3' ), mixed_frame ) 209 | table.AddRow( c1, c2, c3 ) 210 | 211 | section.append( table ) 212 | 213 | section.append( 'In fact frames can be applied to paragraphs too, not just cells.' ) 214 | 215 | p = Paragraph( ss.ParagraphStyles.Normal, thin_frame ) 216 | p.append( 'This whole paragraph is in a frame.' ) 217 | section.append( p ) 218 | return doc 219 | 220 | def MakeExample4() : 221 | doc = Document() 222 | ss = doc.StyleSheet 223 | section = Section() 224 | doc.Sections.append( section ) 225 | 226 | section.append( 'Example 4' ) 227 | section.append( 'This example test changing the colour of fonts.' ) 228 | 229 | # 230 | # text properties can be specified in two ways, either a 231 | # Text object can have its text properties specified like: 232 | tps = TextPS( colour=ss.Colours.Red ) 233 | text = Text( 'RED', tps ) 234 | p = Paragraph() 235 | p.append( 'This next word should be in ', text ) 236 | section.append( p ) 237 | 238 | # or the shortcut TEXT function can be used like: 239 | p = Paragraph() 240 | p.append( 'This next word should be in ', TEXT( 'Green', colour=ss.Colours.Green ) ) 241 | section.append( p ) 242 | 243 | # when specifying colours it is important to use the colours from the 244 | # style sheet supplied with the document and not the StandardColours object 245 | # each document get its own copy of the stylesheet so that changes can be 246 | # made on a document by document basis without mucking up other documents 247 | # that might be based on the same basic stylesheet 248 | 249 | return doc 250 | 251 | 252 | # 253 | # DO SOME WITH HEADERS AND FOOTERS 254 | def MakeExample5() : 255 | doc = Document() 256 | ss = doc.StyleSheet 257 | section = Section() 258 | doc.Sections.append( section ) 259 | 260 | section.Header.append( 'This is the header' ) 261 | section.Footer.append( 'This is the footer' ) 262 | 263 | p = Paragraph( ss.ParagraphStyles.Heading1 ) 264 | p.append( 'Example 5' ) 265 | section.append( p ) 266 | 267 | # blank paragraphs are just empty strings 268 | section.append( '' ) 269 | 270 | p = Paragraph( ss.ParagraphStyles.Normal ) 271 | p.append( 'This document has a header and a footer.' ) 272 | section.append( p ) 273 | 274 | return doc 275 | 276 | def MakeExample6() : 277 | doc = Document() 278 | ss = doc.StyleSheet 279 | section = Section() 280 | doc.Sections.append( section ) 281 | 282 | section.FirstHeader.append( 'This is the header for the first page.' ) 283 | section.FirstFooter.append( 'This is the footer for the first page.' ) 284 | 285 | section.Header.append( 'This is the header that will appear on subsequent pages.' ) 286 | section.Footer.append( 'This is the footer that will appear on subsequent pages.' ) 287 | 288 | p = Paragraph( ss.ParagraphStyles.Heading1 ) 289 | p.append( 'Example 6' ) 290 | section.append( p ) 291 | 292 | # blank paragraphs are just empty strings 293 | section.append( '' ) 294 | 295 | p = Paragraph( ss.ParagraphStyles.Normal ) 296 | p.append( 'This document has different headers and footers for the first and then subsequent pages. ' 297 | 'If you insert a page break you should see a different header and footer.' ) 298 | section.append( p ) 299 | 300 | return doc 301 | 302 | def MakeExample7() : 303 | doc = Document() 304 | ss = doc.StyleSheet 305 | section = Section() 306 | doc.Sections.append( section ) 307 | 308 | section.FirstHeader.append( 'This is the header for the first page.' ) 309 | section.FirstFooter.append( 'This is the footer for the first page.' ) 310 | 311 | section.Header.append( 'This is the header that will appear on subsequent pages.' ) 312 | section.Footer.append( 'This is the footer that will appear on subsequent pages.' ) 313 | 314 | p = Paragraph( ss.ParagraphStyles.Heading1 ) 315 | p.append( 'Example 7' ) 316 | section.append( p ) 317 | 318 | p = Paragraph( ss.ParagraphStyles.Normal ) 319 | p.append( 'This document has different headers and footers for the first and then subsequent pages. ' 320 | 'If you insert a page break you should see a different header and footer.' ) 321 | section.append( p ) 322 | 323 | p = Paragraph( ss.ParagraphStyles.Normal, ParagraphPS().SetPageBreakBefore( True ) ) 324 | p.append( 'This should be page 2 ' 325 | 'with the subsequent headers and footers.' ) 326 | section.append( p ) 327 | 328 | section = Section( break_type=Section.PAGE, first_page_number=1 ) 329 | doc.Sections.append( section ) 330 | 331 | section.FirstHeader.append( 'This is the header for the first page of the second section.' ) 332 | section.FirstFooter.append( 'This is the footer for the first page of the second section.' ) 333 | 334 | section.Header.append( 'This is the header that will appear on subsequent pages for the second section.' ) 335 | p = Paragraph( 'This is the footer that will appear on subsequent pages for the second section.', LINE ) 336 | p.append( PAGE_NUMBER, ' of ', SECTION_PAGES ) 337 | section.Footer.append( p ) 338 | 339 | section.append( 'This is the first page' ) 340 | 341 | p = Paragraph( ParagraphPS().SetPageBreakBefore( True ), 'This is the second page' ) 342 | section.append( p ) 343 | 344 | return doc 345 | 346 | def OpenFile( name ) : 347 | return file( '%s.rtf' % name, 'w' ) 348 | 349 | if __name__ == '__main__' : 350 | DR = Renderer() 351 | 352 | doc1 = MakeExample1() 353 | doc2 = MakeExample2() 354 | doc3 = MakeExample3() 355 | doc4 = MakeExample4() 356 | doc5 = MakeExample5() 357 | doc6 = MakeExample6() 358 | doc7 = MakeExample7() 359 | 360 | DR.Write( doc1, OpenFile( '1' ) ) 361 | DR.Write( doc2, OpenFile( '2' ) ) 362 | DR.Write( doc3, OpenFile( '3' ) ) 363 | DR.Write( doc4, OpenFile( '4' ) ) 364 | DR.Write( doc5, OpenFile( '5' ) ) 365 | DR.Write( doc6, OpenFile( '6' ) ) 366 | DR.Write( doc7, OpenFile( '7' ) ) 367 | 368 | print "Finished" 369 | -------------------------------------------------------------------------------- /PyRTF/PropertySets.py: -------------------------------------------------------------------------------- 1 | """ 2 | PropertySets group common attributes together, each property set is used to control a specific part of the rendering. 3 | 4 | PropertySets can be used in different elements of the document. 5 | 6 | For example the FramePropertySet is used in paragraphs, tables, cells, etc. 7 | 8 | The TextPropertySet can be used for text or in a Paragraph Style. 9 | 10 | """ 11 | 12 | from types import StringType 13 | from copy import deepcopy 14 | 15 | 16 | # 17 | # We need some basic Type like fonts, colours and paper definitions 18 | # 19 | def MakeAttributeName( value ) : 20 | assert value and type( value ) is StringType 21 | value = value.replace( ' ', '' ) 22 | return value 23 | 24 | class AttributedList( list ) : 25 | def __init__( self, accepted_type=None ) : 26 | super( AttributedList, self ).__init__() 27 | self.AcceptedType = accepted_type 28 | self._append = super( AttributedList, self ).append 29 | 30 | def append( self, *values ) : 31 | for value in values : 32 | if self.AcceptedType : assert isinstance( value, self.AcceptedType ) 33 | 34 | self._append( value ) 35 | 36 | name = getattr( value, 'Name', None ) 37 | if name : 38 | name = MakeAttributeName( value.Name ) 39 | setattr( self, name, value ) 40 | 41 | def __deepcopy__( self, memo ) : 42 | result = self.__class__() 43 | result.append( *self[:] ) 44 | return result 45 | 46 | class Colour : 47 | def __init__( self, name, red, green, blue ) : 48 | self.SetName ( name ) 49 | self.SetRed ( red ) 50 | self.SetGreen( green ) 51 | self.SetBlue ( blue ) 52 | 53 | def SetName( self, value ) : 54 | self.Name = value 55 | return self 56 | 57 | def SetRed( self, value ) : 58 | self.Red = value 59 | return self 60 | 61 | def SetGreen( self, value ) : 62 | self.Green = value 63 | return self 64 | 65 | def SetBlue( self, value ) : 66 | self.Blue = value 67 | return self 68 | 69 | class Colours( AttributedList ) : 70 | def __init__( self ) : 71 | super( Colours, self ).__init__( Colour ) 72 | 73 | class Font : 74 | def __init__( self, name, family, character_set = 0, pitch = None, panose = None, alternate = None ) : 75 | self.SetName ( name ) 76 | self.SetFamily ( family ) 77 | self.SetCharacterSet( character_set ) 78 | self.SetPitch ( pitch ) 79 | self.SetPanose ( panose ) 80 | self.SetAlternate ( alternate ) 81 | 82 | def SetName( self, value ) : 83 | self.Name = value 84 | return self 85 | 86 | def SetFamily( self, value ) : 87 | self.Family = value 88 | return self 89 | 90 | def SetCharacterSet( self, value ) : 91 | self.CharacterSet = value 92 | return self 93 | 94 | def SetPitch( self, value ) : 95 | self.Pitch = value 96 | return self 97 | 98 | def SetPanose( self, value ) : 99 | self.Panose = value 100 | return self 101 | 102 | def SetAlternate( self, value ) : 103 | self.Alternate = value 104 | return self 105 | 106 | class Fonts( AttributedList ) : 107 | def __init__( self ) : 108 | super( Fonts, self ).__init__( Font ) 109 | 110 | class Paper : 111 | def __init__( self, name, code, description, width, height ) : 112 | self.SetName ( name ) 113 | self.SetCode ( code ) 114 | self.SetDescription( description ) 115 | self.SetWidth ( width ) 116 | self.SetHeight ( height ) 117 | 118 | def SetName( self, value ) : 119 | self.Name = value 120 | return self 121 | 122 | def SetCode( self, value ) : 123 | self.Code = value 124 | return self 125 | 126 | def SetDescription( self, value ) : 127 | self.Description = value 128 | return self 129 | 130 | def SetWidth( self, value ) : 131 | self.Width = value 132 | return self 133 | 134 | def SetHeight( self, value ) : 135 | self.Height = value 136 | return self 137 | 138 | class Papers( AttributedList ) : 139 | def __init__( self ) : 140 | super( Papers, self ).__init__( Paper ) 141 | 142 | # 143 | # Then we have property sets which represent different aspects of Styles 144 | # 145 | class MarginsPropertySet : 146 | def __init__( self, top=None, left=None, bottom=None, right=None ) : 147 | self.SetTop ( top ) 148 | self.SetLeft ( left ) 149 | self.SetBottom( bottom ) 150 | self.SetRight ( right ) 151 | 152 | def SetTop( self, value ) : 153 | self.Top = value 154 | return self 155 | 156 | def SetLeft( self, value ) : 157 | self.Left = value 158 | return self 159 | 160 | def SetBottom( self, value ) : 161 | self.Bottom = value 162 | return self 163 | 164 | def SetRight( self, value ) : 165 | self.Right = value 166 | return self 167 | 168 | class ShadingPropertySet : 169 | HORIZONTAL = 1 170 | VERTICAL = 2 171 | FORWARD_DIAGONAL = 3 172 | BACKWARD_DIAGONAL = 4 173 | VERTICAL_CROSS = 5 174 | DIAGONAL_CROSS = 6 175 | DARK_HORIZONTAL = 7 176 | DARK_VERTICAL = 8 177 | DARK_FORWARD_DIAGONAL = 9 178 | DARK_BACKWARD_DIAGONAL = 10 179 | DARK_VERTICAL_CROSS = 11 180 | DARK_DIAGONAL_CROSS = 12 181 | PATTERNS = [ HORIZONTAL, 182 | VERTICAL, 183 | FORWARD_DIAGONAL, 184 | BACKWARD_DIAGONAL, 185 | VERTICAL_CROSS, 186 | DIAGONAL_CROSS, 187 | DARK_HORIZONTAL, 188 | DARK_VERTICAL, 189 | DARK_FORWARD_DIAGONAL, 190 | DARK_BACKWARD_DIAGONAL, 191 | DARK_VERTICAL_CROSS, 192 | DARK_DIAGONAL_CROSS ] 193 | 194 | def __init__( self, shading=None, pattern=None, foreground=None, background=None ) : 195 | self.SetShading ( shading ) 196 | self.SetForeground( foreground ) 197 | self.SetBackground( background ) 198 | self.SetPattern ( pattern ) 199 | 200 | def __deepcopy__( self, memo ) : 201 | return ShadingPropertySet( self.Shading, 202 | self.Foreground, 203 | self.Background, 204 | self.Pattern ) 205 | 206 | def SetShading( self, value ) : 207 | self.Shading = value 208 | return self 209 | 210 | def SetPattern( self, value ) : 211 | assert value is None or value in self.PATTERNS 212 | self.Pattern = value 213 | return self 214 | 215 | def SetForeground( self, value ) : 216 | assert not value or isinstance( value, Colour ) 217 | self.Foreground = value 218 | return self 219 | 220 | def SetBackground( self, value ) : 221 | assert not value or isinstance( value, Colour ) 222 | self.Background = value 223 | return self 224 | 225 | 226 | class BorderPropertySet : 227 | SINGLE = 1 228 | DOUBLE = 2 229 | SHADOWED = 3 230 | DOUBLED = 4 231 | DOTTED = 5 232 | DASHED = 6 233 | HAIRLINE = 7 234 | STYLES = [ SINGLE, DOUBLE, SHADOWED, DOUBLED, DOTTED, DASHED, HAIRLINE ] 235 | 236 | def __init__( self, width=None, style=None, colour=None, spacing=None ) : 237 | self.SetWidth ( width ) 238 | self.SetStyle ( style or self.SINGLE ) 239 | self.SetColour ( colour ) 240 | self.SetSpacing( spacing ) 241 | 242 | def SetWidth( self, value ) : 243 | self.Width = value 244 | return self 245 | 246 | def SetStyle( self, value ) : 247 | assert value is None or value in self.STYLES 248 | self.Style = value 249 | return self 250 | 251 | def SetColour( self, value ) : 252 | assert value is None or isinstance( value, Colour ) 253 | self.Colour = value 254 | return self 255 | 256 | def SetSpacing( self, value ) : 257 | self.Spacing = value 258 | return self 259 | 260 | class FramePropertySet : 261 | def __init__( self, top=None, left=None, bottom=None, right=None ) : 262 | self.SetTop ( top ) 263 | self.SetLeft ( left ) 264 | self.SetBottom( bottom ) 265 | self.SetRight ( right ) 266 | 267 | def SetTop( self, value ) : 268 | assert value is None or isinstance( value, BorderPropertySet ) 269 | self.Top = value 270 | return self 271 | 272 | def SetLeft( self, value ) : 273 | assert value is None or isinstance( value, BorderPropertySet ) 274 | self.Left = value 275 | return self 276 | 277 | def SetBottom( self, value ) : 278 | assert value is None or isinstance( value, BorderPropertySet ) 279 | self.Bottom = value 280 | return self 281 | 282 | def SetRight( self, value ) : 283 | assert value is None or isinstance( value, BorderPropertySet ) 284 | self.Right = value 285 | return self 286 | 287 | class TabPropertySet : 288 | DEFAULT_WIDTH = 720 289 | 290 | LEFT = 1 291 | RIGHT = 2 292 | CENTER = 3 293 | DECIMAL = 4 294 | ALIGNMENT = [ LEFT, RIGHT, CENTER, DECIMAL ] 295 | 296 | DOTS = 1 297 | HYPHENS = 2 298 | UNDERLINE = 3 299 | THICK_LINE = 4 300 | EQUAL_SIGN = 5 301 | LEADERS = [ DOTS, HYPHENS, UNDERLINE, THICK_LINE, EQUAL_SIGN ] 302 | 303 | def __init__( self, width=None, alignment=None, leader=None ) : 304 | self.SetWidth ( width ) 305 | self.SetAlignment( alignment or self.LEFT ) 306 | self.SetLeader ( leader ) 307 | 308 | def SetWidth( self, value ) : 309 | self.Width = value 310 | return self 311 | 312 | def SetAlignment( self, value ) : 313 | assert value in self.ALIGNMENT 314 | self.Alignment = value 315 | return self 316 | 317 | def SetLeader( self, value ) : 318 | assert not value or value in self.LEADERS 319 | self.Leader = value 320 | return self 321 | 322 | class TextPropertySet : 323 | 324 | def __init__( self, font=None, size=None, bold=None, italic=None, underline=None, colour=None, frame=None, expansion=None ) : 325 | self.SetFont ( font ) 326 | self.SetSize ( size ) 327 | 328 | self.SetBold ( bold or False ) 329 | self.SetItalic ( italic or False ) 330 | self.SetUnderline ( underline or False ) 331 | 332 | self.SetColour( colour ) 333 | self.SetFrame ( frame ) 334 | 335 | self.SetStrikeThrough ( False ) 336 | self.SetDottedUnderline( False ) 337 | self.SetDoubleUnderline( False ) 338 | self.SetWordUnderline ( False ) 339 | self.SetExpansion ( expansion ) 340 | 341 | def Copy( self ) : 342 | return deepcopy( self ) 343 | 344 | def __deepcopy__( self, memo ) : 345 | # the font must remain a reference to the same font that we are looking at 346 | # so we want to stop the recursiveness at this point and return an object 347 | # with the right references. 348 | result = TextPropertySet( self.Font, 349 | self.Size, 350 | self.Bold, 351 | self.Italic, 352 | self.Underline, 353 | self.Colour, 354 | deepcopy( self.Frame, memo ) ) 355 | result.SetStrikeThrough( self.StrikeThrough ) 356 | return result 357 | 358 | def SetFont( self, value ) : 359 | assert not value or isinstance( value, Font ) 360 | self.Font = value 361 | return self 362 | 363 | def SetSize( self, value ) : 364 | self.Size = value 365 | return self 366 | 367 | def SetBold( self, value ) : 368 | self.Bold = False 369 | if value : self.Bold = True 370 | return self 371 | 372 | def SetItalic( self, value ) : 373 | self.Italic = False 374 | if value : self.Italic = True 375 | return self 376 | 377 | def SetUnderline( self, value ) : 378 | self.Underline = False 379 | if value : self.Underline = True 380 | return self 381 | 382 | def SetColour( self, value ) : 383 | assert value is None or isinstance( value, Colour ) 384 | self.Colour = value 385 | return self 386 | 387 | def SetFrame( self, value ) : 388 | assert value is None or isinstance( value, BorderPropertySet ) 389 | self.Frame = value 390 | return self 391 | 392 | def SetStrikeThrough( self, value ) : 393 | self.StrikeThrough = False 394 | if value : self.StrikeThrough = True 395 | return self 396 | 397 | def SetDottedUnderline( self, value ) : 398 | self.DottedUnderline = False 399 | if value : self.DottedUnderline = True 400 | return self 401 | 402 | def SetDoubleUnderline( self, value ) : 403 | self.DoubleUnderline = False 404 | if value : self.DoubleUnderline = True 405 | return self 406 | 407 | def SetWordUnderline( self, value ) : 408 | self.WordUnderline = False 409 | if value : self.WordUnderline = True 410 | return self 411 | 412 | def SetExpansion( self, value ) : 413 | self.Expansion = value 414 | return self 415 | 416 | class ParagraphPropertySet : 417 | LEFT = 1 418 | RIGHT = 2 419 | CENTER = 3 420 | JUSTIFY = 4 421 | DISTRIBUTE = 5 422 | ALIGNMENT = [ LEFT, RIGHT, CENTER, JUSTIFY, DISTRIBUTE ] 423 | 424 | def __init__( self, alignment=None, space_before=None, space_after=None, tabs=None, first_line_indent=None, left_indent=None, right_indent=None, page_break_before=None ) : 425 | self.SetAlignment ( alignment or self.LEFT ) 426 | self.SetSpaceBefore( space_before ) 427 | self.SetSpaceAfter ( space_after ) 428 | 429 | self.Tabs = [] 430 | if tabs : apply( self.SetTabs, tabs ) 431 | 432 | self.SetFirstLineIndent( first_line_indent or None ) 433 | self.SetLeftIndent ( left_indent or None ) 434 | self.SetRightIndent ( right_indent or None ) 435 | 436 | self.SetPageBreakBefore( page_break_before ) 437 | 438 | self.SetSpaceBetweenLines( None ) 439 | 440 | def Copy( self ) : 441 | return deepcopy( self ) 442 | 443 | def SetAlignment( self, value ) : 444 | assert not value or value in self.ALIGNMENT 445 | self.Alignment = value or self.LEFT 446 | return self 447 | 448 | def SetSpaceBefore( self, value ) : 449 | self.SpaceBefore = value 450 | return self 451 | 452 | def SetSpaceAfter( self, value ) : 453 | self.SpaceAfter = value 454 | return self 455 | 456 | def SetTabs( self, *params ) : 457 | self.Tabs = params 458 | return self 459 | 460 | def SetFirstLineIndent( self, value ) : 461 | self.FirstLineIndent = value 462 | return self 463 | 464 | def SetLeftIndent( self, value ) : 465 | self.LeftIndent = value 466 | return self 467 | 468 | def SetRightIndent( self, value ) : 469 | self.RightIndent = value 470 | return self 471 | 472 | def SetSpaceBetweenLines( self, value ) : 473 | self.SpaceBetweenLines = value 474 | return self 475 | 476 | def SetPageBreakBefore( self, value ) : 477 | self.PageBreakBefore = False 478 | if value : self.PageBreakBefore = True 479 | return self 480 | 481 | # Some short cuts to make the code a bit easier to read 482 | MarginsPS = MarginsPropertySet 483 | ShadingPS = ShadingPropertySet 484 | BorderPS = BorderPropertySet 485 | FramePS = FramePropertySet 486 | TabPS = TabPropertySet 487 | TextPS = TextPropertySet 488 | ParagraphPS = ParagraphPropertySet 489 | -------------------------------------------------------------------------------- /PyRTF/Renderer.py: -------------------------------------------------------------------------------- 1 | from types import StringType, ListType, TupleType 2 | from copy import deepcopy 3 | from Elements import * 4 | 5 | DEFAULT_TAB_WIDTH = 720 6 | 7 | ParagraphAlignmentMap = { ParagraphPropertySet.LEFT : 'ql', 8 | ParagraphPropertySet.RIGHT : 'qr', 9 | ParagraphPropertySet.CENTER : 'qc', 10 | ParagraphPropertySet.JUSTIFY : 'qj', 11 | ParagraphPropertySet.DISTRIBUTE : 'qd' } 12 | 13 | TabAlignmentMap = { TabPropertySet.LEFT : '', 14 | TabPropertySet.RIGHT : 'tqr', 15 | TabPropertySet.CENTER : 'tqc', 16 | TabPropertySet.DECIMAL : 'tqdec' } 17 | 18 | TableAlignmentMap = { Table.LEFT : 'trql', 19 | Table.RIGHT : 'trqr', 20 | Table.CENTER : 'trqc' } 21 | 22 | CellAlignmentMap = { Cell.ALIGN_TOP : '', # clvertalt 23 | Cell.ALIGN_CENTER : 'clvertalc', 24 | Cell.ALIGN_BOTTOM : 'clvertalb' } 25 | 26 | CellFlowMap = { Cell.FLOW_LR_TB : '', # cltxlrtb, Text in a cell flows from left to right and top to bottom (default) 27 | Cell.FLOW_RL_TB : 'cltxtbrl', # Text in a cell flows right to left and top to bottom 28 | Cell.FLOW_LR_BT : 'cltxbtlr', # Text in a cell flows left to right and bottom to top 29 | Cell.FLOW_VERTICAL_LR_TB : 'cltxlrtbv', # Text in a cell flows left to right and top to bottom, vertical 30 | Cell.FLOW_VERTICAL_TB_RL : 'cltxtbrlv' } # Text in a cell flows top to bottom and right to left, vertical 31 | 32 | ShadingPatternMap = { ShadingPropertySet.HORIZONTAL : 'bghoriz', 33 | ShadingPropertySet.VERTICAL : 'bgvert', 34 | ShadingPropertySet.FORWARD_DIAGONAL : 'bgfdiag', 35 | ShadingPropertySet.BACKWARD_DIAGONAL : 'bgbdiag', 36 | ShadingPropertySet.VERTICAL_CROSS : 'bgcross', 37 | ShadingPropertySet.DIAGONAL_CROSS : 'bgdcross', 38 | ShadingPropertySet.DARK_HORIZONTAL : 'bgdkhoriz', 39 | ShadingPropertySet.DARK_VERTICAL : 'bgdkvert', 40 | ShadingPropertySet.DARK_FORWARD_DIAGONAL : 'bgdkfdiag', 41 | ShadingPropertySet.DARK_BACKWARD_DIAGONAL : 'bgdkbdiag', 42 | ShadingPropertySet.DARK_VERTICAL_CROSS : 'bgdkcross', 43 | ShadingPropertySet.DARK_DIAGONAL_CROSS : 'bgdkdcross' } 44 | 45 | TabLeaderMap = { TabPropertySet.DOTS : 'tldot', 46 | TabPropertySet.HYPHENS : 'tlhyph', 47 | TabPropertySet.UNDERLINE : 'tlul', 48 | TabPropertySet.THICK_LINE : 'tlth', 49 | TabPropertySet.EQUAL_SIGN : 'tleq' } 50 | 51 | BorderStyleMap = { BorderPropertySet.SINGLE : 'brdrs', 52 | BorderPropertySet.DOUBLE : 'brdrth', 53 | BorderPropertySet.SHADOWED : 'brdrsh', 54 | BorderPropertySet.DOUBLED : 'brdrdb', 55 | BorderPropertySet.DOTTED : 'brdrdot', 56 | BorderPropertySet.DASHED : 'brdrdash', 57 | BorderPropertySet.HAIRLINE : 'brdrhair' } 58 | 59 | SectionBreakTypeMap = { Section.NONE : 'sbknone', 60 | Section.COLUMN : 'sbkcol', 61 | Section.PAGE : 'sbkpage', 62 | Section.EVEN : 'sbkeven', 63 | Section.ODD : 'sbkodd' } 64 | 65 | class Settings( list ) : 66 | def __init__( self ) : 67 | super( Settings, self ).__init__() 68 | self._append = super( Settings, self ).append 69 | 70 | def append( self, value, mask=None, fallback=None ) : 71 | if (value is not 0) and value in [ False, None, '' ] : 72 | if fallback : self._append( self, fallback ) 73 | 74 | else : 75 | if mask : 76 | if value is True : 77 | value = mask 78 | else : 79 | value = mask % value 80 | self._append( value ) 81 | 82 | def Join( self ) : 83 | if self : return r'\%s' % '\\'.join( self ) 84 | return '' 85 | 86 | def __repr__( self ) : 87 | return self.Join() 88 | 89 | class Renderer : 90 | def __init__( self, write_custom_element_callback=None ) : 91 | self.character_style_map = {} 92 | self.paragraph_style_map = {} 93 | self.WriteCustomElement = write_custom_element_callback 94 | 95 | # 96 | # All of the Rend* Functions populate a Settings object with values 97 | # 98 | def _RendPageProperties( self, section, settings, in_section ) : 99 | # this one is different from the others as it takes the settings from a 100 | if in_section : 101 | #paper_size_code = 'psz%s' 102 | paper_width_code = 'pgwsxn%s' 103 | paper_height_code = 'pghsxn%s' 104 | landscape = 'lndscpsxn' 105 | margin_suffix = 'sxn' 106 | 107 | else : 108 | #paper_size_code = 'psz%s' 109 | paper_width_code = 'paperw%s' 110 | paper_height_code = 'paperh%s' 111 | landscape = 'landscape' 112 | margin_suffix = '' 113 | 114 | #settings.append( section.Paper.Code, paper_size_code ) 115 | settings.append( section.Paper.Width, paper_width_code ) 116 | settings.append( section.Paper.Height, paper_height_code ) 117 | 118 | if section.Landscape : 119 | settings.append( landscape ) 120 | 121 | if section.FirstPageNumber : 122 | settings.append( section.FirstPageNumber, 'pgnstarts%s' ) 123 | settings.append( 'pgnrestart' ) 124 | 125 | self._RendMarginsPropertySet( section.Margins, settings, margin_suffix ) 126 | 127 | def _RendShadingPropertySet( self, shading_props, settings, prefix='' ) : 128 | if not shading_props : return 129 | 130 | settings.append( shading_props.Shading, prefix + 'shading%s' ) 131 | settings.append( ShadingPatternMap.get( shading_props.Pattern, False ) ) 132 | 133 | settings.append( self._colour_map.get( shading_props.Foreground, False ), prefix + 'cfpat%s' ) 134 | settings.append( self._colour_map.get( shading_props.Background, False ), prefix + 'cbpat%s' ) 135 | 136 | def _RendBorderPropertySet( self, edge_props, settings ) : 137 | settings.append( BorderStyleMap[ edge_props.Style ] ) 138 | settings.append( edge_props.Width , 'brdrw%s' ) 139 | settings.append( self._colour_map.get( edge_props.Colour, False ), 'brdrcf%s' ) 140 | settings.append( edge_props.Spacing or False , 'brsp%s' ) 141 | 142 | def _RendFramePropertySet( self, frame_props, settings, tag_prefix='' ) : 143 | if not frame_props : return 144 | 145 | if frame_props.Top : 146 | settings.append( tag_prefix + 'brdrt' ) 147 | self._RendBorderPropertySet( frame_props.Top, settings ) 148 | 149 | if frame_props.Left : 150 | settings.append( tag_prefix + 'brdrl' ) 151 | self._RendBorderPropertySet( frame_props.Left, settings ) 152 | 153 | if frame_props.Bottom : 154 | settings.append( tag_prefix + 'brdrb' ) 155 | self._RendBorderPropertySet( frame_props.Bottom, settings ) 156 | 157 | if frame_props.Right : 158 | settings.append( tag_prefix + 'brdrr' ) 159 | self._RendBorderPropertySet( frame_props.Right, settings ) 160 | 161 | def _RendMarginsPropertySet( self, margin_props, settings, suffix='' ) : 162 | if not margin_props : return 163 | 164 | settings.append( margin_props.Top, 'margt' + suffix + '%s' ) 165 | settings.append( margin_props.Left, 'margl' + suffix + '%s' ) 166 | settings.append( margin_props.Bottom, 'margb' + suffix + '%s' ) 167 | settings.append( margin_props.Right, 'margr' + suffix + '%s' ) 168 | 169 | def _RendParagraphPropertySet( self, paragraph_props, settings ) : 170 | if not paragraph_props : return 171 | settings.append( ParagraphAlignmentMap[ paragraph_props.Alignment ] ) 172 | 173 | settings.append( paragraph_props.SpaceBefore, 'sb%s' ) 174 | settings.append( paragraph_props.SpaceAfter, 'sa%s' ) 175 | 176 | # then we have to find out all of the tabs 177 | width = 0 178 | for tab in paragraph_props.Tabs : 179 | settings.append( TabAlignmentMap[ tab.Alignment ] ) 180 | settings.append( TabLeaderMap.get( tab.Leader, '' ) ) 181 | 182 | width += tab.Width or DEFAULT_TAB_WIDTH 183 | settings.append( 'tx%s' % width ) 184 | 185 | settings.append( paragraph_props.PageBreakBefore, 'pagebb' ) 186 | 187 | settings.append( paragraph_props.FirstLineIndent, 'fi%s' ) 188 | settings.append( paragraph_props.LeftIndent, 'li%s' ) 189 | settings.append( paragraph_props.RightIndent, 'ri%s' ) 190 | 191 | if paragraph_props.SpaceBetweenLines : 192 | if paragraph_props.SpaceBetweenLines < 0 : 193 | settings.append( paragraph_props.SpaceBetweenLines, r'sl%s\slmult0' ) 194 | else : 195 | settings.append( paragraph_props.SpaceBetweenLines, r'sl%s\slmult1' ) 196 | 197 | def _RendTextPropertySet( self, text_props, settings ) : 198 | if not text_props : return 199 | 200 | if text_props.Expansion : 201 | settings.append( text_props.Expansion, 'expndtw%s' ) 202 | 203 | settings.append( text_props.Bold, 'b' ) 204 | settings.append( text_props.Italic, 'i' ) 205 | settings.append( text_props.Underline, 'ul' ) 206 | settings.append( text_props.DottedUnderline, 'uld' ) 207 | settings.append( text_props.DoubleUnderline, 'uldb' ) 208 | settings.append( text_props.WordUnderline, 'ulw' ) 209 | 210 | settings.append( self._font_map.get( text_props.Font, False ), 'f%s' ) 211 | settings.append( text_props.Size, 'fs%s' ) 212 | settings.append( self._colour_map.get( text_props.Colour, False ), 'cf%s' ) 213 | 214 | if text_props.Frame : 215 | frame = text_props.Frame 216 | settings.append( 'chbrdr' ) 217 | settings.append( BorderStyleMap[ frame.Style ] ) 218 | settings.append( frame.Width , 'brdrw%s' ) 219 | settings.append( self._colour_map.get( frame.Colour, False ), 'brdrcf%s' ) 220 | 221 | # 222 | # All of the Write* functions will write to the internal file object 223 | # 224 | # the _ ones probably don't need to be used by anybody outside 225 | # but the other ones like WriteTextElement could be used in the Custom 226 | # callback. 227 | def Write( self, document, fout ) : 228 | # write all of the standard stuff based upon the first document 229 | self._doc = document 230 | self._fout = fout 231 | self._WriteDocument () 232 | self._WriteColours () 233 | self._WriteFonts () 234 | self._WriteStyleSheet() 235 | 236 | settings = Settings() 237 | self._RendPageProperties( self._doc.Sections[ 0 ], settings, in_section=False ) 238 | self._write( repr( settings ) ) 239 | 240 | # handle the simplest case first, we don't need to do anymore mucking around 241 | # with section headers, etc we can just rip the document out 242 | if len( document.Sections ) == 1 : 243 | self._WriteSection( document.Sections[ 0 ], 244 | is_first = True, 245 | add_header = False ) 246 | 247 | else : 248 | for section_idx, section in enumerate( document.Sections ) : 249 | is_first = section_idx == 0 250 | add_header = True 251 | self._WriteSection( section, is_first, add_header ) 252 | 253 | self._write( '}' ) 254 | 255 | del self._fout, self._doc, self._CurrentStyle 256 | 257 | def _write( self, data, *params ) : 258 | #---------------------------------- 259 | # begin modification 260 | # by Herbert Weinhandl 261 | # to convert accented characters 262 | # to their rtf-compatible form 263 | #for c in range( 128, 256 ) : 264 | # data = data.replace( chr(c), "\'%x" % c) 265 | # end modification 266 | # 267 | # This isn't the right place for this as it is going to do 268 | # this loop for all sorts of writes, including settings, control codes, etc. 269 | # 270 | # I will create a def _WriteText (or something) method that is used when the 271 | # actual string that is to be viewed in the document is written, this can then 272 | # do the final accented character check. 273 | # 274 | # I left it here so that I remember to do the right thing when I have time 275 | #---------------------------------- 276 | 277 | if params : data = data % params 278 | self._fout.write( data ) 279 | 280 | def _WriteDocument( self ) : 281 | settings = Settings() 282 | 283 | assert Languages.IsValid ( self._doc.DefaultLanguage ) 284 | assert ViewKind.IsValid ( self._doc.ViewKind ) 285 | assert ViewZoomKind.IsValid( self._doc.ViewZoomKind ) 286 | assert ViewScale.IsValid ( self._doc.ViewScale ) 287 | 288 | settings.append( self._doc.DefaultLanguage, 'deflang%s' ) 289 | settings.append( self._doc.ViewKind , 'viewkind%s' ) 290 | settings.append( self._doc.ViewZoomKind , 'viewzk%s' ) 291 | settings.append( self._doc.ViewScale , 'viewscale%s' ) 292 | 293 | self._write( "{\\rtf1\\ansi\\ansicpg1252\\deff0%s\n" % settings ) 294 | 295 | def _WriteColours( self ) : 296 | self._write( r"{\colortbl ;" ) 297 | 298 | self._colour_map = {} 299 | offset = 0 300 | for colour in self._doc.StyleSheet.Colours : 301 | self._write( r'\red%s\green%s\blue%s;', colour.Red, colour.Green, colour.Blue ) 302 | self._colour_map[ colour ] = offset + 1 303 | offset += 1 304 | self._write( "}\n" ) 305 | 306 | def _WriteFonts( self ) : 307 | self._write( r'{\fonttbl' ) 308 | 309 | self._font_map = {} 310 | offset = 0 311 | for font in self._doc.StyleSheet.Fonts : 312 | pitch = '' 313 | panose = '' 314 | alternate = '' 315 | if font.Pitch : pitch = r'\fprq%s' % font.Pitch 316 | if font.Panose : panose = r'{\*\panose %s}' % font.Panose 317 | if font.Alternate : alternate = r'{\*\falt %s}' % font.Alternate.Name 318 | 319 | self._write( r'{\f%s\f%s%s\fcharset%s%s %s%s;}', 320 | offset, 321 | font.Family, 322 | pitch, 323 | font.CharacterSet, 324 | panose, 325 | font.Name, 326 | alternate ) 327 | 328 | self._font_map[ font ] = offset 329 | offset += 1 330 | 331 | self._write( "}\n" ) 332 | 333 | def _WriteStyleSheet( self ) : 334 | self._write( r"{\stylesheet" ) 335 | 336 | # TO DO: character styles, does anybody actually use them? 337 | 338 | offset_map = {} 339 | for idx, style in enumerate( self._doc.StyleSheet.ParagraphStyles ) : 340 | offset_map[ style ] = idx 341 | 342 | # paragraph styles 343 | self.paragraph_style_map = {} 344 | for idx, style in enumerate( self._doc.StyleSheet.ParagraphStyles ) : 345 | 346 | if idx == 0 : 347 | default = style 348 | else : 349 | self._write( '\n' ) 350 | 351 | settings = Settings() 352 | 353 | # paragraph properties 354 | self._RendParagraphPropertySet( style.ParagraphPropertySet, settings ) 355 | self._RendFramePropertySet ( style.FramePropertySet, settings ) 356 | self._RendShadingPropertySet ( style.ShadingPropertySet, settings ) 357 | 358 | # text properties 359 | self._RendTextPropertySet ( style.TextStyle.TextPropertySet, settings ) 360 | self._RendShadingPropertySet( style.TextStyle.ShadingPropertySet, settings ) 361 | 362 | # have to take 363 | based_on = '\\sbasedon%s' % offset_map.get( style.BasedOn, 0 ) 364 | next = '\\snext%s' % offset_map.get( style.Next, 0 ) 365 | 366 | inln = '\\s%s%s' % ( idx, settings ) 367 | self._write( "{%s%s%s %s;}", inln, based_on, next, style.Name ) 368 | 369 | self.paragraph_style_map[ style ] = inln 370 | 371 | # if now style is specified for the first paragraph to be written, this one 372 | # will be used 373 | self._CurrentStyle = self.paragraph_style_map[ default ] 374 | 375 | self._write( "}\n" ) 376 | 377 | def _WriteSection( self, section, is_first, add_header ) : 378 | 379 | def WriteHF( hf, rtfword ) : 380 | #if not hf : return 381 | 382 | # if we don't have anything in the header/footer then include 383 | # a blank paragraph, this stops it from picking up the header/footer 384 | # from the previous section 385 | # if not hf : hf = [ Paragraph( '' ) ] 386 | if not hf : hf = [] 387 | 388 | self._write( '{\\%s' % rtfword ) 389 | self._WriteElements( hf ) 390 | self._write( '}\n' ) 391 | 392 | settings = Settings() 393 | 394 | if not is_first : 395 | # we need to finish off the preceding section 396 | # and reset all of our defaults back to standard 397 | settings.append( 'sect' ) 398 | 399 | # reset to our defaults 400 | settings.append( 'sectd' ) 401 | 402 | if add_header : 403 | settings.append( SectionBreakTypeMap[ section.BreakType ] ) 404 | self._RendPageProperties( section, settings, in_section=True ) 405 | 406 | settings.append( section.HeaderY, 'headery%s' ) 407 | settings.append( section.FooterY, 'footery%s' ) 408 | 409 | # write all of these out now as we need to do a write elements in the 410 | # next section 411 | self._write( repr( settings ) ) 412 | 413 | # finally after all that has settled down we can do the 414 | # headers and footers 415 | if section.FirstHeader or section.FirstFooter : 416 | # include the titlepg flag if the first page has a special format 417 | self._write( r'\titlepg' ) 418 | WriteHF( section.FirstHeader, 'headerf' ) 419 | WriteHF( section.FirstFooter, 'footerf' ) 420 | 421 | WriteHF( section.Header, 'header' ) 422 | WriteHF( section.Footer, 'footer' ) 423 | 424 | # and at last the contents of the section that actually appear on the page 425 | self._WriteElements( section ) 426 | 427 | def _WriteElements( self, elements ) : 428 | new_line = '' 429 | for element in elements : 430 | self._write( new_line ) 431 | new_line = '\n' 432 | 433 | clss = element.__class__ 434 | 435 | if clss == Paragraph : 436 | self.WriteParagraphElement( element ) 437 | 438 | elif clss == Table : 439 | self.WriteTableElement( element ) 440 | 441 | elif clss == StringType : 442 | self.WriteParagraphElement( Paragraph( element ) ) 443 | 444 | elif clss in [ RawCode, Image ] : 445 | self.WriteRawCode( element ) 446 | 447 | #elif clss == List : 448 | # self._HandleListElement( element ) 449 | 450 | elif self.WriteCustomElement : 451 | self.WriteCustomElement( self, element ) 452 | 453 | else : 454 | raise Exception( "Don't know how to handle elements of type %s" % clss ) 455 | 456 | def WriteParagraphElement( self, paragraph_elem, tag_prefix='', tag_suffix=r'\par', opening='{', closing='}' ) : 457 | 458 | # the tag_prefix and the tag_suffix take care of paragraphs in tables. A 459 | # paragraph in a table requires and extra tag at the front (intbl) and we 460 | # don't want the ending tag everytime. We want it for all paragraphs but 461 | # the last. 462 | 463 | overrides = Settings() 464 | self._RendParagraphPropertySet( paragraph_elem.Properties, overrides ) 465 | self._RendFramePropertySet ( paragraph_elem.Frame, overrides ) 466 | self._RendShadingPropertySet ( paragraph_elem.Shading, overrides ) 467 | 468 | # when writing the RTF the style is carried from the previous paragraph to the next, 469 | # so if the currently written paragraph has a style then make it the current one, 470 | # otherwise leave it as it was 471 | self._CurrentStyle = self.paragraph_style_map.get( paragraph_elem.Style, self._CurrentStyle ) 472 | 473 | self._write( r'%s\pard\plain%s %s%s ' % ( opening, tag_prefix, self._CurrentStyle, overrides ) ) 474 | 475 | for element in paragraph_elem : 476 | 477 | if isinstance( element, StringType ) : 478 | self._write( element ) 479 | 480 | elif isinstance( element, RawCode ) : 481 | self._write( element.Data ) 482 | 483 | elif isinstance( element, Text ) : 484 | self.WriteTextElement( element ) 485 | 486 | elif isinstance( element, Inline ) : 487 | self.WriteInlineElement( element ) 488 | 489 | elif element == TAB : 490 | self._write( r'\tab ' ) 491 | 492 | elif element == LINE : 493 | self._write( r'\line ' ) 494 | 495 | elif self.WriteCustomElement : 496 | self.WriteCustomElement( self, element ) 497 | 498 | else : 499 | raise Exception( 'Don\'t know how to handle %s' % element ) 500 | 501 | self._write( tag_suffix + closing ) 502 | 503 | def WriteRawCode( self, raw_elem ) : 504 | self._write( raw_elem.Data ) 505 | 506 | def WriteTextElement( self, text_elem ) : 507 | overrides = Settings() 508 | 509 | self._RendTextPropertySet ( text_elem.Properties, overrides ) 510 | self._RendShadingPropertySet( text_elem.Shading, overrides, 'ch' ) 511 | 512 | # write the wrapper and then let the custom handler have a go 513 | if overrides : self._write( '{%s ' % repr( overrides ) ) 514 | 515 | # if the data is just a string then we can now write it 516 | if isinstance( text_elem.Data, StringType ) : 517 | self._write( text_elem.Data or '' ) 518 | 519 | elif text_elem.Data == TAB : 520 | self._write( r'\tab ' ) 521 | 522 | else : 523 | self.WriteCustomElement( self, text_elem.Data ) 524 | 525 | if overrides : self._write( '}' ) 526 | 527 | def WriteInlineElement( self, inline_elem ) : 528 | overrides = Settings() 529 | 530 | self._RendTextPropertySet ( inline_elem.Properties, overrides ) 531 | self._RendShadingPropertySet( inline_elem.Shading, overrides, 'ch' ) 532 | 533 | # write the wrapper and then let the custom handler have a go 534 | if overrides : self._write( '{%s ' % repr( overrides ) ) 535 | 536 | for element in inline_elem : 537 | # if the data is just a string then we can now write it 538 | if isinstance( element, StringType ) : 539 | self._write( element ) 540 | 541 | elif isinstance( element, RawCode ) : 542 | self._write( element.Data ) 543 | 544 | elif element == TAB : 545 | self._write( r'\tab ' ) 546 | 547 | elif element == LINE : 548 | self._write( r'\line ' ) 549 | 550 | else : 551 | self.WriteCustomElement( self, element ) 552 | 553 | if overrides : self._write( '}' ) 554 | 555 | def WriteText( self, text ) : 556 | self._write( text or '' ) 557 | 558 | def WriteTableElement( self, table_elem ) : 559 | 560 | vmerge = [ False ] * table_elem.ColumnCount 561 | for height, cells in table_elem.Rows : 562 | 563 | # calculate the right hand edge of the cells taking into account the spans 564 | offset = table_elem.LeftOffset or 0 565 | cellx = [] 566 | cell_idx = 0 567 | for cell in cells : 568 | cellx.append( offset + sum( table_elem.ColumnWidths[ : cell_idx + cell.Span ] ) ) 569 | cell_idx += cell.Span 570 | 571 | self._write( r'{\trowd' ) 572 | 573 | settings = Settings() 574 | 575 | # the spec says that this value is mandatory and I think that 108 is the default value 576 | # so I'll take care of it here 577 | settings.append( table_elem.GapBetweenCells or 108, 'trgaph%s' ) 578 | settings.append( TableAlignmentMap[ table_elem.Alignment ] ) 579 | settings.append( height, 'trrh%s' ) 580 | settings.append( table_elem.LeftOffset, 'trleft%s' ) 581 | 582 | width = table_elem.LeftOffset or 0 583 | for idx, cell in enumerate( cells ) : 584 | self._RendFramePropertySet ( cell.Frame, settings, 'cl' ) 585 | 586 | # cells don't have margins so I don't know why I was doing this 587 | # I think it might have an affect in some versions of some WPs. 588 | #self._RendMarginsPropertySet( cell.Margins, settings, 'cl' ) 589 | 590 | # if we are starting to merge or if this one is the first in what is 591 | # probably a series of merges then start the vertical merging 592 | if cell.StartVerticalMerge or (cell.VerticalMerge and not vmerge[ idx ]) : 593 | settings.append( 'clvmgf' ) 594 | vmerge[ idx ] = True 595 | 596 | elif cell.VerticalMerge : 597 | #..continuing a merge 598 | settings.append( 'clvmrg' ) 599 | 600 | else : 601 | #..no merging going on so make sure that it is off 602 | vmerge[ idx ] = False 603 | 604 | # for any cell in the next row that is covered by this span we 605 | # need to run off the vertical merging as we don't want them 606 | # merging up into this spanned cell 607 | for vmerge_idx in range( idx + 1, idx + cell.Span - 1 ) : 608 | vmerge[ vmerge_idx ] = False 609 | 610 | settings.append( CellAlignmentMap[ cell.Alignment ] ) 611 | settings.append( CellFlowMap[ cell.Flow ] ) 612 | 613 | # this terminates the definition of a cell and represents the right most edge of the cell from the left margin 614 | settings.append( cellx[ idx ], 'cellx%s' ) 615 | 616 | self._write( repr( settings ) ) 617 | 618 | for cell in cells : 619 | if len( cell ) : 620 | last_idx = len( cell ) - 1 621 | for element_idx, element in enumerate( cell ) : 622 | # wrap plain strings in paragraph tags 623 | if isinstance( element, StringType ) : 624 | element = Paragraph( element ) 625 | 626 | # don't forget the prefix or else word crashes and does all sorts of strange things 627 | if element_idx == last_idx : 628 | self.WriteParagraphElement( element, tag_prefix=r'\intbl', tag_suffix='', opening='', closing='' ) 629 | 630 | else : 631 | self.WriteParagraphElement( element, tag_prefix=r'\intbl', opening='', closing='' ) 632 | 633 | self._write( r'\cell' ) 634 | 635 | else : 636 | self._write( r'\pard\intbl\cell' ) 637 | 638 | self._write( '\\row}\n' ) 639 | -------------------------------------------------------------------------------- /PyRTF/Elements.py: -------------------------------------------------------------------------------- 1 | from types import IntType, FloatType, LongType, StringTypes 2 | from copy import deepcopy 3 | from binascii import hexlify 4 | 5 | from Constants import * 6 | from Styles import * 7 | 8 | class UnhandledParamError( Exception ) : 9 | def __init__( self, param ) : 10 | Exception.__init__( self, "Don't know what to do with param %s" % param ) 11 | 12 | # red green blue 13 | StandardColours = Colours() 14 | StandardColours.append( Colour( 'Black', 0, 0, 0 ) ) 15 | StandardColours.append( Colour( 'Blue', 0, 0, 255 ) ) 16 | StandardColours.append( Colour( 'Turquoise', 0, 255, 255 ) ) 17 | StandardColours.append( Colour( 'Green', 0, 255, 0 ) ) 18 | StandardColours.append( Colour( 'Pink', 255, 0, 255 ) ) 19 | StandardColours.append( Colour( 'Red', 255, 0, 0 ) ) 20 | StandardColours.append( Colour( 'Yellow', 255, 255, 0 ) ) 21 | StandardColours.append( Colour( 'White', 255, 255, 255 ) ) 22 | StandardColours.append( Colour( 'Blue Dark', 0, 0, 128 ) ) 23 | StandardColours.append( Colour( 'Teal', 0, 128, 128 ) ) 24 | StandardColours.append( Colour( 'Green Dark', 0, 128, 0 ) ) 25 | StandardColours.append( Colour( 'Violet', 128, 0, 128 ) ) 26 | StandardColours.append( Colour( 'Red Dark', 128, 0, 0 ) ) 27 | StandardColours.append( Colour( 'Yellow Dark', 128, 128, 0 ) ) 28 | StandardColours.append( Colour( 'Grey Dark', 128, 128, 128 ) ) 29 | StandardColours.append( Colour( 'Grey', 192, 192, 192 ) ) 30 | 31 | StandardFonts = Fonts() 32 | StandardFonts.append( Font( 'Arial' , 'swiss' , 0, 2, '020b0604020202020204' ) ) 33 | StandardFonts.append( Font( 'Arial Black' , 'swiss' , 0, 2, '020b0a04020102020204' ) ) 34 | StandardFonts.append( Font( 'Arial Narrow' , 'swiss' , 0, 2, '020b0506020202030204' ) ) 35 | StandardFonts.append( Font( 'Bitstream Vera Sans Mono', 'modern', 0, 1, '020b0609030804020204' ) ) 36 | StandardFonts.append( Font( 'Bitstream Vera Sans' , 'swiss' , 0, 2, '020b0603030804020204' ) ) 37 | StandardFonts.append( Font( 'Bitstream Vera Serif' , 'roman' , 0, 2, '02060603050605020204' ) ) 38 | StandardFonts.append( Font( 'Book Antiqua' , 'roman' , 0, 2, '02040602050305030304' ) ) 39 | StandardFonts.append( Font( 'Bookman Old Style' , 'roman' , 0, 2, '02050604050505020204' ) ) 40 | StandardFonts.append( Font( 'Castellar' , 'roman' , 0, 2, '020a0402060406010301' ) ) 41 | StandardFonts.append( Font( 'Century Gothic' , 'swiss' , 0, 2, '020b0502020202020204' ) ) 42 | StandardFonts.append( Font( 'Comic Sans MS' , 'script', 0, 2, '030f0702030302020204' ) ) 43 | StandardFonts.append( Font( 'Courier New' , 'modern', 0, 1, '02070309020205020404' ) ) 44 | StandardFonts.append( Font( 'Franklin Gothic Medium' , 'swiss' , 0, 2, '020b0603020102020204' ) ) 45 | StandardFonts.append( Font( 'Garamond' , 'roman' , 0, 2, '02020404030301010803' ) ) 46 | StandardFonts.append( Font( 'Georgia' , 'roman' , 0, 2, '02040502050405020303' ) ) 47 | StandardFonts.append( Font( 'Haettenschweiler' , 'swiss' , 0, 2, '020b0706040902060204' ) ) 48 | StandardFonts.append( Font( 'Impact' , 'swiss' , 0, 2, '020b0806030902050204' ) ) 49 | StandardFonts.append( Font( 'Lucida Console' , 'modern', 0, 1, '020b0609040504020204' ) ) 50 | StandardFonts.append( Font( 'Lucida Sans Unicode' , 'swiss' , 0, 2, '020b0602030504020204' ) ) 51 | StandardFonts.append( Font( 'Microsoft Sans Serif' , 'swiss' , 0, 2, '020b0604020202020204' ) ) 52 | StandardFonts.append( Font( 'Monotype Corsiva' , 'script', 0, 2, '03010101010201010101' ) ) 53 | StandardFonts.append( Font( 'Palatino Linotype' , 'roman' , 0, 2, '02040502050505030304' ) ) 54 | StandardFonts.append( Font( 'Papyrus' , 'script', 0, 2, '03070502060502030205' ) ) 55 | StandardFonts.append( Font( 'Sylfaen' , 'roman' , 0, 2, '010a0502050306030303' ) ) 56 | StandardFonts.append( Font( 'Symbol' , 'roman' , 2, 2, '05050102010706020507' ) ) 57 | StandardFonts.append( Font( 'Tahoma' , 'swiss' , 0, 2, '020b0604030504040204' ) ) 58 | StandardFonts.append( Font( 'Times New Roman' , 'roman' , 0, 2, '02020603050405020304' ) ) 59 | StandardFonts.append( Font( 'Trebuchet MS' , 'swiss' , 0, 2, '020b0603020202020204' ) ) 60 | StandardFonts.append( Font( 'Verdana' , 'swiss' , 0, 2, '020b0604030504040204' ) ) 61 | 62 | StandardFonts.Castellar.SetAlternate( StandardFonts.Georgia ) 63 | 64 | """ 65 | Found the following definition at http://www.pbdr.com/vbtips/gen/convtwip.htm 66 | 67 | Twips are screen-independent units used to ensure that the placement and 68 | proportion of screen elements in your screen application are the same on all 69 | display systems. A twip is a unit of screen measurement equal to 1/20 of a 70 | printer's point. The conversion between twips and 71 | inches/centimeters/millimeters is as follows: 72 | 73 | There are approximately 1440 twips to a inch (the length of a screen item 74 | measuring one inch when printed). 75 | 76 | As there are 2.54 centimeters to 1 inch, then there are approximately 567 77 | twips to a centimeter (the length of a screen item measuring one centimeter 78 | when printed). 79 | 80 | Or in millimeters, as there are 25.4 millimeters to 1 inch, therefore there 81 | are approximately 56.7 twips to a millimeter (the length of a screen item 82 | measuring one millimeter when printed).""" 83 | 84 | # Width default is 12240, Height default is 15840 85 | StandardPaper = Papers() 86 | StandardPaper.append( Paper( 'LETTER' , 1, 'Letter 8 1/2 x 11 in' , 12240, 15840 ) ) 87 | StandardPaper.append( Paper( 'LETTERSMALL' , 2, 'Letter Small 8 1/2 x 11 in' , 12240, 15840 ) ) 88 | StandardPaper.append( Paper( 'TABLOID' , 3, 'Tabloid 11 x 17 in' , 15840, 24480 ) ) 89 | StandardPaper.append( Paper( 'LEDGER' , 4, 'Ledger 17 x 11 in' , 24480, 15840 ) ) 90 | StandardPaper.append( Paper( 'LEGAL' , 5, 'Legal 8 1/2 x 14 in' , 12240, 20160 ) ) 91 | StandardPaper.append( Paper( 'STATEMENT' , 6, 'Statement 5 1/2 x 8 1/2 in' , 7920, 12240 ) ) 92 | StandardPaper.append( Paper( 'EXECUTIVE' , 7, 'Executive 7 1/4 x 10 1/2 in' , 10440, 15120 ) ) 93 | StandardPaper.append( Paper( 'A3' , 8, 'A3 297 x 420 mm' , 16838, 23811 ) ) 94 | StandardPaper.append( Paper( 'A4' , 9, 'A4 210 x 297 mm' , 11907, 16838 ) ) 95 | StandardPaper.append( Paper( 'A4SMALL' , 10, 'A4 Small 210 x 297 mm' , 11907, 16838 ) ) 96 | StandardPaper.append( Paper( 'A5' , 11, 'A5 148 x 210 mm' , 8391, 11907 ) ) 97 | StandardPaper.append( Paper( 'B4' , 12, 'B4 (JIS) 250 x 354' , 14175, 20072 ) ) 98 | StandardPaper.append( Paper( 'B5' , 13, 'B5 (JIS) 182 x 257 mm' , 10319, 14572 ) ) 99 | StandardPaper.append( Paper( 'FOLIO' , 14, 'Folio 8 1/2 x 13 in' , 12240, 18720 ) ) 100 | StandardPaper.append( Paper( 'QUARTO' , 15, 'Quarto 215 x 275 mm' , 12191, 15593 ) ) 101 | StandardPaper.append( Paper( '10X14' , 16, '10x14 in' , 14400, 20160 ) ) 102 | StandardPaper.append( Paper( '11X17' , 17, '11x17 in' , 15840, 24480 ) ) 103 | StandardPaper.append( Paper( 'NOTE' , 18, 'Note 8 1/2 x 11 in' , 12240, 15840 ) ) 104 | StandardPaper.append( Paper( 'ENV_9' , 19, 'Envelope #9 3 7/8 x 8 7/8' , 5580, 12780 ) ) 105 | StandardPaper.append( Paper( 'ENV_10' , 20, 'Envelope #10 4 1/8 x 9 1/2' , 5940, 13680 ) ) 106 | StandardPaper.append( Paper( 'ENV_11' , 21, 'Envelope #11 4 1/2 x 10 3/8' , 6480, 14940 ) ) 107 | StandardPaper.append( Paper( 'ENV_12' , 22, 'Envelope #12 4 3/4 x 11' , 6840, 15840 ) ) 108 | StandardPaper.append( Paper( 'ENV_14' , 23, 'Envelope #14 5 x 11 1/2' , 7200, 16560 ) ) 109 | StandardPaper.append( Paper( 'CSHEET' , 24, 'C size sheet 18 x 24 in' , 29520, 34560 ) ) 110 | StandardPaper.append( Paper( 'DSHEET' , 25, 'D size sheet 22 x 34 in' , 31680, 48960 ) ) 111 | StandardPaper.append( Paper( 'ESHEET' , 26, 'E size sheet 34 x 44 in' , 48960, 63360 ) ) 112 | StandardPaper.append( Paper( 'ENV_DL' , 27, 'Envelope DL 110 x 220mm' , 6237, 12474 ) ) 113 | StandardPaper.append( Paper( 'ENV_C5' , 28, 'Envelope C5 162 x 229 mm' , 9185, 12984 ) ) 114 | StandardPaper.append( Paper( 'ENV_C3' , 29, 'Envelope C3 324 x 458 mm' , 18371, 25969 ) ) 115 | StandardPaper.append( Paper( 'ENV_C4' , 30, 'Envelope C4 229 x 324 mm' , 12984, 18371 ) ) 116 | StandardPaper.append( Paper( 'ENV_C6' , 31, 'Envelope C6 114 x 162 mm' , 6464, 9185 ) ) 117 | StandardPaper.append( Paper( 'ENV_C65' , 32, 'Envelope C65 114 x 229 mm' , 6464, 12984 ) ) 118 | StandardPaper.append( Paper( 'ENV_B4' , 33, 'Envelope B4 250 x 353 mm' , 14175, 20015 ) ) 119 | StandardPaper.append( Paper( 'ENV_B5' , 34, 'Envelope B5 176 x 250 mm' , 9979, 14175 ) ) 120 | StandardPaper.append( Paper( 'ENV_B6' , 35, 'Envelope B6 176 x 125 mm' , 9979, 7088 ) ) 121 | StandardPaper.append( Paper( 'ENV_ITALY' , 36, 'Envelope 110 x 230 mm' , 6237, 13041 ) ) 122 | StandardPaper.append( Paper( 'ENV_MONARCH' , 37, 'Envelope Monarch 3.875 x 7.5 in' , 5580, 10800 ) ) 123 | StandardPaper.append( Paper( 'ENV_PERSONAL' , 38, '6 3/4 Envelope 3 5/8 x 6 1/2 in' , 5220, 9360 ) ) 124 | StandardPaper.append( Paper( 'FANFOLD_US' , 39, 'US Std Fanfold 14 7/8 x 11 in' , 21420, 15840 ) ) 125 | StandardPaper.append( Paper( 'FANFOLD_STD_GERMAN' , 40, 'German Std Fanfold 8 1/2 x 12 in' , 12240, 17280 ) ) 126 | StandardPaper.append( Paper( 'FANFOLD_LGL_GERMAN' , 41, 'German Legal Fanfold 8 1/2 x 13 in' , 12240, 18720 ) ) 127 | 128 | # 129 | # Finally a StyleSheet in which all of this stuff is put together 130 | # 131 | class StyleSheet : 132 | def __init__( self, colours=None, fonts=None ) : 133 | 134 | self.Colours = colours or deepcopy( StandardColours ) 135 | self.Fonts = fonts or deepcopy( StandardFonts ) 136 | 137 | self.TextStyles = AttributedList() 138 | self.ParagraphStyles = AttributedList() 139 | 140 | class Section( list ) : 141 | NONE = 1 142 | COLUMN = 2 143 | PAGE = 3 144 | EVEN = 4 145 | ODD = 5 146 | BREAK_TYPES = [ NONE, COLUMN, PAGE, EVEN, ODD ] 147 | 148 | def __init__( self, paper=None, margins=None, break_type=None, headery=None, footery=None, landscape=None, first_page_number=None ) : 149 | super( Section, self ).__init__() 150 | 151 | self.Paper = paper or StandardPaper.A4 152 | self.SetMargins( margins ) 153 | 154 | self.Header = [] 155 | self.Footer = [] 156 | self.FirstHeader = [] 157 | self.FirstFooter = [] 158 | 159 | self.SetBreakType( break_type or self.NONE ) 160 | self.SetHeaderY( headery ) 161 | self.SetFooterY( footery ) 162 | self.SetLandscape( landscape ) 163 | self.SetFirstPageNumber( first_page_number ) 164 | 165 | def TwipsToRightMargin( self ) : 166 | return self.Paper.Width - ( self.Margins.Left + self.Margins.Right ) 167 | 168 | def SetMargins( self, value ) : 169 | self.Margins = value or MarginsPropertySet( top=1000, left=1200, bottom=1000, right=1200 ) 170 | self.Width = self.Paper.Width - ( self.Margins.Left + self.Margins.Right ) 171 | 172 | def SetBreakType( self, value ) : 173 | assert value in self.BREAK_TYPES 174 | self.BreakType = value 175 | return self 176 | 177 | def SetHeaderY( self, value ) : 178 | self.HeaderY = value 179 | return self 180 | 181 | def SetFooterY( self, value ) : 182 | self.FooterY = value 183 | return self 184 | 185 | def SetLandscape( self, value ) : 186 | self.Landscape = False 187 | if value : self.Landscape = True 188 | return self 189 | 190 | def SetFirstPageNumber( self, value ) : 191 | self.FirstPageNumber = value 192 | return self 193 | 194 | def MakeDefaultStyleSheet( ) : 195 | result = StyleSheet() 196 | 197 | NormalText = TextStyle( TextPropertySet( result.Fonts.Arial, 22 ) ) 198 | 199 | ps = ParagraphStyle( 'Normal', 200 | NormalText.Copy(), 201 | ParagraphPropertySet( space_before = 60, 202 | space_after = 60 ) ) 203 | result.ParagraphStyles.append( ps ) 204 | 205 | ps = ParagraphStyle( 'Normal Short', 206 | NormalText.Copy() ) 207 | result.ParagraphStyles.append( ps ) 208 | 209 | NormalText.TextPropertySet.SetSize( 32 ) 210 | ps = ParagraphStyle( 'Heading 1', 211 | NormalText.Copy(), 212 | ParagraphPropertySet( space_before = 240, 213 | space_after = 60 ) ) 214 | result.ParagraphStyles.append( ps ) 215 | 216 | NormalText.TextPropertySet.SetSize( 24 ).SetBold( True ) 217 | ps = ParagraphStyle( 'Heading 2', 218 | NormalText.Copy(), 219 | ParagraphPropertySet( space_before = 240, 220 | space_after = 60 ) ) 221 | result.ParagraphStyles.append( ps ) 222 | 223 | # Add some more in that are based on the normal template but that 224 | # have some indenting set that makes them suitable for doing numbered 225 | normal_numbered = result.ParagraphStyles.Normal.Copy() 226 | normal_numbered.SetName( 'Normal Numbered' ) 227 | normal_numbered.ParagraphPropertySet.SetFirstLineIndent( TabPropertySet.DEFAULT_WIDTH * -1 ) 228 | normal_numbered.ParagraphPropertySet.SetLeftIndent ( TabPropertySet.DEFAULT_WIDTH ) 229 | 230 | result.ParagraphStyles.append( normal_numbered ) 231 | 232 | normal_numbered2 = result.ParagraphStyles.Normal.Copy() 233 | normal_numbered2.SetName( 'Normal Numbered 2' ) 234 | normal_numbered2.ParagraphPropertySet.SetFirstLineIndent( TabPropertySet.DEFAULT_WIDTH * -1 ) 235 | normal_numbered2.ParagraphPropertySet.SetLeftIndent ( TabPropertySet.DEFAULT_WIDTH * 2 ) 236 | 237 | result.ParagraphStyles.append( normal_numbered2 ) 238 | 239 | ## LIST STYLES 240 | for idx, indent in [ (1, TabPS.DEFAULT_WIDTH ), 241 | (2, TabPS.DEFAULT_WIDTH * 2), 242 | (3, TabPS.DEFAULT_WIDTH * 3) ] : 243 | indent = TabPropertySet.DEFAULT_WIDTH 244 | ps = ParagraphStyle( 'List %s' % idx, 245 | TextStyle( TextPropertySet( result.Fonts.Arial, 22 ) ), 246 | ParagraphPropertySet( space_before = 60, 247 | space_after = 60, 248 | first_line_indent = -indent, 249 | left_indent = indent) ) 250 | result.ParagraphStyles.append( ps ) 251 | 252 | return result 253 | 254 | class TAB : pass 255 | class LINE : pass 256 | 257 | class RawCode : 258 | def __init__( self, data ) : 259 | self.Data = data 260 | 261 | PAGE_NUMBER = RawCode( r'{\field{\fldinst page}}' ) 262 | TOTAL_PAGES = RawCode( r'{\field{\fldinst numpages}}' ) 263 | SECTION_PAGES = RawCode( r'{\field{\fldinst sectionpages}}' ) 264 | ARIAL_BULLET = RawCode( r'{\f2\'95}' ) 265 | 266 | def _get_jpg_dimensions( fin ): 267 | """ 268 | converted from: http://dev.w3.org/cvsweb/Amaya/libjpeg/rdjpgcom.c?rev=1.2 269 | """ 270 | 271 | M_SOF0 = chr( 0xC0 ) # /* Start Of Frame N */ 272 | M_SOF1 = chr( 0xC1 ) # /* N indicates which compression process */ 273 | M_SOF2 = chr( 0xC2 ) # /* Only SOF0-SOF2 are now in common use */ 274 | M_SOF3 = chr( 0xC3 ) # 275 | M_SOF5 = chr( 0xC5 ) # /* NB: codes C4 and CC are NOT SOF markers */ 276 | M_SOF6 = chr( 0xC6 ) # 277 | M_SOF7 = chr( 0xC7 ) # 278 | M_SOF9 = chr( 0xC9 ) # 279 | M_SOF10 = chr( 0xCA ) # 280 | M_SOF11 = chr( 0xCB ) # 281 | M_SOF13 = chr( 0xCD ) # 282 | M_SOF14 = chr( 0xCE ) # 283 | M_SOF15 = chr( 0xCF ) # 284 | M_SOI = chr( 0xD8 ) # /* Start Of Image (beginning of datastream) */ 285 | M_EOI = chr( 0xD9 ) # /* End Of Image (end of datastream) */ 286 | 287 | M_FF = chr( 0xFF ) 288 | 289 | MARKERS = [ M_SOF0, M_SOF1, M_SOF2, M_SOF3, 290 | M_SOF5, M_SOF6, M_SOF7, M_SOF9, 291 | M_SOF10,M_SOF11, M_SOF13, M_SOF14, 292 | M_SOF15 ] 293 | 294 | def get_length() : 295 | b1 = fin.read( 1 ) 296 | b2 = fin.read( 1 ) 297 | return (ord(b1) << 8) + ord(b2) 298 | 299 | def next_marker() : 300 | # markers come straight after an 0xFF so skip everything 301 | # up to the first 0xFF that we find 302 | while fin.read(1) != M_FF : 303 | pass 304 | 305 | # there can be more than one 0xFF as they can be used 306 | # for padding so we are now looking for the first byte 307 | # that isn't an 0xFF, this will be the marker 308 | while True : 309 | result = fin.read(1) 310 | if result != M_FF : 311 | return result 312 | 313 | raise Exception( 'Invalid JPEG' ) 314 | 315 | # BODY OF THE FUNCTION 316 | if not ((fin.read(1) == M_FF) and (fin.read(1) == M_SOI)) : 317 | raise Exception( 'Invalid Jpeg' ) 318 | 319 | while True : 320 | marker = next_marker() 321 | 322 | # the marker is always followed by two bytes representing the length of the data field 323 | length = get_length () 324 | if length < 2 : raise Exception( "Erroneous JPEG marker length" ) 325 | 326 | # if it is a compression process marker then it will contain the dimension of the image 327 | if marker in MARKERS : 328 | # the next byte is the data precision, just skip it 329 | fin.read(1) 330 | 331 | # bingo 332 | image_height = get_length() 333 | image_width = get_length() 334 | return image_width, image_height 335 | 336 | # just skip whatever data it contains 337 | fin.read( length - 2 ) 338 | 339 | raise Exception( 'Invalid JPEG, end of stream reached' ) 340 | 341 | 342 | _PNG_HEADER = '\x89\x50\x4e' 343 | def _get_png_dimensions( data ) : 344 | if data[0:3] != _PNG_HEADER : 345 | raise Exception( 'Invalid PNG image' ) 346 | 347 | width = (ord(data[18]) * 256) + (ord(data[19])) 348 | height = (ord(data[22]) * 256) + (ord(data[23])) 349 | return width, height 350 | 351 | 352 | class Image( RawCode ) : 353 | 354 | # Need to add in the width and height in twips as it crashes 355 | # word xp with these values. Still working out the most 356 | # efficient way of getting these values. 357 | # \picscalex100\picscaley100\piccropl0\piccropr0\piccropt0\piccropb0 358 | # picwgoal900\pichgoal281 359 | 360 | PNG_LIB = 'pngblip' 361 | JPG_LIB = 'jpegblip' 362 | PICT_TYPES = { 'png' : PNG_LIB, 363 | 'jpg' : JPG_LIB } 364 | 365 | def __init__( self, file_name, **kwargs ) : 366 | 367 | fin = file( file_name, 'rb' ) 368 | 369 | pict_type = self.PICT_TYPES[ file_name[ -3 : ].lower() ] 370 | if pict_type == self.PNG_LIB : 371 | width, height = _get_png_dimensions( fin.read( 100 ) ) 372 | else : 373 | width, height = _get_jpg_dimensions( fin ) 374 | 375 | codes = [ pict_type, 376 | 'picwgoal%s' % (width * 20), 377 | 'pichgoal%s' % (height * 20) ] 378 | for kwarg, code, default in [ ( 'scale_x', 'scalex', '100' ), 379 | ( 'scale_y', 'scaley', '100' ), 380 | ( 'crop_left', 'cropl', '0' ), 381 | ( 'crop_right', 'cropr', '0' ), 382 | ( 'crop_top', 'cropt', '0' ), 383 | ( 'crop_bottom', 'cropb', '0' ) ] : 384 | codes.append( 'pic%s%s' % ( code, kwargs.pop( kwarg, default ) ) ) 385 | 386 | 387 | # reset back to the start of the file to get all of it and now 388 | # turn it into hex. 389 | fin.seek( 0, 0 ) 390 | data = [] 391 | image = hexlify( fin.read() ) 392 | for i in range( 0, len( image ), 128 ) : 393 | data.append( image[ i : i + 128 ] ) 394 | 395 | data = r'{\pict{\%s}%s}' % ( '\\'.join( codes ), '\n'.join( data ) ) 396 | RawCode.__init__( self, data ) 397 | 398 | def ToRawCode( self, var_name ) : 399 | return '%s = RawCode( """%s""" )' % ( var_name, self.Data ) 400 | 401 | class Text : 402 | def __init__( self, *params ) : 403 | self.Data = None 404 | self.Style = None 405 | self.Properties = None 406 | self.Shading = None 407 | 408 | for param in params : 409 | if isinstance( param, TextStyle ) : self.Style = param 410 | elif isinstance( param, TextPS ) : self.Properties = param 411 | elif isinstance( param, ShadingPS ) : self.Shading = param 412 | else : 413 | # otherwise let the rendering custom handler sort it out itself 414 | self.Data = param 415 | 416 | def SetData( self, value ) : 417 | self.Data = value 418 | 419 | class Inline( list ) : 420 | def __init__( self, *params ) : 421 | super( Inline, self ).__init__() 422 | 423 | self.Style = None 424 | self.Properties = None 425 | self.Shading = None 426 | 427 | self._append = super( Inline, self ).append 428 | 429 | for param in params : 430 | if isinstance( param, TextStyle ) : self.Style = param 431 | elif isinstance( param, TextPS ) : self.Properties = param 432 | elif isinstance( param, ShadingPS ) : self.Shading = param 433 | else : 434 | # otherwise we add to it to our list of elements and let 435 | # the rendering custom handler sort it out itself. 436 | self.append( param ) 437 | 438 | def append( self, *params ) : 439 | # filter out any that are explicitly None 440 | [ self._append( param ) for param in params if param is not None ] 441 | 442 | class Paragraph( list ) : 443 | def __init__( self, *params ) : 444 | super( Paragraph, self ).__init__() 445 | 446 | self.Style = None 447 | self.Properties = None 448 | self.Frame = None 449 | self.Shading = None 450 | 451 | self._append = super( Paragraph, self ).append 452 | 453 | for param in params : 454 | if isinstance( param, ParagraphStyle ) : self.Style = param 455 | elif isinstance( param, ParagraphPS ) : self.Properties = param 456 | elif isinstance( param, FramePS ) : self.Frame = param 457 | elif isinstance( param, ShadingPS ) : self.Shading = param 458 | else : 459 | # otherwise we add to it to our list of elements and let 460 | # the rendering custom handler sort it out itself. 461 | self.append( param ) 462 | 463 | def append( self, *params ) : 464 | # filter out any that are explicitly None 465 | [ self._append( param ) for param in params if param is not None ] 466 | 467 | def insert( self, index, value ) : 468 | if value is not None : 469 | super( Paragraph, self ).insert( index, value ) 470 | 471 | class Table : 472 | LEFT = 1 473 | RIGHT = 2 474 | CENTER = 3 475 | ALIGNMENT = [ LEFT, RIGHT, CENTER ] 476 | 477 | NO_WRAPPING = 1 478 | WRAP_AROUND = 2 479 | WRAPPING = [ NO_WRAPPING, WRAP_AROUND ] 480 | 481 | # trrh height of row, 0 means automatically adjust, use negative for an absolute 482 | # trgaph is half of the space between a table cell in width, reduce this one 483 | # to get a really tiny column 484 | 485 | def __init__( self, *column_widths, **kwargs ) : 486 | 487 | self.Rows = [] 488 | 489 | self.SetAlignment ( kwargs.pop( 'alignment', self.LEFT ) ) 490 | self.SetLeftOffset ( kwargs.pop( 'left_offset', None ) ) 491 | self.SetGapBetweenCells( kwargs.pop( 'gap_between_cells', None ) ) 492 | self.SetColumnWidths ( *column_widths ) 493 | 494 | assert not kwargs, 'invalid keyword args %s' % kwargs 495 | 496 | def SetAlignment( self, value ) : 497 | assert value is None or value in self.ALIGNMENT 498 | self.Alignment = value or self.LEFT 499 | return self 500 | 501 | def SetLeftOffset( self, value ) : 502 | self.LeftOffset = value 503 | return self 504 | 505 | def SetGapBetweenCells( self, value ) : 506 | self.GapBetweenCells = value 507 | return self 508 | 509 | def SetColumnWidths( self, *column_widths ) : 510 | self.ColumnWidths = column_widths 511 | self.ColumnCount = len( column_widths ) 512 | return self 513 | 514 | def AddRow( self, *cells ) : 515 | height = None 516 | if isinstance( cells[ 0 ], (IntType, FloatType, LongType) ): 517 | height = int( cells[ 0 ] ) 518 | cells = cells[ 1 : ] 519 | 520 | # make sure all of the spans add up to the number of columns 521 | # otherwise the table will get corrupted 522 | if self.ColumnCount != sum( [ cell.Span for cell in cells ] ) : 523 | raise Exception( 'ColumnCount != the total of this row\'s cell.Spans.' ) 524 | 525 | self.Rows.append( ( height, cells ) ) 526 | 527 | append = AddRow 528 | 529 | class Cell( list ) : 530 | 531 | """ 532 | \clvertalt Text is top-aligned in cell (the default). 533 | \clvertalc Text is centered vertically in cell. 534 | \clvertalb Text is bottom-aligned in cell. 535 | \cltxlrtb Vertical text aligned left (direction bottom up). 536 | \cltxtbrl Vertical text aligned right (direction top down). 537 | """ 538 | 539 | ALIGN_TOP = 1 540 | ALIGN_CENTER = 2 541 | ALIGN_BOTTOM = 3 542 | 543 | FLOW_LR_TB = 1 544 | FLOW_RL_TB = 2 545 | FLOW_LR_BT = 3 546 | FLOW_VERTICAL_LR_TB = 4 547 | FLOW_VERTICAL_TB_RL = 5 548 | 549 | def __init__( self, *params, **kwargs ) : 550 | super( Cell, self ).__init__() 551 | 552 | self.SetFrame ( None ) 553 | self.SetMargins( None ) 554 | 555 | self.SetAlignment( kwargs.get( 'alignment', self.ALIGN_TOP ) ) 556 | self.SetFlow ( kwargs.get( 'flow' , self.FLOW_LR_TB ) ) 557 | self.SetSpan ( kwargs.get( 'span', 1 ) ) 558 | 559 | self.SetStartVerticalMerge( kwargs.get( 'start_vertical_merge', False ) ) 560 | self.SetVerticalMerge ( kwargs.get( 'vertical_merge', False ) ) 561 | 562 | self._append = super( Cell, self ).append 563 | 564 | for param in params : 565 | if isinstance( param, StringType ) : self.append ( param ) 566 | elif isinstance( param, Paragraph ) : self.append ( param ) 567 | elif isinstance( param, FramePS ) : self.SetFrame ( param ) 568 | elif isinstance( param, MarginsPS ) : self.SetMargins( param ) 569 | 570 | def SetFrame( self, value ) : 571 | self.Frame = value 572 | return self 573 | 574 | def SetMargins( self, value ) : 575 | self.Margins = value 576 | return self 577 | 578 | def SetAlignment( self, value ) : 579 | assert value in [ self.ALIGN_TOP, self.ALIGN_CENTER, self.ALIGN_BOTTOM ] #, self.ALIGN_TEXT_TOP_DOWN, self.ALIGN_TEXT_BOTTOM_UP ] 580 | self.Alignment = value 581 | 582 | def SetFlow( self, value ) : 583 | 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 ] 584 | self.Flow = value 585 | 586 | def SetSpan( self, value ) : 587 | # must be a positive integer 588 | self.Span = int( max( value, 1 ) ) 589 | return self 590 | 591 | def SetStartVerticalMerge( self, value ) : 592 | self.StartVerticalMerge = False 593 | if value : 594 | self.StartVerticalMerge = True 595 | return self 596 | 597 | def SetVerticalMerge( self, value ) : 598 | self.VerticalMerge = False 599 | if value : 600 | self.VerticalMerge = True 601 | return self 602 | 603 | def append( self, *params ) : 604 | [ self._append( param ) for param in params ] 605 | 606 | class Document : 607 | def __init__( self, style_sheet=None, default_language=None, view_kind=None, view_zoom_kind=None, view_scale=None ) : 608 | self.StyleSheet = style_sheet or MakeDefaultStyleSheet() 609 | self.Sections = AttributedList( Section ) 610 | 611 | self.SetTitle( None ) 612 | 613 | self.DefaultLanguage = default_language or Languages.DEFAULT 614 | self.ViewKind = view_kind or ViewKind.DEFAULT 615 | self.ViewZoomKind = view_zoom_kind 616 | self.ViewScale = view_scale 617 | 618 | def NewSection( self, *params, **kwargs ) : 619 | result = Section( *params, **kwargs ) 620 | self.Sections.append( result ) 621 | return result 622 | 623 | def SetTitle( self, value ) : 624 | self.Title = value 625 | return self 626 | 627 | def Copy( self ) : 628 | result = Document( style_sheet = self.StyleSheet.Copy(), 629 | default_language = self.DefaultLanguage, 630 | view_kind = self.ViewKind, 631 | view_zoom_kind = self.ViewZoomKind, 632 | view_scale = self.ViewScale ) 633 | result.SetTitle( self.Title ) 634 | result.Sections = self.Sections.Copy() 635 | 636 | return result 637 | 638 | def TEXT( *params, **kwargs ) : 639 | text_props = TextPropertySet() 640 | text_props.SetFont ( kwargs.get( 'font', None ) ) 641 | text_props.SetSize ( kwargs.get( 'size', None ) ) 642 | text_props.SetBold ( kwargs.get( 'bold', False ) ) 643 | text_props.SetItalic ( kwargs.get( 'italic', False ) ) 644 | text_props.SetUnderline( kwargs.get( 'underline', False ) ) 645 | text_props.SetColour ( kwargs.get( 'colour', None ) ) 646 | 647 | if len( params ) == 1 : 648 | return Text( params[ 0 ], text_props ) 649 | 650 | result = Inline( text_props ) 651 | apply( result.append, params ) 652 | return result 653 | 654 | def B( *params ) : 655 | text_props = TextPropertySet( bold=True ) 656 | 657 | if len( params ) == 1 : 658 | return Text( params[ 0 ], text_props ) 659 | 660 | result = Inline( text_props ) 661 | apply( result.append, params ) 662 | return result 663 | 664 | def I( *params ) : 665 | text_props = TextPropertySet( italic=True ) 666 | 667 | if len( params ) == 1 : 668 | return Text( params[ 0 ], text_props ) 669 | 670 | result = Inline( text_props ) 671 | apply( result.append, params ) 672 | return result 673 | 674 | def U( *params ) : 675 | text_props = TextPropertySet( underline=True ) 676 | 677 | if len( params ) == 1 : 678 | return Text( params[ 0 ], text_props ) 679 | 680 | result = Inline( text_props ) 681 | apply( result.append, params ) 682 | return result --------------------------------------------------------------------------------