~vorlon/ubuntu/saucy/gourmet/trunk

« back to all changes in this revision

Viewing changes to src/lib/exporters/pdf_exporter.py

  • Committer: Bazaar Package Importer
  • Author(s): Rolf Leggewie
  • Date: 2008-07-26 13:29:41 UTC
  • Revision ID: james.westby@ubuntu.com-20080726132941-6ldd73qmacrzz0bn
Tags: upstream-0.14.0
ImportĀ upstreamĀ versionĀ 0.14.0

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
import reportlab
 
2
from reportlab.pdfbase import pdfmetrics
 
3
from reportlab.lib.units import inch,mm
 
4
from reportlab.pdfgen import canvas
 
5
import reportlab.platypus as platypus
 
6
from reportlab.platypus.flowables import ParagraphAndImage
 
7
import reportlab.lib.pagesizes as pagesizes
 
8
import reportlab.lib.fonts as fonts
 
9
import reportlab.lib.units as units
 
10
import reportlab.lib.styles as styles
 
11
from gettext import gettext as _
 
12
from gettext import ngettext
 
13
from gourmet import convert
 
14
from gourmet import gglobals
 
15
from gourmet.gtk_extras import dialog_extras as de
 
16
from gourmet import ImageExtras
 
17
import xml.sax.saxutils
 
18
import exporter
 
19
import types, re
 
20
import tempfile, os.path
 
21
import math
 
22
from page_drawer import PageDrawer
 
23
 
 
24
PASS_REPORTLAB_UNICODE = (reportlab.Version.find('2')==0)
 
25
 
 
26
DEFAULT_PDF_ARGS = {
 
27
    #'pagesize':'letter',
 
28
    #'pagemode':'landscape',
 
29
    #'left_margin':0.3*inch,
 
30
    #'right_margin':0.3*inch,
 
31
    #'top_margin':0.3*inch,
 
32
    #'bottom_margin':0.3*inch,
 
33
    #'base_font_size':8,
 
34
    #'mode':('index_cards',(5*inch,3.5*inch))
 
35
    }
 
36
 
 
37
# Code for MCLine from:
 
38
# http://two.pairlist.net/pipermail/reportlab-users/2005-February/003695.html
 
39
class MCLine(platypus.Flowable):
 
40
    """Line flowable --- draws a line in a flowable"""
 
41
    
 
42
    def __init__(self,width):
 
43
        platypus.Flowable.__init__(self)
 
44
        self.width = width
 
45
        
 
46
    def __repr__(self):
 
47
        return "Line(w=%s)" % self.width
 
48
    
 
49
    def draw(self):
 
50
        self.canv.line(0,0,self.width,0)
 
51
 
 
52
import reportlab.lib.colors as colors
 
53
class Star (platypus.Flowable):
 
54
    '''A hand flowable.'''
 
55
    def __init__(self, size=None, fillcolor=colors.tan, strokecolor=colors.green):
 
56
        from reportlab.lib.units import inch
 
57
        if size is None: size=12 # 12 point
 
58
        self.fillcolor, self.strokecolor = fillcolor, strokecolor
 
59
        self.size = size
 
60
        # normal size is 4 inches
 
61
        
 
62
    def getSpaceBefore (self):
 
63
        return 6 # 6 points
 
64
 
 
65
    def getSpaceAfter (self):
 
66
        return 6 # 6 points
 
67
        
 
68
    def wrap(self, availW, availH):
 
69
        if self.size > availW or self.size > availH:
 
70
            if availW > availH:
 
71
                self.size = availH
 
72
            else:
 
73
                self.size = availW
 
74
        return (self.size,self.size)
 
75
 
 
76
    def draw (self):
 
77
        size = self.size # * 0.8
 
78
        self.draw_star(inner_length=size/4,
 
79
                       outer_length=size/2)
 
80
 
 
81
    def draw_circle (self, x, y, r):
 
82
        # Test...
 
83
        canvas = self.canv
 
84
        canvas.setLineWidth(0)        
 
85
        canvas.setStrokeColor(colors.grey)
 
86
        canvas.setFillColor(colors.grey)        
 
87
        p = canvas.beginPath()
 
88
        p.circle(x,y,r)
 
89
        p.close()
 
90
        canvas.drawPath(p,fill=1)
 
91
 
 
92
    def draw_half_star (self, inner_length=1*inch, outer_length=2*inch, points=5, origin=None):
 
93
        canvas = self.canv
 
94
        canvas.setLineWidth(0)
 
95
        if not origin: canvas.translate(self.size*0.5,self.size*0.5)
 
96
        else: canvas.translate(*origin)
 
97
        canvas.setFillColor(self.fillcolor)
 
98
        canvas.setStrokeColor(self.strokecolor)
 
99
        p = canvas.beginPath()
 
100
        inner = False # Start on top
 
101
        is_origin = True
 
102
        #print 'Drawing star with radius',outer_length,'(moving origin ',origin,')'
 
103
        for theta in range(0,360,360/(points*2)):
 
104
            if 0 < theta < 180: continue
 
105
            if inner: r = inner_length
 
106
            else: r = outer_length
 
107
            x = (math.sin(math.radians(theta)) * r)
 
108
            y = (math.cos(math.radians(theta)) * r)
 
109
            #print 'POINT:',x,y
 
110
            if is_origin:
 
111
                p.moveTo(x,y)
 
112
                is_origin = False
 
113
            else:
 
114
                p.lineTo(x,y)
 
115
            inner = not inner
 
116
        p.close()
 
117
        canvas.drawPath(p,fill=1)
 
118
 
 
119
    def draw_star (self, inner_length=1*inch, outer_length=2*inch, points=5, origin=None):
 
120
        canvas = self.canv
 
121
        canvas.setLineWidth(0)
 
122
        if not origin: canvas.translate(self.size*0.5,self.size*0.5)
 
123
        else: canvas.translate(*origin)
 
124
        canvas.setFillColor(self.fillcolor)
 
125
        canvas.setStrokeColor(self.strokecolor)
 
126
        p = canvas.beginPath()
 
127
        inner = False # Start on top
 
128
        is_origin = True
 
129
        #print 'Drawing star with radius',outer_length,'(moving origin ',origin,')'
 
130
        for theta in range(0,360,360/(points*2)):
 
131
            if inner: r = inner_length
 
132
            else: r = outer_length
 
133
            x = (math.sin(math.radians(theta)) * r)
 
