Modifs for logo and multiple-header
[odoo/odoo.git] / bin / report / render / rml2pdf / trml2pdf.py
1 #!/usr/bin/python
2 # -*- coding: utf-8 -*-
3
4 # trml2pdf - An RML to PDF converter
5 # Copyright (C) 2003, Fabien Pinckaers, UCL, FSA
6 # Contributors
7 #     Richard Waid <richard@iopen.net>
8 #
9 # This library is free software; you can redistribute it and/or
10 # modify it under the terms of the GNU Lesser General Public
11 # License as published by the Free Software Foundation; either
12 # version 2.1 of the License, or (at your option) any later version.
13 #
14 # This library is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17 # Lesser General Public License for more details.
18 #
19 # You should have received a copy of the GNU Lesser General Public
20 # License along with this library; if not, write to the Free Software
21 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
22
23 import sys
24 import StringIO
25 import xml.dom.minidom
26 import copy
27
28 import reportlab
29 from reportlab.pdfgen import canvas
30 from reportlab import platypus
31
32 import utils
33 import color
34 import os
35
36 #
37 # Change this to UTF-8 if you plan tu use Reportlab's UTF-8 support
38 #
39 # reportlab use "code page 1252" encoding by default. cfr reportlab user guide p.46
40 encoding = 'utf-8'
41
42 def str2xml(s):
43         return s.replace('&', '&amp;').replace('<', '&lt;').replace('>', '&gt;')
44
45 def _child_get(node, childs):
46         clds = []
47         for n in node.childNodes:
48                 if (n.nodeType==n.ELEMENT_NODE) and (n.localName==childs):
49                         clds.append(n)
50         return clds
51
52 class _rml_styles(object):
53         def __init__(self, nodes):
54                 self.styles = {}
55                 self.names = {}
56                 self.table_styles = {}
57                 for node in nodes:
58                         for style in node.getElementsByTagName('blockTableStyle'):
59                                 self.table_styles[style.getAttribute('id')] = self._table_style_get(style)
60                         for style in node.getElementsByTagName('paraStyle'):
61                                 self.styles[style.getAttribute('name')] = self._para_style_get(style)
62                         for variable in node.getElementsByTagName('initialize'):
63                                 for name in variable.getElementsByTagName('name'):
64                                         self.names[ name.getAttribute('id')] = name.getAttribute('value')
65
66         def _para_style_update(self, style, node):
67                 for attr in ['textColor', 'backColor', 'bulletColor']:
68                         if node.hasAttribute(attr):
69                                 style.__dict__[attr] = color.get(node.getAttribute(attr))
70                 for attr in ['fontName', 'bulletFontName', 'bulletText']:
71                         if node.hasAttribute(attr):
72                                 style.__dict__[attr] = node.getAttribute(attr)
73                 for attr in ['fontSize', 'leftIndent', 'rightIndent', 'spaceBefore', 'spaceAfter', 'firstLineIndent', 'bulletIndent', 'bulletFontSize', 'leading']:
74                         if node.hasAttribute(attr):
75                                 style.__dict__[attr] = utils.unit_get(node.getAttribute(attr))
76                 if node.hasAttribute('alignment'):
77                         align = {
78                                 'right':reportlab.lib.enums.TA_RIGHT,
79                                 'center':reportlab.lib.enums.TA_CENTER,
80                                 'justify':reportlab.lib.enums.TA_JUSTIFY
81                         }
82                         style.alignment = align.get(node.getAttribute('alignment').lower(), reportlab.lib.enums.TA_LEFT)
83                 return style
84
85         def _table_style_get(self, style_node):
86                 styles = []
87                 for node in style_node.childNodes:
88                         if node.nodeType==node.ELEMENT_NODE:
89                                 start = utils.tuple_int_get(node, 'start', (0,0) )
90                                 stop = utils.tuple_int_get(node, 'stop', (-1,-1) )
91                                 if node.localName=='blockValign':
92                                         styles.append(('VALIGN', start, stop, str(node.getAttribute('value'))))
93                                 elif node.localName=='blockFont':
94                                         styles.append(('FONT', start, stop, str(node.getAttribute('name'))))
95                                 elif node.localName=='blockTextColor':
96                                         styles.append(('TEXTCOLOR', start, stop, color.get(str(node.getAttribute('colorName')))))
97                                 elif node.localName=='blockLeading':
98                                         styles.append(('LEADING', start, stop, utils.unit_get(node.getAttribute('length'))))
99                                 elif node.localName=='blockAlignment':
100                                         styles.append(('ALIGNMENT', start, stop, str(node.getAttribute('value'))))
101                                 elif node.localName=='blockLeftPadding':
102                                         styles.append(('LEFTPADDING', start, stop, utils.unit_get(node.getAttribute('length'))))
103                                 elif node.localName=='blockRightPadding':
104                                         styles.append(('RIGHTPADDING', start, stop, utils.unit_get(node.getAttribute('length'))))
105                                 elif node.localName=='blockTopPadding':
106                                         styles.append(('TOPPADDING', start, stop, utils.unit_get(node.getAttribute('length'))))
107                                 elif node.localName=='blockBottomPadding':
108                                         styles.append(('BOTTOMPADDING', start, stop, utils.unit_get(node.getAttribute('length'))))
109                                 elif node.localName=='blockBackground':
110                                         styles.append(('BACKGROUND', start, stop, color.get(node.getAttribute('colorName'))))
111                                 if node.hasAttribute('size'):
112                                         styles.append(('FONTSIZE', start, stop, utils.unit_get(node.getAttribute('size'))))
113                                 elif node.localName=='lineStyle':
114                                         kind = node.getAttribute('kind')
115                                         kind_list = [ 'GRID', 'BOX', 'OUTLINE', 'INNERGRID', 'LINEBELOW', 'LINEABOVE','LINEBEFORE', 'LINEAFTER' ]
116                                         assert kind in kind_list
117                                         thick = 1
118                                         if node.hasAttribute('thickness'):
119                                                 thick = float(node.getAttribute('thickness'))
120                                         styles.append((kind, start, stop, thick, color.get(node.getAttribute('colorName'))))
121                 return platypus.tables.TableStyle(styles)
122
123         def _para_style_get(self, node):
124                 styles = reportlab.lib.styles.getSampleStyleSheet()
125                 style = copy.deepcopy(styles["Normal"])
126                 self._para_style_update(style, node)
127                 return style
128
129         def para_style_get(self, node):
130                 style = False
131                 if node.hasAttribute('style'):
132                         if node.getAttribute('style') in self.styles:
133                                 style = copy.deepcopy(self.styles[node.getAttribute('style')])
134                         else:
135                                 sys.stderr.write('Warning: style not found, %s - setting default!\n' % (node.getAttribute('style'),) )
136                 if not style:
137                         styles = reportlab.lib.styles.getSampleStyleSheet()
138                         style = copy.deepcopy(styles['Normal'])
139                 return self._para_style_update(style, node)
140
141 class _rml_doc(object):
142         def __init__(self, data, images={}, path='.'):
143                 self.dom = xml.dom.minidom.parseString(data)
144                 self.filename = self.dom.documentElement.getAttribute('filename')
145                 self.images = images
146                 self.path = path
147
148         def docinit(self, els):
149                 from reportlab.lib.fonts import addMapping
150                 from reportlab.pdfbase import pdfmetrics
151                 from reportlab.pdfbase.ttfonts import TTFont
152
153                 for node in els:
154                         for font in node.getElementsByTagName('registerFont'):
155                                 name = font.getAttribute('fontName').encode('ascii')
156                                 fname = font.getAttribute('fontFile').encode('ascii')
157                                 pdfmetrics.registerFont(TTFont(name, fname ))
158                                 addMapping(name, 0, 0, name)    #normal
159                                 addMapping(name, 0, 1, name)    #italic
160                                 addMapping(name, 1, 0, name)    #bold
161                                 addMapping(name, 1, 1, name)    #italic and bold
162
163         def _textual_image(self, node):
164                 import base64
165                 rc = ''
166                 for n in node.childNodes:
167                         if n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
168                                 rc += n.data
169                 return base64.decodestring(rc)
170
171         def _images(self, el):
172                 result = {}
173                 for node in el.getElementsByTagName('image'):
174                         result[node.getAttribute('name')] = self._textual_image(node)
175                 return result
176
177         def render(self, out):
178                 el = self.dom.documentElement.getElementsByTagName('docinit')
179                 if el:
180                         self.docinit(el)
181
182                 el = self.dom.documentElement.getElementsByTagName('stylesheet')
183                 self.styles = _rml_styles(el)
184
185                 el = self.dom.documentElement.getElementsByTagName('images')
186                 if el:
187                         self.images.update( self._images(el[0]) )
188
189                 el = self.dom.documentElement.getElementsByTagName('template')
190                 if len(el):
191                         pt_obj = _rml_template(out, el[0], self, images=self.images, path=self.path)
192                         pt_obj.render(self.dom.documentElement.getElementsByTagName('story'))
193                 else:
194                         self.canvas = canvas.Canvas(out)
195                         pd = self.dom.documentElement.getElementsByTagName('pageDrawing')[0]
196                         pd_obj = _rml_canvas(self.canvas, None, self, self.images, path=self.path)
197                         pd_obj.render(pd)
198                         self.canvas.showPage()
199                         self.canvas.save()
200
201 class _rml_canvas(object):
202         def __init__(self, canvas, doc_tmpl=None, doc=None, images={}, path='.'):
203                 self.canvas = canvas
204                 self.styles = doc.styles
205                 self.doc_tmpl = doc_tmpl
206                 self.doc = doc
207                 self.images = images
208                 self.path = path
209
210         def _textual(self, node):
211                 rc = ''
212                 for n in node.childNodes:
213                         if n.nodeType == n.ELEMENT_NODE:
214                                 if n.localName == 'pageNumber':
215                                         rc += str(self.canvas.getPageNumber())
216                         elif n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
217                                 # this doesn't need to be "entities" encoded like flowables need to
218                                 rc += n.data
219                 return rc.encode(encoding, 'replace')
220
221         def _drawString(self, node):
222                 self.canvas.drawString(text=self._textual(node), **utils.attr_get(node, ['x','y']))
223         def _drawCenteredString(self, node):
224                 self.canvas.drawCentredString(text=self._textual(node), **utils.attr_get(node, ['x','y']))
225         def _drawRightString(self, node):
226                 self.canvas.drawRightString(text=self._textual(node), **utils.attr_get(node, ['x','y']))
227         def _rect(self, node):
228                 if node.hasAttribute('round'):
229                         self.canvas.roundRect(radius=utils.unit_get(node.getAttribute('round')), **utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
230                 else:
231                         self.canvas.rect(**utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
232
233         def _ellipse(self, node):
234                 x1 = utils.unit_get(node.getAttribute('x'))
235                 x2 = utils.unit_get(node.getAttribute('width'))
236                 y1 = utils.unit_get(node.getAttribute('y'))
237                 y2 = utils.unit_get(node.getAttribute('height'))
238                 self.canvas.ellipse(x1,y1,x2,y2, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
239         def _curves(self, node):
240                 line_str = utils.text_get(node).split()
241                 lines = []
242                 while len(line_str)>7:
243                         self.canvas.bezier(*[utils.unit_get(l) for l in line_str[0:8]])
244                         line_str = line_str[8:]
245         def _lines(self, node):
246                 line_str = utils.text_get(node).split()
247                 lines = []
248                 while len(line_str)>3:
249                         lines.append([utils.unit_get(l) for l in line_str[0:4]])
250                         line_str = line_str[4:]
251                 self.canvas.lines(lines)
252         def _grid(self, node):
253                 xlist = [utils.unit_get(s) for s in node.getAttribute('xs').split(',')]
254                 ylist = [utils.unit_get(s) for s in node.getAttribute('ys').split(',')]
255                 self.canvas.grid(xlist, ylist)
256         def _translate(self, node):
257                 dx = 0
258                 dy = 0
259                 if node.hasAttribute('dx'):
260                         dx = utils.unit_get(node.getAttribute('dx'))
261                 if node.hasAttribute('dy'):
262                         dy = utils.unit_get(node.getAttribute('dy'))
263                 self.canvas.translate(dx,dy)
264
265         def _circle(self, node):
266                 self.canvas.circle(x_cen=utils.unit_get(node.getAttribute('x')), y_cen=utils.unit_get(node.getAttribute('y')), r=utils.unit_get(node.getAttribute('radius')), **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
267
268         def _place(self, node):
269                 flows = _rml_flowable(self.doc, images=self.images, path=self.path).render(node)
270                 infos = utils.attr_get(node, ['x','y','width','height'])
271
272                 infos['y']+=infos['height']
273                 for flow in flows:
274                         w,h = flow.wrap(infos['width'], infos['height'])
275                         if w<=infos['width'] and h<=infos['height']:
276                                 infos['y']-=h
277                                 flow.drawOn(self.canvas,infos['x'],infos['y'])
278                                 infos['height']-=h
279                         else:
280                                 raise ValueError, "Not enough space"
281
282         def _line_mode(self, node):
283                 ljoin = {'round':1, 'mitered':0, 'bevelled':2}
284                 lcap = {'default':0, 'round':1, 'square':2}
285                 if node.hasAttribute('width'):
286                         self.canvas.setLineWidth(utils.unit_get(node.getAttribute('width')))
287                 if node.hasAttribute('join'):
288                         self.canvas.setLineJoin(ljoin[node.getAttribute('join')])
289                 if node.hasAttribute('cap'):
290                         self.canvas.setLineCap(lcap[node.getAttribute('cap')])
291                 if node.hasAttribute('miterLimit'):
292                         self.canvas.setDash(utils.unit_get(node.getAttribute('miterLimit')))
293                 if node.hasAttribute('dash'):
294                         dashes = node.getAttribute('dash').split(',')
295                         for x in range(len(dashes)):
296                                 dashes[x]=utils.unit_get(dashes[x])
297                         self.canvas.setDash(node.getAttribute('dash').split(','))
298
299         def _image(self, node):
300                 import urllib
301                 from reportlab.lib.utils import ImageReader
302
303                 if not node.hasAttribute('file'):
304                         if node.getAttribute('name') in self.images:
305                                 scontent = self.images[node.getAttribute('name')]
306                                 s = StringIO.StringIO(scontent)
307                         else:
308                                 return True
309                 else:
310                         if node.getAttribute('file') in self.images:
311                                 s = StringIO.StringIO(self.images[node.getAttribute('file')])
312                         else:
313                                 try:
314                                         u = urllib.urlopen(str(node.getAttribute('file')))
315                                         s = StringIO.StringIO(u.read())
316                                 except:
317                                         u = file(os.path.join(self.path,str(node.getAttribute('file'))), 'rb')
318                                         s = StringIO.StringIO(u.read())
319                 img = ImageReader(s)
320                 (sx,sy) = img.getSize()
321
322                 args = {}
323                 for tag in ('width','height','x','y'):
324                         if node.hasAttribute(tag):
325                                 args[tag] = utils.unit_get(node.getAttribute(tag))
326                 if ('width' in args) and (not 'height' in args):
327                         args['height'] = sy * args['width'] / sx
328                 elif ('height' in args) and (not 'width' in args):
329                         args['width'] = sx * args['height'] / sy
330                 elif ('width' in args) and ('height' in args):
331                         if (float(args['width'])/args['height'])>(float(sx)>sy):
332                                 args['width'] = sx * args['height'] / sy
333                         else:
334                                 args['height'] = sy * args['width'] / sx
335                 self.canvas.drawImage(img, **args)
336
337         def _path(self, node):
338                 self.path = self.canvas.beginPath()
339                 self.path.moveTo(**utils.attr_get(node, ['x','y']))
340                 for n in node.childNodes:
341                         if n.nodeType == node.ELEMENT_NODE:
342                                 if n.localName=='moveto':
343                                         vals = utils.text_get(n).split()
344                                         self.path.moveTo(utils.unit_get(vals[0]), utils.unit_get(vals[1]))
345                                 elif n.localName=='curvesto':
346                                         vals = utils.text_get(n).split()
347                                         while len(vals)>5:
348                                                 pos=[]
349                                                 while len(pos)<6:
350                                                         pos.append(utils.unit_get(vals.pop(0)))
351                                                 self.path.curveTo(*pos)
352                         elif (n.nodeType == node.TEXT_NODE):
353                                 data = n.data.split()               # Not sure if I must merge all TEXT_NODE ?
354                                 while len(data)>1:
355                                         x = utils.unit_get(data.pop(0))
356                                         y = utils.unit_get(data.pop(0))
357                                         self.path.lineTo(x,y)
358                 if (not node.hasAttribute('close')) or utils.bool_get(node.getAttribute('close')):
359                         self.path.close()
360                 self.canvas.drawPath(self.path, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
361
362         def render(self, node):
363                 tags = {
364                         'drawCentredString': self._drawCenteredString,
365                         'drawRightString': self._drawRightString,
366                         'drawString': self._drawString,
367                         'rect': self._rect,
368                         'ellipse': self._ellipse,
369                         'lines': self._lines,
370                         'grid': self._grid,
371                         'curves': self._curves,
372                         'fill': lambda node: self.canvas.setFillColor(color.get(node.getAttribute('color'))),
373                         'stroke': lambda node: self.canvas.setStrokeColor(color.get(node.getAttribute('color'))),
374                         'setFont': lambda node: self.canvas.setFont(node.getAttribute('name'), utils.unit_get(node.getAttribute('size'))),
375                         'place': self._place,
376                         'circle': self._circle,
377                         'lineMode': self._line_mode,
378                         'path': self._path,
379                         'rotate': lambda node: self.canvas.rotate(float(node.getAttribute('degrees'))),
380                         'translate': self._translate,
381                         'image': self._image
382                 }
383                 for nd in node.childNodes:
384                         if nd.nodeType==nd.ELEMENT_NODE:
385                                 for tag in tags:
386                                         if nd.localName==tag:
387                                                 tags[tag](nd)
388                                                 break
389
390 class _rml_draw(object):
391         def __init__(self, node, styles, images={}, path='.'):
392                 self.node = node
393                 self.styles = styles
394                 self.canvas = None
395                 self.images = images
396                 self.path = path
397
398         def render(self, canvas, doc):
399                 canvas.saveState()
400                 cnv = _rml_canvas(canvas, doc, self.styles, images=self.images, path=self.path)
401                 cnv.render(self.node)
402                 canvas.restoreState()
403
404 class _rml_flowable(object):
405         def __init__(self, doc, images={}, path='.'):
406                 self.doc = doc
407                 self.styles = doc.styles
408                 self.images = images
409                 self.path = path
410
411         def _textual(self, node):
412                 rc = ''
413                 for n in node.childNodes:
414                         if n.nodeType == node.ELEMENT_NODE:
415                                 if n.localName == 'getName':
416                                         newNode = self.doc.dom.createTextNode(self.styles.names.get(n.getAttribute('id'),'Unknown name'))
417                                         node.insertBefore(newNode, n)
418                                         node.removeChild(n)
419                                 elif n.localName == 'pageNumber':
420                                         rc += '<pageNumber/>'            # TODO: change this !
421                                 else:
422                                         #CHECKME: I wonder if this is useful since we don't stock the result. Maybe for the getName tag?
423                                         self._textual(n)
424                                 rc += n.toxml()
425                         elif n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
426                                 rc += str2xml(n.data)
427                 return rc.encode(encoding, 'replace')
428
429         def _table(self, node):
430                 length = 0
431                 colwidths = None
432                 rowheights = None
433                 data = []
434                 childs = _child_get(node,'tr')
435                 if not childs:
436                         return None
437                 for tr in childs:
438                         data2 = []
439                         for td in _child_get(tr, 'td'):
440                                 flow = []
441                                 for n in td.childNodes:
442                                         if n.nodeType==node.ELEMENT_NODE:
443                                                 flow.append( self._flowable(n) )
444                                 if not len(flow):
445                                         flow = self._textual(td)
446                                 data2.append( flow )
447                         if len(data2)>length:
448                                 length=len(data2)
449                                 for ab in data:
450                                         while len(ab)<length:
451                                                 ab.append('')
452                         while len(data2)<length:
453                                 data2.append('')
454                         data.append( data2 )
455                 if node.hasAttribute('colWidths'):
456                         assert length == len(node.getAttribute('colWidths').split(','))
457                         colwidths = [utils.unit_get(f.strip()) for f in node.getAttribute('colWidths').split(',')]
458                 if node.hasAttribute('rowHeights'):
459                         rowheights = [utils.unit_get(f.strip()) for f in node.getAttribute('rowHeights').split(',')]
460                 table = platypus.Table(data = data, colWidths=colwidths, rowHeights=rowheights, **(utils.attr_get(node, ['splitByRow'] ,{'repeatRows':'int','repeatCols':'int'})))
461                 if node.hasAttribute('style'):
462                         table.setStyle(self.styles.table_styles[node.getAttribute('style')])
463                 return table
464
465         def _illustration(self, node):
466                 class Illustration(platypus.flowables.Flowable):
467                         def __init__(self, node, styles, self2):
468                                 self.node = node
469                                 self.styles = styles
470                                 self.width = utils.unit_get(node.getAttribute('width'))
471                                 self.height = utils.unit_get(node.getAttribute('height'))
472                                 self.self2 = self2
473                         def wrap(self, *args):
474                                 return (self.width, self.height)
475                         def draw(self):
476                                 canvas = self.canv
477                                 drw = _rml_draw(self.node, self.styles, images=self.self2.images, path=self.self2.path)
478                                 drw.render(self.canv, None)
479                 return Illustration(node, self.styles, self)
480
481         def _textual_image(self, node):
482                 import base64
483                 rc = ''
484                 for n in node.childNodes:
485                         if n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
486                                 rc += n.data
487                 return base64.decodestring(rc)
488
489         def _flowable(self, node):
490                 if node.localName=='para':
491                         style = self.styles.para_style_get(node)
492                         return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
493                 elif node.localName=='barCode':
494                         try:
495                                 from reportlab.graphics.barcode import code128
496                                 from reportlab.graphics.barcode import code39
497                                 from reportlab.graphics.barcode import code93
498                                 from reportlab.graphics.barcode import common
499                                 from reportlab.graphics.barcode import fourstate
500                                 from reportlab.graphics.barcode import usps
501                         except Exception, e:
502                                 print 'Warning: Reportlab barcode extension not installed !'
503                                 return None
504                         args = utils.attr_get(node, [], {'ratio':'float','xdim':'unit','height':'unit','checksum':'bool','quiet':'bool'})
505                         codes = {
506                                 'codabar': lambda x: common.Codabar(x, **args),
507                                 'code11': lambda x: common.Code11(x, **args),
508                                 'code128': lambda x: code128.Code128(x, **args),
509                                 'standard39': lambda x: code39.Standard39(x, **args),
510                                 'standard93': lambda x: code93.Standard93(x, **args),
511                                 'i2of5': lambda x: common.I2of5(x, **args),
512                                 'extended39': lambda x: code39.Extended39(x, **args),
513                                 'extended93': lambda x: code93.Extended93(x, **args),
514                                 'msi': lambda x: common.MSI(x, **args),
515                                 'fim': lambda x: usps.FIM(x, **args),
516                                 'postnet': lambda x: usps.POSTNET(x, **args),
517                         }
518                         code = 'code128'
519                         if node.hasAttribute('code'):
520                                 code = node.getAttribute('code').lower()
521                         return codes[code](self._textual(node))
522                 elif node.localName=='name':
523                         self.styles.names[ node.getAttribute('id')] = node.getAttribute('value')
524                         return None
525                 elif node.localName=='xpre':
526                         style = self.styles.para_style_get(node)
527                         return platypus.XPreformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int','frags':'int'})))
528                 elif node.localName=='pre':
529                         style = self.styles.para_style_get(node)
530                         return platypus.Preformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int'})))
531                 elif node.localName=='illustration':
532                         return  self._illustration(node)
533                 elif node.localName=='blockTable':
534                         return  self._table(node)
535                 elif node.localName=='title':
536                         styles = reportlab.lib.styles.getSampleStyleSheet()
537                         style = styles['Title']
538                         return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
539                 elif node.localName=='h1':
540                         styles = reportlab.lib.styles.getSampleStyleSheet()
541                         style = styles['Heading1']
542                         return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
543                 elif node.localName=='h2':
544                         styles = reportlab.lib.styles.getSampleStyleSheet()
545                         style = styles['Heading2']
546                         return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
547                 elif node.localName=='h3':
548                         styles = reportlab.lib.styles.getSampleStyleSheet()
549                         style = styles['Heading3']
550                         return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
551                 elif node.localName=='image':
552                         if not node.hasAttribute('file'):
553                                 if node.hasAttribute('name'):
554                                         image_data = self.doc.images[node.getAttribute('name')].read()
555                                 else:
556                                         import base64
557                                         image_data = base64.decodestring(node.firstChild.nodeValue)
558                                 image = StringIO.StringIO(image_data)
559                                 return platypus.Image(image, mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
560                         else:
561                                 return platypus.Image(node.getAttribute('file'), mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
562
563                         from reportlab.lib.utils import ImageReader
564                         name = str(node.getAttribute('file'))
565                         img = ImageReader(name)
566                         (sx,sy) = img.getSize()
567
568                         args = {}
569                         for tag in ('width','height'):
570                                 if node.hasAttribute(tag):
571                                         args[tag] = utils.unit_get(node.getAttribute(tag))
572                         if ('width' in args) and (not 'height' in args):
573                                 args['height'] = sy * args['width'] / sx
574                         elif ('height' in args) and (not 'width' in args):
575                                 args['width'] = sx * args['height'] / sy
576                         elif ('width' in args) and ('height' in args):
577                                 if (float(args['width'])/args['height'])>(float(sx)>sy):
578                                         args['width'] = sx * args['height'] / sy
579                                 else:
580                                         args['height'] = sy * args['width'] / sx
581                         return platypus.Image(name, mask=(250,255,250,255,250,255), **args)
582                 elif node.localName=='spacer':
583                         if node.hasAttribute('width'):
584                                 width = utils.unit_get(node.getAttribute('width'))
585                         else:
586                                 width = utils.unit_get('1cm')
587                         length = utils.unit_get(node.getAttribute('length'))
588                         return platypus.Spacer(width=width, height=length)
589                 elif node.localName=='section':
590                         return self.render(node)
591                 elif node.localName in ('pageBreak', 'nextPage'):
592                         return platypus.PageBreak()
593                 elif node.localName=='condPageBreak':
594                         return platypus.CondPageBreak(**(utils.attr_get(node, ['height'])))
595                 elif node.localName=='setNextTemplate':
596                         return platypus.NextPageTemplate(str(node.getAttribute('name')))
597                 elif node.localName=='nextFrame':
598                         return platypus.CondPageBreak(1000)           # TODO: change the 1000 !
599                 elif node.localName == 'setNextFrame':
600                         from reportlab.platypus.doctemplate import NextFrameFlowable
601                         return NextFrameFlowable(str(node.getAttribute('name')))
602                 elif node.localName == 'currentFrame':
603                         from reportlab.platypus.doctemplate import CurrentFrameFlowable
604                         return CurrentFrameFlowable(str(node.getAttribute('name')))
605                 elif node.localName == 'frameEnd':
606                         return EndFrameFlowable()
607                 else:
608                         sys.stderr.write('Warning: flowable not yet implemented: %s !\n' % (node.localName,))
609                         return None
610
611         def render(self, node_story):
612                 story = []
613                 node = node_story.firstChild
614                 while node:
615                         if node.nodeType == node.ELEMENT_NODE:
616                                 flow = self._flowable(node) 
617                                 if flow:
618                                         if type(flow) == type([]):
619                                                 story = story + flow
620                                         else:
621                                                 story.append(flow)
622                         node = node.nextSibling
623                 return story
624
625 from reportlab.platypus.doctemplate import ActionFlowable
626
627 class EndFrameFlowable(ActionFlowable):
628         def __init__(self,resume=0):
629                 ActionFlowable.__init__(self,('frameEnd',resume))
630
631 class TinyDocTemplate(platypus.BaseDocTemplate):
632
633         def ___handle_pageBegin(self):
634                 self.page = self.page + 1
635                 self.pageTemplate.beforeDrawPage(self.canv,self)
636                 self.pageTemplate.checkPageSize(self.canv,self)
637                 self.pageTemplate.onPage(self.canv,self)
638                 for f in self.pageTemplate.frames: f._reset()
639                 self.beforePage()
640                 #keep a count of flowables added to this page.  zero indicates bad stuff
641                 self._curPageFlowableCount = 0
642                 if hasattr(self,'_nextFrameIndex'):
643                         del self._nextFrameIndex
644                 for f in self.pageTemplate.frames:
645                         if f.id == 'first':
646                                 self.frame = f
647                                 break
648                 self.handle_frameBegin()
649
650 class _rml_template(object):
651         def __init__(self, out, node, doc, images={}, path='.'):
652                 self.images= images
653                 self.path = path
654                 if not node.hasAttribute('pageSize'):
655                         pageSize = (utils.unit_get('21cm'), utils.unit_get('29.7cm'))
656                 else:
657                         ps = map(lambda x:x.strip(), node.getAttribute('pageSize').replace(')', '').replace('(', '').split(','))
658                         pageSize = ( utils.unit_get(ps[0]),utils.unit_get(ps[1]) )
659                 cm = reportlab.lib.units.cm
660                 self.doc_tmpl = TinyDocTemplate(out, pagesize=pageSize, **utils.attr_get(node, ['leftMargin','rightMargin','topMargin','bottomMargin'], {'allowSplitting':'int','showBoundary':'bool','title':'str','author':'str'}))
661                 self.page_templates = []
662                 self.styles = doc.styles
663                 self.doc = doc
664                 pts = node.getElementsByTagName('pageTemplate')
665                 for pt in pts:
666                         frames = []
667                         for frame_el in pt.getElementsByTagName('frame'):
668                                 frame = platypus.Frame( **(utils.attr_get(frame_el, ['x1','y1', 'width','height', 'leftPadding', 'rightPadding', 'bottomPadding', 'topPadding'], {'id':'str', 'showBoundary':'bool'})) )
669                                 if utils.attr_get(frame_el, ['last']):
670                                         frame.lastFrame = True
671                                 frames.append( frame )
672                         gr = pt.getElementsByTagName('pageGraphics')
673                         if len(gr):
674                                 drw = _rml_draw(gr[0], self.doc, images=images, path=self.path)
675                                 self.page_templates.append( platypus.PageTemplate(frames=frames, onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) ))
676                         else:
677                                 self.page_templates.append( platypus.PageTemplate(frames=frames, **utils.attr_get(pt, [], {'id':'str'}) ))
678                 self.doc_tmpl.addPageTemplates(self.page_templates)
679
680         def render(self, node_stories):
681                 fis = []
682                 r = _rml_flowable(self.doc,images=self.images, path=self.path)
683                 for node_story in node_stories:
684                         fis += r.render(node_story)
685                         fis.append(platypus.PageBreak())
686                 self.doc_tmpl.build(fis)
687
688 def parseString(data, fout=None, images={}, path='.'):
689         r = _rml_doc(data, images, path)
690         if fout:
691                 fp = file(fout,'wb')
692                 r.render(fp)
693                 fp.close()
694                 return fout
695         else:
696                 fp = StringIO.StringIO()
697                 r.render(fp)
698                 return fp.getvalue()
699
700 def trml2pdf_help():
701         print 'Usage: trml2pdf input.rml >output.pdf'
702         print 'Render the standard input (RML) and output a PDF file'
703         sys.exit(0)
704
705 if __name__=="__main__":
706         if len(sys.argv)>1:
707                 if sys.argv[1]=='--help':
708                         trml2pdf_help()
709                 print parseString(file(sys.argv[1], 'r').read()),
710         else:
711                 print 'Usage: trml2pdf input.rml >output.pdf'
712                 print 'Try \'trml2pdf --help\' for more information.'