1 | from __future__ import print_function |
---|
2 | from copy import deepcopy |
---|
3 | from binascii import hexlify |
---|
4 | |
---|
5 | from .Constants import * |
---|
6 | from .Styles import * |
---|
7 | |
---|
8 | if PY2: |
---|
9 | NumberTypes = (int, float, long) |
---|
10 | StringType = basestring |
---|
11 | else: |
---|
12 | NumberTypes = (int, float) |
---|
13 | StringType = str |
---|
14 | |
---|
15 | class UnhandledParamError( Exception ) : |
---|
16 | def __init__( self, param ) : |
---|
17 | Exception.__init__( self, "Don't know what to do with param %s" % param ) |
---|
18 | |
---|
19 | # red green blue |
---|
20 | StandardColours = Colours() |
---|
21 | StandardColours.append( Colour( 'Black', 0, 0, 0 ) ) |
---|
22 | StandardColours.append( Colour( 'Blue', 0, 0, 255 ) ) |
---|
23 | StandardColours.append( Colour( 'Turquoise', 0, 255, 255 ) ) |
---|
24 | StandardColours.append( Colour( 'Green', 0, 255, 0 ) ) |
---|
25 | StandardColours.append( Colour( 'Pink', 255, 0, 255 ) ) |
---|
26 | StandardColours.append( Colour( 'Red', 255, 0, 0 ) ) |
---|
27 | StandardColours.append( Colour( 'Yellow', 255, 255, 0 ) ) |
---|
28 | StandardColours.append( Colour( 'White', 255, 255, 255 ) ) |
---|
29 | StandardColours.append( Colour( 'Blue Dark', 0, 0, 128 ) ) |
---|
30 | StandardColours.append( Colour( 'Teal', 0, 128, 128 ) ) |
---|
31 | StandardColours.append( Colour( 'Green Dark', 0, 128, 0 ) ) |
---|
32 | StandardColours.append( Colour( 'Violet', 128, 0, 128 ) ) |
---|
33 | StandardColours.append( Colour( 'Red Dark', 128, 0, 0 ) ) |
---|
34 | StandardColours.append( Colour( 'Yellow Dark', 128, 128, 0 ) ) |
---|
35 | StandardColours.append( Colour( 'Grey Dark', 128, 128, 128 ) ) |
---|
36 | StandardColours.append( Colour( 'Grey', 192, 192, 192 ) ) |
---|
37 | |
---|
38 | StandardFonts = Fonts() |
---|
39 | StandardFonts.append( Font( 'Arial' , 'swiss' , 0, 2, '020b0604020202020204' ) ) |
---|
40 | StandardFonts.append( Font( 'Arial Black' , 'swiss' , 0, 2, '020b0a04020102020204' ) ) |
---|
41 | StandardFonts.append( Font( 'Arial Narrow' , 'swiss' , 0, 2, '020b0506020202030204' ) ) |
---|
42 | StandardFonts.append( Font( 'Bitstream Vera Sans Mono', 'modern', 0, 1, '020b0609030804020204' ) ) |
---|
43 | StandardFonts.append( Font( 'Bitstream Vera Sans' , 'swiss' , 0, 2, '020b0603030804020204' ) ) |
---|
44 | StandardFonts.append( Font( 'Bitstream Vera Serif' , 'roman' , 0, 2, '02060603050605020204' ) ) |
---|
45 | StandardFonts.append( Font( 'Book Antiqua' , 'roman' , 0, 2, '02040602050305030304' ) ) |
---|
46 | StandardFonts.append( Font( 'Bookman Old Style' , 'roman' , 0, 2, '02050604050505020204' ) ) |
---|
47 | StandardFonts.append( Font( 'Castellar' , 'roman' , 0, 2, '020a0402060406010301' ) ) |
---|
48 | StandardFonts.append( Font( 'Century Gothic' , 'swiss' , 0, 2, '020b0502020202020204' ) ) |
---|
49 | StandardFonts.append( Font( 'Comic Sans MS' , 'script', 0, 2, '030f0702030302020204' ) ) |
---|
50 | StandardFonts.append( Font( 'Courier New' , 'modern', 0, 1, '02070309020205020404' ) ) |
---|
51 | StandardFonts.append( Font( 'Franklin Gothic Medium' , 'swiss' , 0, 2, '020b0603020102020204' ) ) |
---|
52 | StandardFonts.append( Font( 'Garamond' , 'roman' , 0, 2, '02020404030301010803' ) ) |
---|
53 | StandardFonts.append( Font( 'Georgia' , 'roman' , 0, 2, '02040502050405020303' ) ) |
---|
54 | StandardFonts.append( Font( 'Haettenschweiler' , 'swiss' , 0, 2, '020b0706040902060204' ) ) |
---|
55 | StandardFonts.append( Font( 'Impact' , 'swiss' , 0, 2, '020b0806030902050204' ) ) |
---|
56 | StandardFonts.append( Font( 'Lucida Console' , 'modern', 0, 1, '020b0609040504020204' ) ) |
---|
57 | StandardFonts.append( Font( 'Lucida Sans Unicode' , 'swiss' , 0, 2, '020b0602030504020204' ) ) |
---|
58 | StandardFonts.append( Font( 'Microsoft Sans Serif' , 'swiss' , 0, 2, '020b0604020202020204' ) ) |
---|
59 | StandardFonts.append( Font( 'Monotype Corsiva' , 'script', 0, 2, '03010101010201010101' ) ) |
---|
60 | StandardFonts.append( Font( 'Palatino Linotype' , 'roman' , 0, 2, '02040502050505030304' ) ) |
---|
61 | StandardFonts.append( Font( 'Papyrus' , 'script', 0, 2, '03070502060502030205' ) ) |
---|
62 | StandardFonts.append( Font( 'Sylfaen' , 'roman' , 0, 2, '010a0502050306030303' ) ) |
---|
63 | StandardFonts.append( Font( 'Symbol' , 'roman' , 2, 2, '05050102010706020507' ) ) |
---|
64 | StandardFonts.append( Font( 'Tahoma' , 'swiss' , 0, 2, '020b0604030504040204' ) ) |
---|
65 | StandardFonts.append( Font( 'Times New Roman' , 'roman' , 0, 2, '02020603050405020304' ) ) |
---|
66 | StandardFonts.append( Font( 'Trebuchet MS' , 'swiss' , 0, 2, '020b0603020202020204' ) ) |
---|
67 | StandardFonts.append( Font( 'Verdana' , 'swiss' , 0, 2, '020b0604030504040204' ) ) |
---|
68 | |
---|
69 | StandardFonts.Castellar.SetAlternate( StandardFonts.Georgia ) |
---|
70 | |
---|
71 | """ |
---|
72 | Found the following definition at http://www.pbdr.com/vbtips/gen/convtwip.htm |
---|
73 | |
---|
74 | Twips are screen-independent units used to ensure that the placement and |
---|
75 | proportion of screen elements in your screen application are the same on all |
---|
76 | display systems. A twip is a unit of screen measurement equal to 1/20 of a |
---|
77 | printer's point. The conversion between twips and |
---|
78 | inches/centimeters/millimeters is as follows: |
---|
79 | |
---|
80 | There are approximately 1440 twips to a inch (the length of a screen item |
---|
81 | measuring one inch when printed). |
---|
82 | |
---|
83 | As there are 2.54 centimeters to 1 inch, then there are approximately 567 |
---|
84 | twips to a centimeter (the length of a screen item measuring one centimeter |
---|
85 | when printed). |
---|
86 | |
---|
87 | Or in millimeters, as there are 25.4 millimeters to 1 inch, therefore there |
---|
88 | are approximately 56.7 twips to a millimeter (the length of a screen item |
---|
89 | measuring one millimeter when printed).""" |
---|
90 | |
---|
91 | # Width default is 12240, Height default is 15840 |
---|
92 | StandardPaper = Papers() |
---|
93 | StandardPaper.append( Paper( 'LETTER' , 1, 'Letter 8 1/2 x 11 in' , 12240, 15840 ) ) |
---|
94 | StandardPaper.append( Paper( 'LETTERSMALL' , 2, 'Letter Small 8 1/2 x 11 in' , 12240, 15840 ) ) |
---|
95 | StandardPaper.append( Paper( 'TABLOID' , 3, 'Tabloid 11 x 17 in' , 15840, 24480 ) ) |
---|
96 | StandardPaper.append( Paper( 'LEDGER' , 4, 'Ledger 17 x 11 in' , 24480, 15840 ) ) |
---|
97 | StandardPaper.append( Paper( 'LEGAL' , 5, 'Legal 8 1/2 x 14 in' , 12240, 20160 ) ) |
---|
98 | StandardPaper.append( Paper( 'STATEMENT' , 6, 'Statement 5 1/2 x 8 1/2 in' , 7920, 12240 ) ) |
---|
99 | StandardPaper.append( Paper( 'EXECUTIVE' , 7, 'Executive 7 1/4 x 10 1/2 in' , 10440, 15120 ) ) |
---|
100 | StandardPaper.append( Paper( 'A3' , 8, 'A3 297 x 420 mm' , 16838, 23811 ) ) |
---|
101 | StandardPaper.append( Paper( 'A4' , 9, 'A4 210 x 297 mm' , 11907, 16838 ) ) |
---|
102 | StandardPaper.append( Paper( 'A4SMALL' , 10, 'A4 Small 210 x 297 mm' , 11907, 16838 ) ) |
---|
103 | StandardPaper.append( Paper( 'A5' , 11, 'A5 148 x 210 mm' , 8391, 11907 ) ) |
---|
104 | StandardPaper.append( Paper( 'B4' , 12, 'B4 (JIS) 250 x 354' , 14175, 20072 ) ) |
---|
105 | StandardPaper.append( Paper( 'B5' , 13, 'B5 (JIS) 182 x 257 mm' , 10319, 14572 ) ) |
---|
106 | StandardPaper.append( Paper( 'FOLIO' , 14, 'Folio 8 1/2 x 13 in' , 12240, 18720 ) ) |
---|
107 | StandardPaper.append( Paper( 'QUARTO' , 15, 'Quarto 215 x 275 mm' , 12191, 15593 ) ) |
---|
108 | StandardPaper.append( Paper( '10X14' , 16, '10x14 in' , 14400, 20160 ) ) |
---|
109 | StandardPaper.append( Paper( '11X17' , 17, '11x17 in' , 15840, 24480 ) ) |
---|
110 | StandardPaper.append( Paper( 'NOTE' , 18, 'Note 8 1/2 x 11 in' , 12240, 15840 ) ) |
---|
111 | StandardPaper.append( Paper( 'ENV_9' , 19, 'Envelope #9 3 7/8 x 8 7/8' , 5580, 12780 ) ) |
---|
112 | StandardPaper.append( Paper( 'ENV_10' , 20, 'Envelope #10 4 1/8 x 9 1/2' , 5940, 13680 ) ) |
---|
113 | StandardPaper.append( Paper( 'ENV_11' , 21, 'Envelope #11 4 1/2 x 10 3/8' , 6480, 14940 ) ) |
---|
114 | StandardPaper.append( Paper( 'ENV_12' , 22, 'Envelope #12 4 3/4 x 11' , 6840, 15840 ) ) |
---|
115 | StandardPaper.append( Paper( 'ENV_14' , 23, 'Envelope #14 5 x 11 1/2' , 7200, 16560 ) ) |
---|
116 | StandardPaper.append( Paper( 'CSHEET' , 24, 'C size sheet 18 x 24 in' , 29520, 34560 ) ) |
---|
117 | StandardPaper.append( Paper( 'DSHEET' , 25, 'D size sheet 22 x 34 in' , 31680, 48960 ) ) |
---|
118 | StandardPaper.append( Paper( 'ESHEET' , 26, 'E size sheet 34 x 44 in' , 48960, 63360 ) ) |
---|
119 | StandardPaper.append( Paper( 'ENV_DL' , 27, 'Envelope DL 110 x 220mm' , 6237, 12474 ) ) |
---|
120 | StandardPaper.append( Paper( 'ENV_C5' , 28, 'Envelope C5 162 x 229 mm' , 9185, 12984 ) ) |
---|
121 | StandardPaper.append( Paper( 'ENV_C3' , 29, 'Envelope C3 324 x 458 mm' , 18371, 25969 ) ) |
---|
122 | StandardPaper.append( Paper( 'ENV_C4' , 30, 'Envelope C4 229 x 324 mm' , 12984, 18371 ) ) |
---|
123 | StandardPaper.append( Paper( 'ENV_C6' , 31, 'Envelope C6 114 x 162 mm' , 6464, 9185 ) ) |
---|
124 | StandardPaper.append( Paper( 'ENV_C65' , 32, 'Envelope C65 114 x 229 mm' , 6464, 12984 ) ) |
---|
125 | StandardPaper.append( Paper( 'ENV_B4' , 33, 'Envelope B4 250 x 353 mm' , 14175, 20015 ) ) |
---|
126 | StandardPaper.append( Paper( 'ENV_B5' , 34, 'Envelope B5 176 x 250 mm' , 9979, 14175 ) ) |
---|
127 | StandardPaper.append( Paper( 'ENV_B6' , 35, 'Envelope B6 176 x 125 mm' , 9979, 7088 ) ) |
---|
128 | StandardPaper.append( Paper( 'ENV_ITALY' , 36, 'Envelope 110 x 230 mm' , 6237, 13041 ) ) |
---|
129 | StandardPaper.append( Paper( 'ENV_MONARCH' , 37, 'Envelope Monarch 3.875 x 7.5 in' , 5580, 10800 ) ) |
---|
130 | StandardPaper.append( Paper( 'ENV_PERSONAL' , 38, '6 3/4 Envelope 3 5/8 x 6 1/2 in' , 5220, 9360 ) ) |
---|
131 | StandardPaper.append( Paper( 'FANFOLD_US' , 39, 'US Std Fanfold 14 7/8 x 11 in' , 21420, 15840 ) ) |
---|
132 | StandardPaper.append( Paper( 'FANFOLD_STD_GERMAN' , 40, 'German Std Fanfold 8 1/2 x 12 in' , 12240, 17280 ) ) |
---|
133 | StandardPaper.append( Paper( 'FANFOLD_LGL_GERMAN' , 41, 'German Legal Fanfold 8 1/2 x 13 in' , 12240, 18720 ) ) |
---|
134 | |
---|
135 | # |
---|
136 | # Finally a StyleSheet in which all of this stuff is put together |
---|
137 | # |
---|
138 | class StyleSheet : |
---|
139 | def __init__( self, colours=None, fonts=None ) : |
---|
140 | |
---|
141 | self.Colours = colours or deepcopy( StandardColours ) |
---|
142 | self.Fonts = fonts or deepcopy( StandardFonts ) |
---|
143 | |
---|
144 | self.TextStyles = AttributedList() |
---|
145 | self.ParagraphStyles = AttributedList() |
---|
146 | |
---|
147 | class Section( list ) : |
---|
148 | NONE = 1 |
---|
149 | COLUMN = 2 |
---|
150 | PAGE = 3 |
---|
151 | EVEN = 4 |
---|
152 | ODD = 5 |
---|
153 | BREAK_TYPES = [ NONE, COLUMN, PAGE, EVEN, ODD ] |
---|
154 | |
---|
155 | def __init__( self, paper=None, margins=None, break_type=None, headery=None, footery=None, landscape=None, first_page_number=None ) : |
---|
156 | super( Section, self ).__init__() |
---|
157 | |
---|
158 | self.Paper = paper or StandardPaper.A4 |
---|
159 | self.SetMargins( margins ) |
---|
160 | |
---|
161 | self.Header = [] |
---|
162 | self.Footer = [] |
---|
163 | self.FirstHeader = [] |
---|
164 | self.FirstFooter = [] |
---|
165 | |
---|
166 | self.SetBreakType( break_type or self.NONE ) |
---|
167 | self.SetHeaderY( headery ) |
---|
168 | self.SetFooterY( footery ) |
---|
169 | self.SetLandscape( landscape ) |
---|
170 | self.SetFirstPageNumber( first_page_number ) |
---|
171 | |
---|
172 | def TwipsToRightMargin( self ) : |
---|
173 | return self.Paper.Width - ( self.Margins.Left + self.Margins.Right ) |
---|
174 | |
---|
175 | def SetMargins( self, value ) : |
---|
176 | self.Margins = value or MarginsPropertySet( top=1000, left=1200, bottom=1000, right=1200 ) |
---|
177 | self.Width = self.Paper.Width - ( self.Margins.Left + self.Margins.Right ) |
---|
178 | |
---|
179 | def SetBreakType( self, value ) : |
---|
180 | assert value in self.BREAK_TYPES |
---|
181 | self.BreakType = value |
---|
182 | return self |
---|
183 | |
---|
184 | def SetHeaderY( self, value ) : |
---|
185 | self.HeaderY = value |
---|
186 | return self |
---|
187 | |
---|
188 | def SetFooterY( self, value ) : |
---|
189 | self.FooterY = value |
---|
190 | return self |
---|
191 | |
---|
192 | def SetLandscape( self, value ) : |
---|
193 | self.Landscape = False |
---|
194 | if value : self.Landscape = True |
---|
195 | return self |
---|
196 | |
---|
197 | def SetFirstPageNumber( self, value ) : |
---|
198 | self.FirstPageNumber = value |
---|
199 | return self |
---|
200 | |
---|
201 | def MakeDefaultStyleSheet( ) : |
---|
202 | result = StyleSheet() |
---|
203 | |
---|
204 | NormalText = TextStyle( TextPropertySet( result.Fonts.Arial, 22 ) ) |
---|
205 | |
---|
206 | ps = ParagraphStyle( 'Normal', |
---|
207 | NormalText.Copy(), |
---|
208 | ParagraphPropertySet( space_before = 60, |
---|
209 | space_after = 60 ) ) |
---|
210 | result.ParagraphStyles.append( ps ) |
---|
211 | |
---|
212 | ps = ParagraphStyle( 'Normal Short', |
---|
213 | NormalText.Copy() ) |
---|
214 | result.ParagraphStyles.append( ps ) |
---|
215 | |
---|
216 | NormalText.TextPropertySet.SetSize( 32 ) |
---|
217 | ps = ParagraphStyle( 'Heading 1', |
---|
218 | NormalText.Copy(), |
---|
219 | ParagraphPropertySet( space_before = 240, |
---|
220 | space_after = 60 ) ) |
---|
221 | result.ParagraphStyles.append( ps ) |
---|
222 | |
---|
223 | NormalText.TextPropertySet.SetSize( 24 ).SetBold( True ) |
---|
224 | ps = ParagraphStyle( 'Heading 2', |
---|
225 | NormalText.Copy(), |
---|
226 | ParagraphPropertySet( space_before = 240, |
---|
227 | space_after = 60 ) ) |
---|
228 | result.ParagraphStyles.append( ps ) |
---|
229 | |
---|
230 | # Add some more in that are based on the normal template but that |
---|
231 | # have some indenting set that makes them suitable for doing numbered |
---|
232 | normal_numbered = result.ParagraphStyles.Normal.Copy() |
---|
233 | normal_numbered.SetName( 'Normal Numbered' ) |
---|
234 | normal_numbered.ParagraphPropertySet.SetFirstLineIndent( TabPropertySet.DEFAULT_WIDTH * -1 ) |
---|
235 | normal_numbered.ParagraphPropertySet.SetLeftIndent ( TabPropertySet.DEFAULT_WIDTH ) |
---|
236 | |
---|
237 | result.ParagraphStyles.append( normal_numbered ) |
---|
238 | |
---|
239 | normal_numbered2 = result.ParagraphStyles.Normal.Copy() |
---|
240 | normal_numbered2.SetName( 'Normal Numbered 2' ) |
---|
241 | normal_numbered2.ParagraphPropertySet.SetFirstLineIndent( TabPropertySet.DEFAULT_WIDTH * -1 ) |
---|
242 | normal_numbered2.ParagraphPropertySet.SetLeftIndent ( TabPropertySet.DEFAULT_WIDTH * 2 ) |
---|
243 | |
---|
244 | result.ParagraphStyles.append( normal_numbered2 ) |
---|
245 | |
---|
246 | ## LIST STYLES |
---|
247 | for idx, indent in [ (1, TabPS.DEFAULT_WIDTH ), |
---|
248 | (2, TabPS.DEFAULT_WIDTH * 2), |
---|
249 | (3, TabPS.DEFAULT_WIDTH * 3) ] : |
---|
250 | indent = TabPropertySet.DEFAULT_WIDTH |
---|
251 | ps = ParagraphStyle( 'List %s' % idx, |
---|
252 | TextStyle( TextPropertySet( result.Fonts.Arial, 22 ) ), |
---|
253 | ParagraphPropertySet( space_before = 60, |
---|
254 | space_after = 60, |
---|
255 | first_line_indent = -indent, |
---|
256 | left_indent = indent) ) |
---|
257 | result.ParagraphStyles.append( ps ) |
---|
258 | |
---|
259 | return result |
---|
260 | |
---|
261 | class TAB : pass |
---|
262 | class LINE : pass |
---|
263 | |
---|
264 | class RawCode : |
---|
265 | def __init__( self, data ) : |
---|
266 | self.Data = data |
---|
267 | |
---|
268 | PAGE_NUMBER = RawCode( r'{\field{\fldinst page}}' ) |
---|
269 | TOTAL_PAGES = RawCode( r'{\field{\fldinst numpages}}' ) |
---|
270 | SECTION_PAGES = RawCode( r'{\field{\fldinst sectionpages}}' ) |
---|
271 | ARIAL_BULLET = RawCode( r'{\f2\'95}' ) |
---|
272 | |
---|
273 | def _get_jpg_dimensions( fin ): |
---|
274 | """ |
---|
275 | converted from: http://dev.w3.org/cvsweb/Amaya/libjpeg/rdjpgcom.c?rev=1.2 |
---|
276 | """ |
---|
277 | |
---|
278 | M_SOF0 = chr( 0xC0 ) # /* Start Of Frame N */ |
---|
279 | M_SOF1 = chr( 0xC1 ) # /* N indicates which compression process */ |
---|
280 | M_SOF2 = chr( 0xC2 ) # /* Only SOF0-SOF2 are now in common use */ |
---|
281 | M_SOF3 = chr( 0xC3 ) # |
---|
282 | M_SOF5 = chr( 0xC5 ) # /* NB: codes C4 and CC are NOT SOF markers */ |
---|
283 | M_SOF6 = chr( 0xC6 ) # |
---|
284 | M_SOF7 = chr( 0xC7 ) # |
---|
285 | M_SOF9 = chr( 0xC9 ) # |
---|
286 | M_SOF10 = chr( 0xCA ) # |
---|
287 | M_SOF11 = chr( 0xCB ) # |
---|
288 | M_SOF13 = chr( 0xCD ) # |
---|
289 | M_SOF14 = chr( 0xCE ) # |
---|
290 | M_SOF15 = chr( 0xCF ) # |
---|
291 | M_SOI = chr( 0xD8 ) # /* Start Of Image (beginning of datastream) */ |
---|
292 | M_EOI = chr( 0xD9 ) # /* End Of Image (end of datastream) */ |
---|
293 | |
---|
294 | M_FF = chr( 0xFF ) |
---|
295 | |
---|
296 | MARKERS = [ M_SOF0, M_SOF1, M_SOF2, M_SOF3, |
---|
297 | M_SOF5, M_SOF6, M_SOF7, M_SOF9, |
---|
298 | M_SOF10,M_SOF11, M_SOF13, M_SOF14, |
---|
299 | M_SOF15 ] |
---|
300 | |
---|
301 | def get_length() : |
---|
302 | b1 = fin.read( 1 ) |
---|
303 | b2 = fin.read( 1 ) |
---|
304 | return (ord(b1) << 8) + ord(b2) |
---|
305 | |
---|
306 | def next_marker() : |
---|
307 | # markers come straight after an 0xFF so skip everything |
---|
308 | # up to the first 0xFF that we find |
---|
309 | while fin.read(1) != M_FF : |
---|
310 | pass |
---|
311 | |
---|
312 | # there can be more than one 0xFF as they can be used |
---|
313 | # for padding so we are now looking for the first byte |
---|
314 | # that isn't an 0xFF, this will be the marker |
---|
315 | while True : |
---|
316 | result = fin.read(1) |
---|
317 | if result != M_FF : |
---|
318 | return result |
---|
319 | |
---|
320 | raise Exception( 'Invalid JPEG' ) |
---|
321 | |
---|
322 | # BODY OF THE FUNCTION |
---|
323 | if not ((fin.read(1) == M_FF) and (fin.read(1) == M_SOI)) : |
---|
324 | raise Exception( 'Invalid Jpeg' ) |
---|
325 | |
---|
326 | while True : |
---|
327 | marker = next_marker() |
---|
328 | |
---|
329 | # the marker is always followed by two bytes representing the length of the data field |
---|
330 | length = get_length () |
---|
331 | if length < 2 : raise Exception( "Erroneous JPEG marker length" ) |
---|
332 | |
---|
333 | # if it is a compression process marker then it will contain the dimension of the image |
---|
334 | if marker in MARKERS : |
---|
335 | # the next byte is the data precision, just skip it |
---|
336 | fin.read(1) |
---|
337 | |
---|
338 | # bingo |
---|
339 | image_height = get_length() |
---|
340 | image_width = get_length() |
---|
341 | return image_width, image_height |
---|
342 | |
---|
343 | # just skip whatever data it contains |
---|
344 | fin.read( length - 2 ) |
---|
345 | |
---|
346 | raise Exception( 'Invalid JPEG, end of stream reached' ) |
---|
347 | |
---|
348 | |
---|
349 | _PNG_HEADER = '\x89\x50\x4e' |
---|
350 | def _get_png_dimensions( data ) : |
---|
351 | if data[0:3] != _PNG_HEADER : |
---|
352 | raise Exception( 'Invalid PNG image' ) |
---|
353 | |
---|
354 | width = (ord(data[18]) * 256) + (ord(data[19])) |
---|
355 | height = (ord(data[22]) * 256) + (ord(data[23])) |
---|
356 | return width, height |
---|
357 | |
---|
358 | def _get_emf_dimensions( fin ): |
---|
359 | import struct |
---|
360 | def get_DWORD(): |
---|
361 | return struct.unpack("<L",fin.read(4))[0] |
---|
362 | def get_LONG(): |
---|
363 | return struct.unpack("<l",fin.read(4))[0] |
---|
364 | def get_WORD(): |
---|
365 | return struct.unpack("<H",fin.read(2))[0] |
---|
366 | class Empty: |
---|
367 | pass |
---|
368 | header = Empty() |
---|
369 | header.RecordType = get_DWORD() # Record type |
---|
370 | header.RecordSize = get_DWORD() # Size of the record in bytes |
---|
371 | header.BoundsLeft = get_LONG() # Left inclusive bounds |
---|
372 | header.BoundsTop = get_LONG() # Top inclusive bounds |
---|
373 | header.BoundsRight = get_LONG() # Right inclusive bounds |
---|
374 | header.BoundsBottom = get_LONG() # Bottom inclusive bounds |
---|
375 | header.FrameLeft = get_LONG() # Left side of inclusive picture frame |
---|
376 | header.FrameTop = get_LONG() # Top side of inclusive picture frame |
---|
377 | header.FrameRight = get_LONG() # Right side of inclusive picture frame |
---|
378 | header.FrameBottom = get_LONG() # Bottom side of inclusive picture frame |
---|
379 | header.Signature = get_DWORD() # Signature ID (always 0x464D4520) |
---|
380 | header.Version = get_DWORD() # Version of the metafile |
---|
381 | header.Size = get_DWORD() # Size of the metafile in bytes |
---|
382 | header.NumOfRecords = get_DWORD() # Number of records in the metafile |
---|
383 | header.NumOfHandles = get_WORD() # Number of handles in the handle table |
---|
384 | header.Reserved = get_WORD() # Not used (always 0) |
---|
385 | header.SizeOfDescrip = get_DWORD() # Size of description string in WORDs |
---|
386 | header.OffsOfDescrip = get_DWORD() # Offset of description string in metafile |
---|
387 | header.NumPalEntries = get_DWORD() # Number of color palette entries |
---|
388 | header.WidthDevPixels = get_LONG() # Width of reference device in pixels |
---|
389 | header.HeightDevPixels = get_LONG() # Height of reference device in pixels |
---|
390 | header.WidthDevMM = get_LONG() # Width of reference device in millimeters |
---|
391 | header.HeightDevMM = get_LONG() # Height of reference device in millimeters |
---|
392 | |
---|
393 | if 0: |
---|
394 | klist = sorted(header.__dict__.keys()) |
---|
395 | for k in klist: |
---|
396 | print("%20s:%s" % (k,header.__dict__[k])) |
---|
397 | |
---|
398 | dw = header.FrameRight-header.FrameLeft |
---|
399 | dh = header.FrameBottom-header.FrameTop |
---|
400 | |
---|
401 | # convert from 0.01mm units to 1/72in units |
---|
402 | return int(dw * 72.0/2540.0), int(dh * 72.0/2540.0) |
---|
403 | |
---|
404 | class Image( RawCode ) : |
---|
405 | |
---|
406 | # Need to add in the width and height in twips as it crashes |
---|
407 | # word xp with these values. Still working out the most |
---|
408 | # efficient way of getting these values. |
---|
409 | # \picscalex100\picscaley100\piccropl0\piccropr0\piccropt0\piccropb0 |
---|
410 | # picwgoal900\pichgoal281 |
---|
411 | |
---|
412 | PNG_LIB = 'pngblip' |
---|
413 | JPG_LIB = 'jpegblip' |
---|
414 | EMF_LIB = 'emfblip' |
---|
415 | PICT_TYPES = { 'png' : PNG_LIB, |
---|
416 | 'jpg' : JPG_LIB, |
---|
417 | 'emf' : EMF_LIB} |
---|
418 | |
---|
419 | def __init__( self, infile, **kwargs ) : |
---|
420 | |
---|
421 | if hasattr( infile, 'read' ): |
---|
422 | fin = infile |
---|
423 | if 'datatype' not in kwargs.keys(): |
---|
424 | msg = "If passing in a file object, you must also specify type='xxx' where xxx is one of %s" % self.PICT_TYPES.keys() |
---|
425 | raise ValueError(msg) |
---|
426 | file_name = kwargs.pop('datatype') |
---|
427 | else: |
---|
428 | fin = file( infile, 'rb' ) |
---|
429 | file_name = infile |
---|
430 | |
---|
431 | pict_type = self.PICT_TYPES[ file_name[ -3 : ].lower() ] |
---|
432 | if pict_type == self.PNG_LIB : |
---|
433 | width, height = _get_png_dimensions( fin.read( 100 ) ) |
---|
434 | elif pict_type == self.JPG_LIB : |
---|
435 | width, height = _get_jpg_dimensions( fin ) |
---|
436 | elif pict_type == self.EMF_LIB : |
---|
437 | width, height = _get_emf_dimensions( fin ) |
---|
438 | |
---|
439 | |
---|
440 | # if user specified height or width but not both, then |
---|
441 | # scale unspecified dimension to maintain aspect ratio |
---|
442 | |
---|
443 | if ('width' in kwargs) and ('height' not in kwargs): |
---|
444 | height = int(height * float(kwargs['width'])/width) |
---|
445 | elif ('height' in kwargs) and ('width' not in kwargs): |
---|
446 | width = int(width * float(kwargs['height'])/height) |
---|
447 | |
---|
448 | width = kwargs.pop('width',width) |
---|
449 | height = kwargs.pop('height', height) |
---|
450 | |
---|
451 | codes = [ pict_type, |
---|
452 | 'picwgoal%s' % (width * 20), |
---|
453 | 'pichgoal%s' % (height * 20) ] |
---|
454 | # let user specify global scaling |
---|
455 | scale = kwargs.pop('scale',100) |
---|
456 | |
---|
457 | for kwarg, code, default in [ ( 'scale_x', 'scalex', scale ), |
---|
458 | ( 'scale_y', 'scaley', scale ), |
---|
459 | ( 'crop_left', 'cropl', '0' ), |
---|
460 | ( 'crop_right', 'cropr', '0' ), |
---|
461 | ( 'crop_top', 'cropt', '0' ), |
---|
462 | ( 'crop_bottom', 'cropb', '0' ) ] : |
---|
463 | codes.append( 'pic%s%s' % ( code, kwargs.pop( kwarg, default ) ) ) |
---|
464 | |
---|
465 | |
---|
466 | # reset back to the start of the file to get all of it and now |
---|
467 | # turn it into hex. |
---|
468 | fin.seek( 0, 0 ) |
---|
469 | image = hexlify( fin.read() ) |
---|
470 | fin.close() |
---|
471 | data = [] |
---|
472 | for i in range( 0, len( image ), 128 ) : |
---|
473 | data.append( image[ i : i + 128 ] ) |
---|
474 | |
---|
475 | data = r'{\pict{\%s}%s}' % ( '\\'.join( codes ), '\n'.join( data ) ) |
---|
476 | RawCode.__init__( self, data ) |
---|
477 | |
---|
478 | def ToRawCode( self, var_name ) : |
---|
479 | return '%s = RawCode( """%s""" )' % ( var_name, self.Data ) |
---|
480 | |
---|
481 | class Text : |
---|
482 | def __init__( self, *params ) : |
---|
483 | self.Data = None |
---|
484 | self.Style = None |
---|
485 | self.Properties = None |
---|
486 | self.Shading = None |
---|
487 | |
---|
488 | for param in params : |
---|
489 | if isinstance( param, TextStyle ) : self.Style = param |
---|
490 | elif isinstance( param, TextPS ) : self.Properties = param |
---|
491 | elif isinstance( param, ShadingPS ) : self.Shading = param |
---|
492 | else : |
---|
493 | # otherwise let the rendering custom handler sort it out itself |
---|
494 | self.Data = param |
---|
495 | |
---|
496 | def SetData( self, value ) : |
---|
497 | self.Data = value |
---|
498 | |
---|
499 | class Inline( list ) : |
---|
500 | def __init__( self, *params ) : |
---|
501 | super( Inline, self ).__init__() |
---|
502 | |
---|
503 | self.Style = None |
---|
504 | self.Properties = None |
---|
505 | self.Shading = None |
---|
506 | |
---|
507 | self._append = super( Inline, self ).append |
---|
508 | |
---|
509 | for param in params : |
---|
510 | if isinstance( param, TextStyle ) : self.Style = param |
---|
511 | elif isinstance( param, TextPS ) : self.Properties = param |
---|
512 | elif isinstance( param, ShadingPS ) : self.Shading = param |
---|
513 | else : |
---|
514 | # otherwise we add to it to our list of elements and let |
---|
515 | # the rendering custom handler sort it out itself. |
---|
516 | self.append( param ) |
---|
517 | |
---|
518 | def append( self, *params ) : |
---|
519 | # filter out any that are explicitly None |
---|
520 | [ self._append( param ) for param in params if param is not None ] |
---|
521 | |
---|
522 | class Paragraph( list ) : |
---|
523 | def __init__( self, *params ) : |
---|
524 | super( Paragraph, self ).__init__() |
---|
525 | |
---|
526 | self.Style = None |
---|
527 | self.Properties = None |
---|
528 | self.Frame = None |
---|
529 | self.Shading = None |
---|
530 | |
---|
531 | self._append = super( Paragraph, self ).append |
---|
532 | |
---|
533 | for param in params : |
---|
534 | if isinstance( param, ParagraphStyle ) : self.Style = param |
---|
535 | elif isinstance( param, ParagraphPS ) : self.Properties = param |
---|
536 | elif isinstance( param, FramePS ) : self.Frame = param |
---|
537 | elif isinstance( param, ShadingPS ) : self.Shading = param |
---|
538 | else : |
---|
539 | # otherwise we add to it to our list of elements and let |
---|
540 | # the rendering custom handler sort it out itself. |
---|
541 | self.append( param ) |
---|
542 | |
---|
543 | def append( self, *params ) : |
---|
544 | # filter out any that are explicitly None |
---|
545 | [ self._append( param ) for param in params if param is not None ] |
---|
546 | |
---|
547 | def insert( self, index, value ) : |
---|
548 | if value is not None : |
---|
549 | super( Paragraph, self ).insert( index, value ) |
---|
550 | |
---|
551 | class Table : |
---|
552 | LEFT = 1 |
---|
553 | RIGHT = 2 |
---|
554 | CENTER = 3 |
---|
555 | ALIGNMENT = [ LEFT, RIGHT, CENTER ] |
---|
556 | |
---|
557 | NO_WRAPPING = 1 |
---|
558 | WRAP_AROUND = 2 |
---|
559 | WRAPPING = [ NO_WRAPPING, WRAP_AROUND ] |
---|
560 | |
---|
561 | # trrh height of row, 0 means automatically adjust, use negative for an absolute |
---|
562 | # trgaph is half of the space between a table cell in width, reduce this one |
---|
563 | # to get a really tiny column |
---|
564 | |
---|
565 | def __init__( self, *column_widths, **kwargs ) : |
---|
566 | |
---|
567 | self.Rows = [] |
---|
568 | |
---|
569 | self.SetAlignment ( kwargs.pop( 'alignment', self.LEFT ) ) |
---|
570 | self.SetLeftOffset ( kwargs.pop( 'left_offset', None ) ) |
---|
571 | self.SetGapBetweenCells( kwargs.pop( 'gap_between_cells', None ) ) |
---|
572 | self.SetColumnWidths ( *column_widths ) |
---|
573 | |
---|
574 | assert not kwargs, 'invalid keyword args %s' % kwargs |
---|
575 | |
---|
576 | def SetAlignment( self, value ) : |
---|
577 | assert value is None or value in self.ALIGNMENT |
---|
578 | self.Alignment = value or self.LEFT |
---|
579 | return self |
---|
580 | |
---|
581 | def SetLeftOffset( self, value ) : |
---|
582 | self.LeftOffset = value |
---|
583 | return self |
---|
584 | |
---|
585 | def SetGapBetweenCells( self, value ) : |
---|
586 | self.GapBetweenCells = value |
---|
587 | return self |
---|
588 | |
---|
589 | def SetColumnWidths( self, *column_widths ) : |
---|
590 | self.ColumnWidths = column_widths |
---|
591 | self.ColumnCount = len( column_widths ) |
---|
592 | return self |
---|
593 | |
---|
594 | def AddRow( self, *cells ) : |
---|
595 | height = None |
---|
596 | if isinstance( cells[ 0 ], NumberTypes ): |
---|
597 | height = int( cells[ 0 ] ) |
---|
598 | cells = cells[ 1 : ] |
---|
599 | |
---|
600 | # make sure all of the spans add up to the number of columns |
---|
601 | # otherwise the table will get corrupted |
---|
602 | if self.ColumnCount != sum( [ cell.Span for cell in cells ] ) : |
---|
603 | raise Exception( 'ColumnCount != the total of this row\'s cell.Spans.' ) |
---|
604 | |
---|
605 | self.Rows.append( ( height, cells ) ) |
---|
606 | |
---|
607 | append = AddRow |
---|
608 | |
---|
609 | class Cell( list ) : |
---|
610 | |
---|
611 | """ |
---|
612 | \clvertalt Text is top-aligned in cell (the default). |
---|
613 | \clvertalc Text is centered vertically in cell. |
---|
614 | \clvertalb Text is bottom-aligned in cell. |
---|
615 | \cltxlrtb Vertical text aligned left (direction bottom up). |
---|
616 | \cltxtbrl Vertical text aligned right (direction top down). |
---|
617 | """ |
---|
618 | |
---|
619 | ALIGN_TOP = 1 |
---|
620 | ALIGN_CENTER = 2 |
---|
621 | ALIGN_BOTTOM = 3 |
---|
622 | |
---|
623 | FLOW_LR_TB = 1 |
---|
624 | FLOW_RL_TB = 2 |
---|
625 | FLOW_LR_BT = 3 |
---|
626 | FLOW_VERTICAL_LR_TB = 4 |
---|
627 | FLOW_VERTICAL_TB_RL = 5 |
---|
628 | |
---|
629 | def __init__( self, *params, **kwargs ) : |
---|
630 | super( Cell, self ).__init__() |
---|
631 | |
---|
632 | self.SetFrame ( None ) |
---|
633 | self.SetMargins( None ) |
---|
634 | |
---|
635 | self.SetAlignment( kwargs.get( 'alignment', self.ALIGN_TOP ) ) |
---|
636 | self.SetFlow ( kwargs.get( 'flow' , self.FLOW_LR_TB ) ) |
---|
637 | self.SetSpan ( kwargs.get( 'span', 1 ) ) |
---|
638 | |
---|
639 | self.SetStartVerticalMerge( kwargs.get( 'start_vertical_merge', False ) ) |
---|
640 | self.SetVerticalMerge ( kwargs.get( 'vertical_merge', False ) ) |
---|
641 | |
---|
642 | self._append = super( Cell, self ).append |
---|
643 | |
---|
644 | for param in params : |
---|
645 | if isinstance( param, StringType ) : self.append ( param ) |
---|
646 | elif isinstance( param, Paragraph ) : self.append ( param ) |
---|
647 | elif isinstance( param, FramePS ) : self.SetFrame ( param ) |
---|
648 | elif isinstance( param, MarginsPS ) : self.SetMargins( param ) |
---|
649 | |
---|
650 | def SetFrame( self, value ) : |
---|
651 | self.Frame = value |
---|
652 | return self |
---|
653 | |
---|
654 | def SetMargins( self, value ) : |
---|
655 | self.Margins = value |
---|
656 | return self |
---|
657 | |
---|
658 | def SetAlignment( self, value ) : |
---|
659 | assert value in [ self.ALIGN_TOP, self.ALIGN_CENTER, self.ALIGN_BOTTOM ] #, self.ALIGN_TEXT_TOP_DOWN, self.ALIGN_TEXT_BOTTOM_UP ] |
---|
660 | self.Alignment = value |
---|
661 | |
---|
662 | def SetFlow( self, value ) : |
---|
663 | 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 ] |
---|
664 | self.Flow = value |
---|
665 | |
---|
666 | def SetSpan( self, value ) : |
---|
667 | # must be a positive integer |
---|
668 | self.Span = int( max( value, 1 ) ) |
---|
669 | return self |
---|
670 | |
---|
671 | def SetStartVerticalMerge( self, value ) : |
---|
672 | self.StartVerticalMerge = False |
---|
673 | if value : |
---|
674 | self.StartVerticalMerge = True |
---|
675 | return self |
---|
676 | |
---|
677 | def SetVerticalMerge( self, value ) : |
---|
678 | self.VerticalMerge = False |
---|
679 | if value : |
---|
680 | self.VerticalMerge = True |
---|
681 | return self |
---|
682 | |
---|
683 | def append( self, *params ) : |
---|
684 | [ self._append( param ) for param in params ] |
---|
685 | |
---|
686 | class Document : |
---|
687 | def __init__( self, style_sheet=None, default_language=None, view_kind=None, view_zoom_kind=None, view_scale=None ) : |
---|
688 | self.StyleSheet = style_sheet or MakeDefaultStyleSheet() |
---|
689 | self.Sections = AttributedList( Section ) |
---|
690 | |
---|
691 | self.SetTitle( None ) |
---|
692 | |
---|
693 | self.DefaultLanguage = default_language or Languages.DEFAULT |
---|
694 | self.ViewKind = view_kind or ViewKind.DEFAULT |
---|
695 | self.ViewZoomKind = view_zoom_kind |
---|
696 | self.ViewScale = view_scale |
---|
697 | |
---|
698 | def NewSection( self, *params, **kwargs ) : |
---|
699 | result = Section( *params, **kwargs ) |
---|
700 | self.Sections.append( result ) |
---|
701 | return result |
---|
702 | |
---|
703 | def SetTitle( self, value ) : |
---|
704 | self.Title = value |
---|
705 | return self |
---|
706 | |
---|
707 | def Copy( self ) : |
---|
708 | result = Document( style_sheet = self.StyleSheet.Copy(), |
---|
709 | default_language = self.DefaultLanguage, |
---|
710 | view_kind = self.ViewKind, |
---|
711 | view_zoom_kind = self.ViewZoomKind, |
---|
712 | view_scale = self.ViewScale ) |
---|
713 | result.SetTitle( self.Title ) |
---|
714 | result.Sections = self.Sections.Copy() |
---|
715 | |
---|
716 | return result |
---|
717 | |
---|
718 | def TEXT( *params, **kwargs ) : |
---|
719 | text_props = TextPropertySet() |
---|
720 | text_props.SetFont ( kwargs.get( 'font', None ) ) |
---|
721 | text_props.SetSize ( kwargs.get( 'size', None ) ) |
---|
722 | text_props.SetBold ( kwargs.get( 'bold', False ) ) |
---|
723 | text_props.SetItalic ( kwargs.get( 'italic', False ) ) |
---|
724 | text_props.SetUnderline( kwargs.get( 'underline', False ) ) |
---|
725 | text_props.SetColour ( kwargs.get( 'colour', None ) ) |
---|
726 | |
---|
727 | if len( params ) == 1 : |
---|
728 | return Text( params[ 0 ], text_props ) |
---|
729 | |
---|
730 | result = Inline( text_props ) |
---|
731 | result.append(*params) |
---|
732 | return result |
---|
733 | |
---|
734 | def B( *params ) : |
---|
735 | text_props = TextPropertySet( bold=True ) |
---|
736 | |
---|
737 | if len( params ) == 1 : |
---|
738 | return Text( params[ 0 ], text_props ) |
---|
739 | |
---|
740 | result = Inline( text_props ) |
---|
741 | result.append(*params) |
---|
742 | return result |
---|
743 | |
---|
744 | def I( *params ) : |
---|
745 | text_props = TextPropertySet( italic=True ) |
---|
746 | |
---|
747 | if len( params ) == 1 : |
---|
748 | return Text( params[ 0 ], text_props ) |
---|
749 | |
---|
750 | result = Inline( text_props ) |
---|
751 | result.append(*params) |
---|
752 | return result |
---|
753 | |
---|
754 | def U( *params ) : |
---|
755 | text_props = TextPropertySet( underline=True ) |
---|
756 | |
---|
757 | if len( params ) == 1 : |
---|
758 | return Text( params[ 0 ], text_props ) |
---|
759 | |
---|
760 | result = Inline( text_props ) |
---|
761 | result.append(*params) |
---|
762 | return result |
---|
763 | |
---|