134
            y = (math.cos(math.radians(theta)) * r)
 
135
            #print 'POINT:',x,y
 
136
            if is_origin:
 
137
                p.moveTo(x,y)
 
138
                is_origin = False
 
139
            else:
 
140
                p.lineTo(x,y)
 
141
            inner = not inner
 
142
        p.close()
 
143
        canvas.drawPath(p,fill=1)
 
144
 
 
145
 
 
146
class FiveStars (Star):
 
147
 
 
148
    def __init__ (self, height, filled=5, out_of=5,
 
149
                  filled_color=colors.black,
 
150
                  unfilled_color=colors.lightgrey
 
151
                  ):
 
152
        self.height = self.size = height
 
153
        self.filled = filled
 
154
        self.out_of = out_of
 
155
        self.filled_color = filled_color; self.unfilled_color = unfilled_color
 
156
        self.width = self.height * self.out_of + (self.height * 0.2 * (self.out_of-1)) # 20% padding
 
157
        self.ratio = self.height / 12 # 12 point is standard
 
158
        
 
159
 
 
160
    def wrap (self, *args):
 
161
        return self.width,self.height
 
162
 
 
163
    def draw (self):
 
164
        #self.canv.scale(self.ratio,self.ratio)
 
165
        self.draw_stars()
 
166
 
 
167
    def draw_stars (self):
 
168
        #if self.height
 
169
        for n in range(self.out_of):
 
170
            if self.filled - n >= 1:
 
171
                # Then we draw a gold star
 
172
                self.fillcolor,self.strokecolor = self.filled_color,self.filled_color
 
173
                r = self.height * 0.5
 
174
            else:
 
175
                self.fillcolor,self.strokecolor = self.unfilled_color,self.unfilled_color
 
176
                r = self.height * 0.75 * 0.5
 
177
            origin = (
 
178
                # X coordinate
 
179
                ((n >= 1 and (self.height * 1.2)) or self.height*0.5),
 
180
                # Y coordinate
 
181
                ((n < 1 and self.height* 0.5) or 0)
 
182
                )
 
183
            #print 'origin = ',#origin,'or',
 
184
            #print origin[0]/self.height,origin[1]/self.height
 
185
            #self.draw_circle(origin[0],origin[1],r)
 
186
            self.draw_star(points=5,origin=origin,inner_length=r/2,outer_length=r)
 
187
            if self.filled - n == 0.5:
 
188
                # If we're a half star...
 
189
                self.fillcolor,self.strokecolor = self.filled_color,self.filled_color                
 
190
                self.draw_half_star(points=5,
 
191
                                    inner_length=self.height*0.25,
 
192
                                    outer_length=self.height*0.5,
 
193
                                    origin=(0,0)
 
194
                                    )
 
195
            
 
196
# Copied from http://two.pairlist.net/pipermail/reportlab-users/2004-April/002917.html
 
197
# A convenience class for bookmarking
 
198
class Bookmark(platypus.Flowable):
 
199
    """ Utility class to display PDF bookmark. """
 
200
 
 
201
    def __init__(self, title, key):
 
202
        self.title = title
 
203
        self.key = key
 
204
        platypus.Flowable.__init__(self)
 
205
 
 
206
    def wrap(self, availWidth, availHeight):
 
207
        """ Doesn't take up any space. """
 
208
        return (0, 0)
 
209
 
 
210
    def draw(self):
 
211
        # set the bookmark outline to show when the file's opened
 
212
        self.canv.showOutline()
 
213
        # step 1: put a bookmark on the
 
214
        self.canv.bookmarkPage(str(self.key))
 
215
        # step 2: put an entry in the bookmark outline
 
216
        self.canv.addOutlineEntry(self.title,
 
217
                                  self.key, 0, 0)
 
218
 
 
219
 
 
220
class PdfWriter:
 
221
 
 
222
    def __init__ (self):
 
223
        pass
 
224
 
 
225
    def setup_document (self, file, mode=('column',1), size='default', pagesize='letter',
 
226
                        pagemode='portrait',left_margin=inch,right_margin=inch,
 
227
                        top_margin=inch,
 
228
                        bottom_margin=inch,
 
229
                        base_font_size=10
 
230
                        ):
 
231
        frames = self.setup_frames(mode,size,pagesize,pagemode,
 
232
                                   left_margin,right_margin,top_margin,
 
233
                                   bottom_margin,base_font_size)
 
234
        pt = platypus.PageTemplate(frames=frames)
 
235
        self.doc = platypus.BaseDocTemplate(file,pagesize=self.pagesize,
 
236
                                            pageTemplates=[pt],)
 
237
        self.doc.frame_width = frames[0].width
 
238
        self.doc.frame_height = frames[0].height
 
239
        self.styleSheet = styles.getSampleStyleSheet()
 
240
        perc_scale = float(base_font_size)/self.styleSheet['Normal'].fontSize
 
241
        if perc_scale!=1.0:
 
242
            self.scale_stylesheet(perc_scale)
 
243
        self.txt = []
 
244
 
 
245
    def setup_frames (self,mode=('column',1), size='default', pagesize='letter',
 
246
                        pagemode='portrait',left_margin=inch,right_margin=inch,
 
247
                        top_margin=inch,
 
248
                        bottom_margin=inch,
 
249
                        base_font_size=10):
 
250
        if type(mode)!=tuple: raise "What is this mode! %s"%str(mode)
 
251
        if type(pagesize) in types.StringTypes:
 
252
            self.pagesize = getattr(pagesizes,pagemode)(getattr(pagesizes,pagesize))
 
253
        else:
 
254
            self.pagesize = getattr(pagesizes,pagemode)(pagesize)
 
255
        self.margins = (left_margin,right_margin,top_margin,bottom_margin)
 
256
        if mode[0] == 'column':
 
257
            frames = self.setup_column_frames(mode[1])
 
258
        elif mode[0] == 'index_cards':
 
259
            frames = self.setup_multiple_index_cards(mode[1])
 
260
        else:
 
261
            raise("WTF - mode = %s"%str(mode))
 
262
        return frames
 
263
 
 
264
    def scale_stylesheet (self, perc):
 
265
        for name,sty in self.styleSheet.byName.items():
 
266
            for attr in ['firstLineIndent',
 
267
                         'fontSize',
 
268
                         'leftIndent',
 
269
                         'rightIndent',
 
270
                         'leading']:
 
271
                setattr(sty,attr,int(perc*getattr(sty,attr)))
 
272
        
 
273
    def setup_column_frames (self, n):
 
274
        COLUMN_SEPARATOR = 0.5 * inch
 
275
        x = self.pagesize[0]
 
276
        y = self.pagesize[1]
 
277
        leftM,rightM,topM,bottomM = self.margins
 
278
        FRAME_Y = bottomM
 
279
        FRAME_HEIGHT = y - topM - bottomM
 
280
        FRAME_WIDTH = (x - (COLUMN_SEPARATOR*(n-1)) - leftM - rightM)/n
 
281
        frames = []
 
282
        for i in range(n):
 
283
            left_start = leftM + (FRAME_WIDTH + COLUMN_SEPARATOR)*i
 
284
            frames.append(
 
285
                platypus.Frame(
 
286
                left_start,FRAME_Y,width=FRAME_WIDTH,height=FRAME_HEIGHT
 
287
                )
 
288
                )
 
289
        return frames
 
290
 
 
291
    def setup_multiple_index_cards (self,card_size):
 
292
        leftM,rightM,topM,bottomM = self.margins
 
293
        MINIMUM_SPACING = 0.1*inch
 
294
        drawable_x = self.pagesize[0] - leftM - rightM
 
295
        drawable_y = self.pagesize[1] - topM - bottomM
 
296
        fittable_x = int(drawable_x / (card_size[0]+MINIMUM_SPACING))
 
297
        fittable_y = int(drawable_y / (card_size[1]+MINIMUM_SPACING))
 
298
        # Raise a ValueError if we can't actually fit multiple index cards on this page.
 
299
        if (not fittable_x) or (not fittable_y):
 
300
            raise ValueError("Card size %s does not fit on page %s with margins %s"%(
 
301
                card_size,self.pagesize,self.margins
 
302
                )
 
303
                             )
 
304
        x_spacer = (
 
305
            # Extra space = 
 
306
            fittable_x * # Number of cards times
 
307
            ((drawable_x/fittable_x) # space per card
 
308
             - card_size[0] ) # - space occupied by card
 
309
            / # Divide extra space by n+1, so we get [   CARD    ], [  CARD  CARD  ], etc.
 
310
            (fittable_x+1)
 
311
            )
 
312
        y_spacer = (
 
313
            fittable_y *
 
314
            ((drawable_y/fittable_y)
 
315
              - (card_size[1]))
 
316
             /
 
317
            (fittable_y+1)
 
318
            )
 
319
        frames = []
 
320
        for x in range(fittable_x):
 
321
            x_start = leftM + (x_spacer*(x+1)) + (card_size[0]*x)
 
322
            for y in range(fittable_y-1,-1,-1):
 
323
                # Count down for the y, since we start from the bottom
 
324
                # and move up
 
325
                y_start = bottomM + (y_spacer*(y+1)) +  (card_size[1]*y)
 
326
                frames.append(
 
327
                    platypus.Frame(x_start,y_start,
 
328
                                   width=card_size[0],
 
329
                                   height=card_size[1],
 
330
                                   showBoundary=1)
 
331
                    )
 
332
        return frames
 
333
    
 
334
    def make_paragraph (self, txt, style=None, attributes="",keep_with_next=False):
 
335
        if attributes:
 
336
            txt = '<para %s>%s</para>'%(attributes,txt)
 
337
        else:
 
338
            txt = '<para>%s</para>'%txt
 
339
        if not style: style = self.styleSheet['Normal']
 
340
        try:
 
341
            if PASS_REPORTLAB_UNICODE:
 
342
                return platypus.Paragraph(unicode(txt),style)
 
343
            else:
 
344
                return platypus.Paragraph(unicode(txt).encode('iso-8859-1','replace'),style)
 
345
        except UnicodeDecodeError:
 
346
            try:
 
347
                #print 'WORK AROUND UNICODE ERROR WITH ',txt[:20]
 
348
                # This seems to be the standard on windows.
 
349
                platypus.Paragraph(txt,style)
 
350
            except:
 
351
                print 'Trouble with ',txt
 
352
                raise
 
353
 
 
354
    def write_paragraph (self, txt, style=None, keep_with_next=False, attributes=""):
 
355
        p = self.make_paragraph(txt,style,attributes,keep_with_next=keep_with_next)
 
356
        if keep_with_next:
 
357
            # Keep with next isn't working, so we use a conditional
 
358
            # page break, on the assumption that no header should have
 
359
            # less than 3/4 inch of stuff after it on the page.
 
360
            self.txt.append(platypus.CondPageBreak(0.75*inch))
 
361
        self.txt.append(p)
 
362
 
 
363
 
 
364
    def write_header (self, txt):
 
365
        """Write a header.
 
366
 
 
367
        WARNING: If this is not followed by a call to our write_paragraph(...keep_with_next=False),
 
368
        the header won't necessarily be written.
 
369
        """
 
370
        self.write_paragraph(
 
371
            txt,
 
372
            style=self.styleSheet['Heading1'],
 
373
            keep_with_next = True
 
374
            )
 
375
 
 
376
    def write_subheader (self, txt):
 
377
        """Write a subheader.
 
378
 
 
379
        WARNING: If this is not followed by a call to our write_paragraph(...keep_with_next=False),
 
380
        the header won't necessarily be written.
 
381
        """        
 
382
        self.write_paragraph(
 
383
            txt,
 
384
            style=self.styleSheet['Heading2'],
 
385
            keep_with_next=True
 
386
            )
 
387
 
 
388
    def close (self):
 
389
        t = self.txt[:]
 
390
        try: self.doc.build(self.txt)
 
391
        except:
 
392
            print 'Trouble building',t[:20]
 
393
            raise
 
394
        
 
395
class PdfExporter (exporter.exporter_mult, PdfWriter):
 
396
 
 
397
    def __init__ (self, rd, r, out,
 
398
                  doc=None,
 
399
                  styleSheet=None,
 
400
                  txt=[],
 
401
                  pdf_args=DEFAULT_PDF_ARGS,
 
402
                  **kwargs):
 
403
        self.links = [] # Keep track of what recipes we link to to
 
404
                        # make sure we use them...
 
405
        PdfWriter.__init__(self)
 
406
        if type(out) in types.StringTypes:
 
407
            out = file(out,'wb')
 
408
        if not doc:
 
409
            self.setup_document(out,**pdf_args)
 
410
            self.multidoc = False
 
411
        else:
 
412
            self.doc = doc; self.styleSheet = styleSheet; self.txt = []
 
413
            self.master_txt = txt
 
414
            self.multidoc = True
 
415
            # Put nice lines to separate multiple recipes out...
 
416
            #if pdf_args.get('mode',('columns',1))[0]=='columns':
 
417
            #    self.txt.append(MCLine(self.doc.frame_width*0.8))
 
418
        exporter.exporter_mult.__init__(
 
419
            self,
 
420
            rd,r,
 
421
            None, # exporter_mult has no business touching a file
 
422
            use_ml=True,
 
423
            order=['image','attr','ings','text'],
 
424
            do_markup=True,
 
425
            fractions=convert.FRACTIONS_NORMAL,
 
426
            **kwargs
 
427
            )
 
428
        if not self.multidoc:
 
429
            self.close() # Finish the document if this is all-in-one
 
430
            out.close()
 
431
        else:
 
432
            #self.txt.append(platypus.PageBreak()) # Otherwise, a new page
 
433
            # Append to the txt list we were handed ourselves in a KeepTogether block
 
434
            #self.txt.append(platypus.Spacer(0,inch*0.5))
 
435
            #if pdf_args.get('mode',('column',1))[0]=='column':
 
436
            #    self.master_txt.append(platypus.KeepTogether(self.txt))
 
437
            #else:
 
438
            if self.master_txt:
 
439
                self.master_txt.append(platypus.FrameBreak())            
 
440
            self.master_txt.extend(self.txt)
 
441
            #self.master_txt.extend(self.txt)
 
442
 
 
443
    def handle_italic (self, chunk):
 
444
        return '<i>' + chunk + '</i>'
 
445
    
 
446
    def handle_bold (self, chunk):
 
447
        return '<b>' + chunk + '</b>'
 
448
    
 
449
    def handle_underline (self, chunk):
 
450
        return '<u>' + chunk + '</u>'
 
451
 
 
452
    def scale_image (self, image, proportion=None):
 
453
        # Platypus assumes image size is in points -- this appears to
 
454
        # be off by the amount below.
 
455
        if not proportion: proportion = inch/100 # we want 100 dots per image
 
456
        image.drawHeight = image.drawHeight*proportion        
 
457
        image.drawWidth = image.drawWidth*proportion
 
458
 
 
459
    def write_image (self, data):
 
460
        fn = ImageExtras.write_image_tempfile(data)
 
461
        i = platypus.Image(fn)
 
462
        self.scale_image(i)
 
463
        factor = 1
 
464
        MAX_WIDTH = self.doc.frame_width * 0.35
 
465
        MAX_HEIGHT = self.doc.frame_height * 0.5
 
466
        if i.drawWidth > MAX_WIDTH:
 
467
            factor = MAX_WIDTH/i.drawWidth
 
468
        if i.drawHeight > MAX_HEIGHT:
 
469
            f = MAX_HEIGHT/i.drawHeight
 
470
            if f < factor: factor = f
 
471
        if factor < 1.0:
 
472
            self.scale_image(i,factor)
 
473
        self.image = i
 
474
 
 
475
    def write_attr_head (self):
 
476
        # just move .txt aside through the attrs -- this way we can
 
477
        # use our regular methods to keep adding attribute elements 
 
478
        self.attributes = []
 
479
        
 
480
    def write_attr_foot (self):
 
481
        # If we have 3 or fewer attributes and no images, we don't
 
482
        # need a table
 
483
        if len(self.attributes)<=3 and not hasattr(self,'image'):
 
484
            self.txt.extend(self.attributes)
 
485
            return
 
486
        if not self.attributes and hasattr(self,'image'):
 
487
            # If we only have an image...
 
488
            self.txt.append(self.image)
 
489
            return
 
490
        elif hasattr(self,'image') and self.image.drawWidth > (self.doc.frame_width / 2.25):
 
491
            self.txt.append(self.image)
 
492
            self.txt.extend(self.attributes)
 
493
            return
 
494
        # Otherwise, we're going to make a table...
 
495
        if hasattr(self,'image'):
 
496
            # If we have an image, we put attributes on the
 
497
            # left, image on the right
 
498
            table_data = [
 
499
                [# 1 row
 
500
                # column 1 = attributes
 
501
                self.attributes,
 
502
                # column 2 = image
 
503
                self.image
 
504
                ],
 
505
                # End of "table"
 
506
                ]
 
507
        else:
 
508
            nattributes = len(self.attributes)
 
509
            first_col_size = nattributes/2 + nattributes % 2
 
510
            first = self.attributes[:first_col_size]
 
511
            second = self.attributes[first_col_size:]
 
512
            table_data = []
 
513
            for n,left in enumerate(first):
 
514
                right = len(second)>n and second[n] or ''
 
515
                table_data.append([left,right])
 
516
        t = platypus.Table(table_data)
 
517
        t.setStyle(
 
518
            platypus.TableStyle([
 
519
            ('VALIGN',(0,0),(0,-1),'TOP'),
 
520
            ('LEFTPADDING',(0,0),(0,-1),0),
 
521
            # for debugging
 
522
            #('INNERGRID',(0,0),(-1,-1),.25,colors.red),
 
523
            #('BOX',(0,0),(-1,-1),.25,colors.red),            
 
524
            ]
 
525
                                )
 
526
            )
 
527
        self.txt.append(t)
 
528
        #self.txt = [platypus.KeepTogether(self.txt)]
 
529
 
 
530
    def make_rating (self, label, val):
 
531
        """Make a pretty representation of our rating.
 
532
        """
 
533
        try:
 
534
            assert(type(val)==int)
 
535
        except:
 
536
            raise TypeError("Rating %s is not an integer"%val)
 
537
        i = FiveStars(10, filled=(val/2.0)) # 12 point
 
538
        lwidth = len(label+': ')*4 # A very cheap approximation of width
 
539
        t = platypus.Table(
 
540
            [[label+': ',i]],
 
541
            colWidths=[lwidth,inch],
 
542
            )
 
543
        t.hAlign = 'LEFT'
 
544
        t.setStyle(
 
545
            platypus.TableStyle([
 
546
            ('LEFTPADDING',(0,0),(-1,0),0),
 
547
            ('LEFTPADDING',(1,0),(1,0),6),
 
548
            ('TOPPADDING',(0,0),(-1,-1),0),
 
549
            ('ALIGNMENT',(1,0),(1,0),'LEFT'),
 
550
            ('VALIGN',(0,0),(0,0),'TOP'),
 
551
            # for debugging
 
552
            #('INNERGRID',(0,0),(-1,-1),.25,colors.black),
 
553
            #('BOX',(0,0),(-1,-1),.25,colors.black),            
 
554
             ]
 
555
                                )
 
556
            )
 
557
        return t
 
558
    
 
559
    def write_attr (self, label, text):
 
560
        attr = gglobals.NAME_TO_ATTR[label]
 
561
        if attr=='title':
 
562
            self.txt.append(Bookmark(self.r.title,'r'+str(self.r.id)))
 
563
            self.write_paragraph(text,style=self.styleSheet['Heading1'])
 
564
        if attr=='rating':
 
565
            from gourmet.importers.importer import string_to_rating
 
566
            val = string_to_rating(text)
 
567
            if val:
 
568
                self.attributes.append(self.make_rating(label,val))
 
569
                return
 
570
        if attr=='link':
 
571
            trimmed = text.strip()
 
572
            if len(trimmed)>32:
 
573
                trimmed=trimmed[:29]+'&#8230;'
 
574
            self.attributes.append(self.make_paragraph('%s: <link href="%s">%s</link>'%(label,text,trimmed)))
 
575
            return
 
576
        # If nothing else has returned...
 
577
        self.attributes.append(self.make_paragraph("%s: %s"%(label,text)))
 
578
 
 
579
    def write_text (self, label, text):
 
580
        self.write_subheader(label)
 
581
        first_para = True
 
582
        for t in text.split('\n'):
 
583
            # HARDCODING paragraph style to space
 
584
            if first_para:
 
585
                first_para = False
 
586
            self.write_paragraph(t,attributes="spacebefore='6'")
 
587
 
 
588
    def write_inghead (self):
 
589
        self.save_txt = self.txt[:]
 
590
        self.txt = []
 
591
        self.write_subheader(xml.sax.saxutils.escape(_('Ingredients')))
 
592
 
 
593
    def write_grouphead (self, name):
 
594
        self.write_paragraph(name,self.styleSheet['Heading3'])
 
595
 
 
596
    def write_ingfoot (self):
 
597
        # Ugly -- we know that heads comprise two elements -- a
 
598
        # condbreak and a head...
 
599
        ings = self.txt[2:]
 
600
        if len(ings) > 4:
 
601
            half = (len(ings) / 2)
 
602
            first_half = ings[:-half]
 
603
            second_half = ings[-half:]
 
604
            t = platypus.Table(
 
605
                [[first_half,second_half]]
 
606
                )
 
607
            t.hAlign = 'LEFT'
 
608
            t.setStyle(
 
609
                platypus.TableStyle([
 
610
                ('VALIGN',(0,0),(1,0),'TOP'),
 
611
                ]
 
612
                                    )
 
613
                )
 
614
            self.txt = self.txt[:2] + [t]
 
615
        self.txt = self.save_txt + [platypus.KeepTogether(self.txt)]
 
616
 
 
617
    def write_ing (self, amount=1, unit=None, item=None, key=None, optional=False):
 
618
        txt = ""
 
619
        for blob in [amount,unit,item,(optional and _('optional') or '')]:
 
620
            if not blob: continue
 
621
            if txt: txt += " %s"%blob
 
622
            else: txt = blob
 
623
        hanging = inch*0.25
 
624
        self.write_paragraph(
 
625
            txt,
 
626
            attributes=' firstLineIndent="-%(hanging)s" leftIndent="%(hanging)s"'%locals()
 
627
            )
 
628
 
 
629
    def write_ingref (self, amount, unit, item, refid, optional):
 
630
        reffed = self.rd.get_rec(refid)
 
631
        if not reffed:
 
632
            reffed = self.rd.fetch_one(self.rd.recipe_table,title=item,deleted=False)
 
633
            if reffed:
 
634
                refid = reffed.id
 
635
        if not reffed:
 
636
            return self.write_ing(amount,unit,item,optional=optional)
 
637
        txt = ""
 
638
        for blob in [amount,unit,item,(optional and _('optional') or '')]:
 
639
            if blob == item:
 
640
              blob = '<link href="r%s">'%refid + blob + '</link>'  
 
641
            if not blob: continue
 
642
            if txt: txt += " %s"%blob
 
643
            else: txt = blob
 
644
        hanging = inch*0.25
 
645
        self.links.append(refid)
 
646
        self.write_paragraph(
 
647
            txt,
 
648
            attributes=' firstLineIndent="-%(hanging)s" leftIndent="%(hanging)s"'%locals()
 
649
            )
 
650
 
 
651
class PdfExporterMultiDoc (exporter.ExporterMultirec, PdfWriter):
 
652
    def __init__ (self, rd, recipes, out, progress_func=None, conv=None,
 
653
                  pdf_args=DEFAULT_PDF_ARGS,
 
654
                  **kwargs):
 
655
        self.links = []
 
656
        PdfWriter.__init__(self)
 
657
        if type(out) in types.StringTypes:
 
658
            out = file(out,'wb')
 
659
        self.setup_document(out,**pdf_args)
 
660
        self.output_file = out
 
661
        kwargs['doc'] = self.doc
 
662
        kwargs['styleSheet'] = self.styleSheet
 
663
        kwargs['txt'] = self.txt
 
664
        kwargs['pdf_args'] = pdf_args
 
665
        exporter.ExporterMultirec.__init__(
 
666
            self,
 
667
            rd, recipes, out,
 
668
            one_file=True, ext='pdf',
 
669
            exporter=PdfExporter,
 
670
            conv=conv,
 
671
            progress_func=progress_func,
 
672
            exporter_kwargs=kwargs,
 
673
            )
 
674
 
 
675
    def write_footer (self):
 
676
        self.close()
 
677
        self.output_file.close()
 
678
 
 
679
class Sizer (PdfWriter):
 
680
 
 
681
    def get_size (self, *args, **kwargs):
 
682
        frames = self.setup_frames(*args,**kwargs)
 
683
        return self.pagesize,frames
 
684
 
 
685
    def get_pagesize_and_frames_for_widget (self, *args, **kwargs):
 
686
        ps,ff = self.get_size(*args,**kwargs)
 
687
        frames = [
 
688
            (f.x1, # X (top corner)
 
689
             ps[1]-f._y2, #Y (top corner)
 
690
             f.width,f.height) for f in ff]
 
691
        return ps,frames
 
692
 
 
693
class PdfPageDrawer (PageDrawer):
 
694
 
 
695
    def __init__ (self,*args,**kwargs):
 
696
        PageDrawer.__init__(self,*args,**kwargs)
 
697
        self.sizer = Sizer()
 
698
        self.set_page()
 
699
 
 
700
    def set_page (self, *args, **kwargs):
 
701
        self.last_kwargs = kwargs
 
702
        size,areas = self.sizer.get_pagesize_and_frames_for_widget(*args,**kwargs)
 
703
        self.set_page_area(size[0],size[1],areas)
 
704
 
 
705
PDF_PREF_DEFAULT={
 
706
    'page_size':_('Letter'),
 
707
    'orientation':_('Portrait'),
 
708
    'font_size':10.0,
 
709
    'page_layout':_('Plain'),
 
710
    'left_margin':1.0,
 
711
    'right_margin':1.0,
 
712
    'top_margin':1.0,
 
713
    'bottom_margin':1.0,    
 
714
    }
 
715
 
 
716
class PdfPrefGetter:
 
717
    page_sizes = {
 
718
        _('11x17"'):'elevenSeventeen',
 
719
        _('Index Card (3.5x5")'):(3.5*inch,5*inch),
 
720
        _('Index Card (4x6")'):(4*inch,6*inch),
 
721
        _('Index Card (5x8")'):(5*inch,8*inch),
 
722
        _('Index Card (A7)'):(74*mm,105*mm),
 
723
        _('Letter'):'letter',
 
724
        _('Legal'):'legal',
 
725
        'A0':'A0','A1':'A1','A2':'A2','A3':'A3','A4':'A4','A5':'A5','A6':'A6',
 
726
        'B0':'B0','B1':'B1','B2':'B2','B3':'B3','B4':'B4','B5':'B5','B6':'B6',
 
727
        }
 
728
 
 
729
    INDEX_CARDS = [(3.5*inch,5*inch),(4*inch,6*inch),(5*inch,8*inch),(74*mm,105*mm)]
 
730
    INDEX_CARD_LAYOUTS = [_('Index Cards (3.5x5)'),
 
731
                          _('Index Cards (4x6)'),
 
732
                          _('Index Cards (A7)'),
 
733
                          ]
 
734
    layouts = {
 
735
        _('Plain'):('column',1),
 
736
        _('Index Cards (3.5x5)'):('index_cards',(5*inch,3.5*inch)),
 
737
        _('Index Cards (4x6)'):('index_cards',(6*inch,4*inch)),
 
738
        _('Index Cards (A7)'):('index_cards',(105*mm,74*mm)),
 
739
        }
 
740
    
 
741
    page_modes = {
 
742
        _('Portrait'):'portrait',
 
743
        _('Landscape'):'landscape',      
 
744
        }
 
745
 
 
746
    OPT_PS,OPT_PO,OPT_FS,OPT_PL,OPT_LM,OPT_RM,OPT_TM,OPT_BM = range(8)
 
747
    
 
748
    def __init__ (self, defaults=PDF_PREF_DEFAULT):
 
749
        self.size_strings = self.page_sizes.keys()
 
750
        self.size_strings.sort()
 
751
        for n in range(2,5):
 
752
            self.layouts[ngettext('%s Column','%s Columns',n)%n]=('column',n)
 
753
        self.layout_strings = self.layouts.keys()
 
754
        self.layout_strings.sort()
 
755
        self.opts = [
 
756
            [_('Paper _Size')+':',(defaults.get('page_size',PDF_PREF_DEFAULT['page_size']),
 
757
                                  self.size_strings)],
 
758
            [_('_Orientation')+':',(defaults.get('orientation',PDF_PREF_DEFAULT['orientation']),
 
759
                                    self.page_modes.keys())],
 
760
            [_('_Font Size')+':',defaults.get('font_size',PDF_PREF_DEFAULT['font_size'])]
 
761
            ,
 
762
            [_('Page _Layout'),(defaults.get('page_layout',PDF_PREF_DEFAULT['page_layout']),
 
763
 
 
764
                                self.layout_strings)],
 
765
            [_('Left Margin')+':',defaults.get('left_margin',PDF_PREF_DEFAULT['left_margin'])]
 
766
            ,
 
767
            [_('Right Margin')+':',defaults.get('right_margin',PDF_PREF_DEFAULT['right_margin'])]
 
768
            ,
 
769
            [_('Top Margin')+':',defaults.get('top_margin',PDF_PREF_DEFAULT['top_margin'])]
 
770
            ,
 
771
            [_('Bottom Margin')+':',defaults.get('bottom_margin',PDF_PREF_DEFAULT['bottom_margin'])]
 
772
            ,
 
773
            ]
 
774
        
 
775
        self.page_drawer = PdfPageDrawer(yalign=0.0)    
 
776
        self.in_ccb = False
 
777
        self.pd = de.PreferencesDialog(self.opts,option_label=None,value_label=None,
 
778
                                  label='PDF Options',
 
779
                                  )
 
780
        self.pd.table.connect('changed',self.change_cb)
 
781
        self.pd.table.emit('changed')
 
782
        self.pd.hbox.pack_start(self.page_drawer,fill=True,expand=True)        
 
783
        self.page_drawer.set_size_request(200,100)
 
784
        self.page_drawer.show()
 
785
 
 
786
    def run (self):
 
787
        self.pd.run()
 
788
        return self.get_args_from_opts(self.opts)
 
789
 
 
790
    def get_args_from_opts (self, opts):
 
791
        args = {}
 
792
        args['pagesize']=self.page_sizes[opts[self.OPT_PS][1]] # PAGE SIZE
 
793
        args['pagemode']=self.page_modes[opts[self.OPT_PO][1]] # PAGE MODE
 
794
        args['base_font_size']=opts[self.OPT_FS][1] # FONT SIZE
 
795
        args['mode']=self.layouts[opts[self.OPT_PL][1]] # LAYOUT/MODE
 
796
        args['left_margin']=opts[self.OPT_LM][1]*inch
 
797
        args['right_margin']=opts[self.OPT_RM][1]*inch
 
798
        args['top_margin']=opts[self.OPT_TM][1]*inch
 
799
        args['bottom_margin']=opts[self.OPT_BM][1]*inch
 
800
        return args
 
801
 
 
802
    def change_cb (self, option_table, *args,**kwargs):
 
803
        if self.in_ccb: return
 
804
        self.in_ccb = True
 
805
        option_table.apply()
 
806
        args = self.get_args_from_opts(self.opts)
 
807
        changed = False
 
808
        if args['pagesize']!=self.page_drawer.last_kwargs.get('pagesize','letter'):
 
809
            last_pagesize = self.page_drawer.last_kwargs.get('pagesize','letter')
 
810
            pagesize = args['pagesize']
 
811
            # If pagesize has changed from index to non-index card,
 
812
            # toggle orientation and margins by default for our user's
 
813
            # convenience...
 
814
            if pagesize in self.INDEX_CARDS and last_pagesize not in self.INDEX_CARDS:
 
815
                changed = True
 
816
                option_table.set_option(self.OPT_PO,_('Landscape'))
 
817
                for o in [self.OPT_LM,self.OPT_RM,self.OPT_BM,self.OPT_TM]:
 
818
                    option_table.set_option(o,0.25)
 
819
                option_table.set_option(self.OPT_FS,8)
 
820
                # Also -- make sure we don't allow index card layout in this...
 
821
                cb = option_table.widgets[self.OPT_PL][0]
 
822
                if not hasattr(self,'index_card_layouts_to_put_back'):
 
823
                    self.index_card_layouts_to_put_back = []
 
824
                    for i in self.INDEX_CARD_LAYOUTS:
 
825
                        pos=self.layout_strings.index(i)
 
826
                        self.index_card_layouts_to_put_back.append((pos,i))
 
827
                    self.index_card_layouts_to_put_back.sort()
 
828
                n = cb.get_active()
 
829
                if n in [i[0] for i in self.index_card_layouts_to_put_back]:
 
830
                    default_pos = self.layout_strings.index(_('Plain'))
 
831
                    cb.set_active(default_pos)
 
832
                self.index_card_layouts_to_put_back.reverse()
 
833
                for pos,txt in self.index_card_layouts_to_put_back:
 
834
                    cb.remove_text(pos)
 
835
                self.index_card_layouts_to_put_back.reverse()
 
836
            elif pagesize not in self.INDEX_CARDS and last_pagesize in self.INDEX_CARDS:
 
837
                changed = True                
 
838
                option_table.set_option(self.OPT_PO,_('Portrait'))
 
839
                for o in [self.OPT_LM,self.OPT_RM,self.OPT_BM,self.OPT_TM]:
 
840
                    option_table.set_option(o,1)
 
841
                option_table.set_option(self.OPT_FS,10)
 
842
                # Also -- we allow index card layout in this...
 
843
                cb = option_table.widgets[self.OPT_PL][0]
 
844
                if hasattr(self,'index_card_layouts_to_put_back'):
 
845
                    for pos,txt in self.index_card_layouts_to_put_back:
 
846
                        cb.insert_text(pos,txt)
 
847
    
 
848
        if (args['mode'][0] != self.page_drawer.last_kwargs.get('mode',('column',1))[0]
 
849
            or
 
850
            (args['mode'][0]=='index_cards'
 
851
             and (args['mode'] != self.page_drawer.last_kwargs['mode']
 
852
                  or
 
853
                  (args['pagesize'] != self.page_drawer.last_kwargs['pagesize']
 
854
                   and
 
855
                   'elevenSeventeen' in [args['pagesize'],self.page_drawer.last_kwargs['pagesize']]
 
856
                   )
 
857
                  )
 
858
             )
 
859
            ):
 
860
            # If our mode has changed...
 
861
            changed = True
 
862
            if args['mode'][0]=='index_cards':
 
863
                option_table.set_option(self.OPT_FS,8)
 
864
                for o in [self.OPT_LM,self.OPT_RM,self.OPT_BM,self.OPT_TM]:
 
865
                    option_table.set_option(o,0.35)
 
866
                if (args['mode'][1][0] <= 5.2*inch) ^ (args['pagesize']=='elevenSeventeen'):
 
867
                    option_table.set_option(self.OPT_PO,_('Landscape'))
 
868
                else:
 
869
                    option_table.set_option(self.OPT_PO,_('Portrait'))
 
870
            else:
 
871
                # Otherwise it's columns...
 
872
                option_table.set_option(self.OPT_FS,10) 
 
873
                for o in [self.OPT_LM,self.OPT_RM,self.OPT_BM,self.OPT_TM]:
 
874
                    option_table.set_option(o,1)
 
875
        if changed:
 
876
            option_table.apply()
 
877
            args = self.get_args_from_opts(self.opts)
 
878
        #backup_args = page_drawer.last_kwargs
 
879
        self.page_drawer.set_page(**args)
 
880
        self.page_drawer.queue_draw()
 
881
        self.in_ccb = False
 
882
 
 
883
def get_pdf_prefs (defaults=PDF_PREF_DEFAULT):
 
884
    pdf_pref_getter = PdfPrefGetter(defaults=defaults)
 
885
    return pdf_pref_getter.run()
 
886
            
 
887
if __name__ == '__main__':
 
888
    from tempfile import tempdir
 
889
    import os.path
 
890
    #opts = get_pdf_prefs(); print opts
 
891
    sw = PdfWriter()
 
892
    f = file(os.path.join(tempdir,'foo.pdf'),'wb')
 
893
    sw.setup_document(f,
 
894
                      mode=('index_cards',(5*inch,3.5*inch)),
 
895
                      pagesize='letter',
 
896
                      pagemode='landscape',
 
897
                      left_margin=0.25*inch,right_margin=0.25*inch,
 
898
                      top_margin=0.25*inch,bottom_margin=0.25*inch,
 
899
                      base_font_size=8,
 
900
                      )
 
901
    #sw.write_header('Heading')
 
902
    #sw.write_subheader('This is a subheading')
 
903
    for n in range(5):
 
904
        sw.write_header(
 
905
            u"This is a header"
 
906
            )
 
907
        #sw.write_subheader(
 
908
        #    u"This is a subheader"
 
909
        #    )
 
910
        sw.write_paragraph(
 
911
            u"%s: These are some sentences.  Hopefully some of these will be quite long sentences.  Some of this text includes unicode -- 45\u00b0F, for example... \u00bfHow's that?"%n*10
 
912
            )
 
913
    #sw.write_paragraph('This is a <i>paragraph</i> with <b>some</b> <u>markup</u>.')
 
914
    #sw.write_paragraph(u"This is some text with unicode - 45\u00b0, \u00bfHow's that?".encode('iso-8859-1'))
 
915
    #sw.write_paragraph(u"This is some text with a unicode object - 45\u00b0, \u00bfHow's that?")
 
916
    sw.close()
 
917
    f.close()
 
918
    
 
919
    #star_file = file(os.path.join(tempdir,'star.pdf'),'wb')
 
920
    #sw = PdfWriter()
 
921
    #sw.setup_document(star_file,mode='two_column')
 
922
    #for n in range(6,72,2):
 
923
    #    sw.write_paragraph("This is some text with a %s pt star"%n)
 
924
    #    sw.txt.append(FiveStars(n,filled=3.5))
 
925
    #    
 
926
    #sw.close()
 
927
    #star_file.close()
 
928
    #import gnome
 
929
    #gnome.program_init('1.0','Gourmet PDF Exporter Test')
 
930
    #gglobals.launch_url('file:/os.path.join(tempdir,/star.pdf')
 
931
    #raise "I don')t want to go any further"
 
932
    
 
933
    if os.name == 'nt':
 
934
        base = 'C:\\grm\grm'
 
935
    else:
 
936
        base = '/home/tom/Projects/grm'
 
937
 
 
938
    #import gourmet.recipeManager as rm
 
939
    #rd = rm.RecipeManager(file=os.path.join(base,'src','tests','reference_setup','recipes.db'))
 
940
    #rd = rm.RecipeManager()
 
941
    #ofi = file(os.path.join(tempdir,'test_rec.pdf'),'w')
 
942
    #rr = []
 
943
    #for n,rec in enumerate(rd.fetch_all(rd.recipe_table,deleted=False)):
 
944
    #    if rec.image:
 
945
    #        rr.append(rec)
 
946
    #pe = PdfExporterMultiDoc(rd,rd.fetch_all(rd.recipe_table),os.path.join(tempdir,'fooby.pdf'))
 
947
    #pe = PdfExporterMultiDoc(rd,rd.fetch_all(rd.recipe_table,deleted=False)[:10],os.path.join(tempdir,'fooby.pdf'))
 
948
    #pe = PdfExporterMultiDoc(rd,rr,os.path.join(tempdir,'fooby.pdf'))
 
949
    #pe.run()
 
950
 
 
951
    def test_formatting ():
 
952
        print 'Test formatting'
 
953
        sw = PdfWriter()
 
954
        f = file(os.path.join(tempdir,'format.pdf'),'wb')
 
955
        sw.setup_document(f)
 
956
        sw.write_header('This is a header & isn\'t it nifty')
 
957
        sw.write_paragraph('<i>This</i> is a <b>paragraph</b> with <u>formatting</u>!')
 
958
        sw.write_header('<u>This is a formatted header & it is also nifty &amp; cool</u>')
 
959
        sw.write_paragraph('<i>This is another formatted paragraph</i>')
 
960
        sw.write_paragraph('<span fg="\#f00">This is color</span>')
 
961
        sw.close()
 
962
        f.close()
 
963
        return os.path.join(tempdir,'format.pdf')
 
964
 
 
965
    def test_3_x_5 ():
 
966
        print 'Test 3x5 layout'
 
967
        sw = PdfWriter()
 
968
        f = file(os.path.join(tempdir,'foo.pdf'),'wb')
 
969
        sw.setup_document(f,
 
970
                          mode=('index_cards',(5*inch,3.5*inch)),
 
971
                          #pagesize=(5*inch,3.5*inch),
 
972
                          pagesize='letter',
 
973
                          pagemode='landscape',
 
974
                          left_margin=0.25*inch,right_margin=0.25*inch,
 
975
                          top_margin=0.25*inch,bottom_margin=0.25*inch,
 
976
                          base_font_size=8,
 
977
                          )
 
978
        for n in range(5):
 
979
            sw.write_header(
 
980
                u"This is a header"
 
981
                )
 
982
            sw.write_paragraph(
 
983
                u"%s: These are some sentences.  Hopefully some of these will be quite long sentences.  Some of this text includes unicode -- 45\u00b0F, for example... \u00bfHow's that?"%n*10
 
984
                )
 
985
        sw.close()
 
986
        f.close()
 
987
        return os.path.join(tempdir,'foo.pdf')
 
988
 
 
989
    def test_grm_export (pdf_args=DEFAULT_PDF_ARGS):
 
990
        fname = tempfile.mktemp('.pdf')
 
991
        #if os.name == 'nt':
 
992
        #    base = 'C:\\grm\grm'
 
993
        #else:
 
994
        #    base = '/home/tom/Projects/grm'
 
995
        import gourmet.recipeManager as rm
 
996
        rd = rm.RecipeManager(file=os.path.join(base,'src','tests','reference_setup','recipes.db'))
 
997
        #rd = rm.RecipeManager()
 
998
        rr = []
 
999
        #for n,rec in enumerate(rd.fetch_all(rd.recipe_table,deleted=False)):
 
1000
        #    if rec.image:
 
1001
        #        rr.append(rec)
 
1002
        pe = PdfExporterMultiDoc(rd,rd.fetch_all(rd.recipe_table,deleted=False),fname,pdf_args=pdf_args)
 
1003
        pe.run()
 
1004
        return fname
 
1005
 
 
1006
    import gourmet.gglobals as gglobals
 
1007
    try:
 
1008
        import gnome
 
1009
        gnome.program_init('1.0','Gourmet PDF Exporter Test')
 
1010
    except ImportError:
 
1011
        print 'We must be on windows...'
 
1012
 
 
1013
    #print 'TEST 3x5'
 
1014
    #gglobals.launch_url('file://'+test_3_x_5())
 
1015
    #gglobals.launch_url('file://'+test_formatting())
 
1016
    #print 'END TEST'
 
1017
    #print 'TEST GRM'
 
1018
    gglobals.launch_url('file://'+test_grm_export())
 
1019
    #print 'TEST CUSTOM GRM'
 
1020
    #gglobals.launch_url('file://'+test_grm_export(get_pdf_prefs({'page_size':_('A4'),'page_layout':'2 Columns'})))
 
1021
    #ppg = PdfPrefGetter()
 
1022
    #print ppg.run()
 
1023
    #print 'END TEST'
 
1024