Removed reportlab -> 2.0
[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 render(self, out):
164                 el = self.dom.documentElement.getElementsByTagName('docinit')
165                 if el:
166                         self.docinit(el)
167
168                 el = self.dom.documentElement.getElementsByTagName('stylesheet')
169                 self.styles = _rml_styles(el)
170
171                 el = self.dom.documentElement.getElementsByTagName('template')
172                 if len(el):
173                         pt_obj = _rml_template(out, el[0], self, images=self.images, path=self.path)
174                         pt_obj.render(self.dom.documentElement.getElementsByTagName('story'))
175                 else:
176                         self.canvas = canvas.Canvas(out)
177                         pd = self.dom.documentElement.getElementsByTagName('pageDrawing')[0]
178                         pd_obj = _rml_canvas(self.canvas, None, self, self.images, path=self.path)
179                         pd_obj.render(pd)
180                         self.canvas.showPage()
181                         self.canvas.save()
182
183 class _rml_canvas(object):
184         def __init__(self, canvas, doc_tmpl=None, doc=None, images={}, path='.'):
185                 self.canvas = canvas
186                 self.styles = doc.styles
187                 self.doc_tmpl = doc_tmpl
188                 self.doc = doc
189                 self.images = images
190                 self.path = path
191
192         def _textual(self, node):
193                 rc = ''
194                 for n in node.childNodes:
195                         if n.nodeType == n.ELEMENT_NODE:
196                                 if n.localName == 'pageNumber':
197                                         rc += str(self.canvas.getPageNumber())
198                         elif n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
199                                 # this doesn't need to be "entities" encoded like flowables need to
200                                 rc += n.data
201                 return rc.encode(encoding, 'replace')
202
203         def _drawString(self, node):
204                 self.canvas.drawString(text=self._textual(node), **utils.attr_get(node, ['x','y']))
205         def _drawCenteredString(self, node):
206                 self.canvas.drawCentredString(text=self._textual(node), **utils.attr_get(node, ['x','y']))
207         def _drawRightString(self, node):
208                 self.canvas.drawRightString(text=self._textual(node), **utils.attr_get(node, ['x','y']))
209         def _rect(self, node):
210                 if node.hasAttribute('round'):
211                         self.canvas.roundRect(radius=utils.unit_get(node.getAttribute('round')), **utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
212                 else:
213                         self.canvas.rect(**utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
214
215         def _ellipse(self, node):
216                 x1 = utils.unit_get(node.getAttribute('x'))
217                 x2 = utils.unit_get(node.getAttribute('width'))
218                 y1 = utils.unit_get(node.getAttribute('y'))
219                 y2 = utils.unit_get(node.getAttribute('height'))
220                 self.canvas.ellipse(x1,y1,x2,y2, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
221         def _curves(self, node):
222                 line_str = utils.text_get(node).split()
223                 lines = []
224                 while len(line_str)>7:
225                         self.canvas.bezier(*[utils.unit_get(l) for l in line_str[0:8]])
226                         line_str = line_str[8:]
227         def _lines(self, node):
228                 line_str = utils.text_get(node).split()
229                 lines = []
230                 while len(line_str)>3:
231                         lines.append([utils.unit_get(l) for l in line_str[0:4]])
232                         line_str = line_str[4:]
233                 self.canvas.lines(lines)
234         def _grid(self, node):
235                 xlist = [utils.unit_get(s) for s in node.getAttribute('xs').split(',')]
236                 ylist = [utils.unit_get(s) for s in node.getAttribute('ys').split(',')]
237                 self.canvas.grid(xlist, ylist)
238         def _translate(self, node):
239                 dx = 0
240                 dy = 0
241                 if node.hasAttribute('dx'):
242                         dx = utils.unit_get(node.getAttribute('dx'))
243                 if node.hasAttribute('dy'):
244                         dy = utils.unit_get(node.getAttribute('dy'))
245                 self.canvas.translate(dx,dy)
246
247         def _circle(self, node):
248                 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'}))
249
250         def _place(self, node):
251                 flows = _rml_flowable(self.doc, images=self.images, path=self.path).render(node)
252                 infos = utils.attr_get(node, ['x','y','width','height'])
253
254                 infos['y']+=infos['height']
255                 for flow in flows:
256                         w,h = flow.wrap(infos['width'], infos['height'])
257                         if w<=infos['width'] and h<=infos['height']:
258                                 infos['y']-=h
259                                 flow.drawOn(self.canvas,infos['x'],infos['y'])
260                                 infos['height']-=h
261                         else:
262                                 raise ValueError, "Not enough space"
263
264         def _line_mode(self, node):
265                 ljoin = {'round':1, 'mitered':0, 'bevelled':2}
266                 lcap = {'default':0, 'round':1, 'square':2}
267                 if node.hasAttribute('width'):
268                         self.canvas.setLineWidth(utils.unit_get(node.getAttribute('width')))
269                 if node.hasAttribute('join'):
270                         self.canvas.setLineJoin(ljoin[node.getAttribute('join')])
271                 if node.hasAttribute('cap'):
272                         self.canvas.setLineCap(lcap[node.getAttribute('cap')])
273                 if node.hasAttribute('miterLimit'):
274                         self.canvas.setDash(utils.unit_get(node.getAttribute('miterLimit')))
275                 if node.hasAttribute('dash'):
276                         dashes = node.getAttribute('dash').split(',')
277                         for x in range(len(dashes)):
278                                 dashes[x]=utils.unit_get(dashes[x])
279                         self.canvas.setDash(node.getAttribute('dash').split(','))
280
281         def _image(self, node):
282                 import urllib
283                 from reportlab.lib.utils import ImageReader
284
285                 if not node.hasAttribute('file'):
286                         s = self.images[node.getAttribute('name')]
287                 else:
288                         try:
289                                 u = urllib.urlopen(str(node.getAttribute('file')))
290                                 s = StringIO.StringIO(u.read())
291                         except:
292                                 u = file(os.path.join(self.path,str(node.getAttribute('file'))), 'rb')
293                                 s = StringIO.StringIO(u.read())
294                 img = ImageReader(s)
295                 (sx,sy) = img.getSize()
296
297                 args = {}
298                 for tag in ('width','height','x','y'):
299                         if node.hasAttribute(tag):
300                                 args[tag] = utils.unit_get(node.getAttribute(tag))
301                 if ('width' in args) and (not 'height' in args):
302                         args['height'] = sy * args['width'] / sx
303                 elif ('height' in args) and (not 'width' in args):
304                         args['width'] = sx * args['height'] / sy
305                 elif ('width' in args) and ('height' in args):
306                         if (float(args['width'])/args['height'])>(float(sx)>sy):
307                                 args['width'] = sx * args['height'] / sy
308                         else:
309                                 args['height'] = sy * args['width'] / sx
310                 self.canvas.drawImage(img, **args)
311
312         def _path(self, node):
313                 self.path = self.canvas.beginPath()
314                 self.path.moveTo(**utils.attr_get(node, ['x','y']))
315                 for n in node.childNodes:
316                         if n.nodeType == node.ELEMENT_NODE:
317                                 if n.localName=='moveto':
318                                         vals = utils.text_get(n).split()
319                                         self.path.moveTo(utils.unit_get(vals[0]), utils.unit_get(vals[1]))
320                                 elif n.localName=='curvesto':
321                                         vals = utils.text_get(n).split()
322                                         while len(vals)>5:
323                                                 pos=[]
324                                                 while len(pos)<6:
325                                                         pos.append(utils.unit_get(vals.pop(0)))
326                                                 self.path.curveTo(*pos)
327                         elif (n.nodeType == node.TEXT_NODE):
328                                 data = n.data.split()               # Not sure if I must merge all TEXT_NODE ?
329                                 while len(data)>1:
330                                         x = utils.unit_get(data.pop(0))
331                                         y = utils.unit_get(data.pop(0))
332                                         self.path.lineTo(x,y)
333                 if (not node.hasAttribute('close')) or utils.bool_get(node.getAttribute('close')):
334                         self.path.close()
335                 self.canvas.drawPath(self.path, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
336
337         def render(self, node):
338                 tags = {
339                         'drawCentredString': self._drawCenteredString,
340                         'drawRightString': self._drawRightString,
341                         'drawString': self._drawString,
342                         'rect': self._rect,
343                         'ellipse': self._ellipse,
344                         'lines': self._lines,
345                         'grid': self._grid,
346                         'curves': self._curves,
347                         'fill': lambda node: self.canvas.setFillColor(color.get(node.getAttribute('color'))),
348                         'stroke': lambda node: self.canvas.setStrokeColor(color.get(node.getAttribute('color'))),
349                         'setFont': lambda node: self.canvas.setFont(node.getAttribute('name'), utils.unit_get(node.getAttribute('size'))),
350                         'place': self._place,
351                         'circle': self._circle,
352                         'lineMode': self._line_mode,
353                         'path': self._path,
354                         'rotate': lambda node: self.canvas.rotate(float(node.getAttribute('degrees'))),
355                         'translate': self._translate,
356                         'image': self._image
357                 }
358                 for nd in node.childNodes:
359                         if nd.nodeType==nd.ELEMENT_NODE:
360                                 for tag in tags:
361                                         if nd.localName==tag:
362                                                 tags[tag](nd)
363                                                 break
364
365 class _rml_draw(object):
366         def __init__(self, node, styles, images={}, path='.'):
367                 self.node = node
368                 self.styles = styles
369                 self.canvas = None
370                 self.images = images
371                 self.path = path
372
373         def render(self, canvas, doc):
374                 canvas.saveState()
375                 cnv = _rml_canvas(canvas, doc, self.styles, images=self.images, path=self.path)
376                 cnv.render(self.node)
377                 canvas.restoreState()
378
379 class _rml_flowable(object):
380         def __init__(self, doc, images={}, path='.'):
381                 self.doc = doc
382                 self.styles = doc.styles
383                 self.images = images
384                 self.path = path
385
386         def _textual(self, node):
387                 rc = ''
388                 for n in node.childNodes:
389                         if n.nodeType == node.ELEMENT_NODE:
390                                 if n.localName == 'getName':
391                                         newNode = self.doc.dom.createTextNode(self.styles.names.get(n.getAttribute('id'),'Unknown name'))
392                                         node.insertBefore(newNode, n)
393                                         node.removeChild(n)
394                                 elif n.localName == 'pageNumber':
395                                         rc += '<pageNumber/>'            # TODO: change this !
396                                 else:
397                                         #CHECKME: I wonder if this is useful since we don't stock the result. Maybe for the getName tag?
398                                         self._textual(n)
399                                 rc += n.toxml()
400                         elif n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
401                                 rc += str2xml(n.data)
402                 return rc.encode(encoding, 'replace')
403
404         def _table(self, node):
405                 length = 0
406                 colwidths = None
407                 rowheights = None
408                 data = []
409                 childs = _child_get(node,'tr')
410                 if not childs:
411                         return None
412                 for tr in childs:
413                         data2 = []
414                         for td in _child_get(tr, 'td'):
415                                 flow = []
416                                 for n in td.childNodes:
417                                         if n.nodeType==node.ELEMENT_NODE:
418                                                 flow.append( self._flowable(n) )
419                                 if not len(flow):
420                                         flow = self._textual(td)
421                                 data2.append( flow )
422                         if len(data2)>length:
423                                 length=len(data2)
424                                 for ab in data:
425                                         while len(ab)<length:
426                                                 ab.append('')
427                         while len(data2)<length:
428                                 data2.append('')
429                         data.append( data2 )
430                 if node.hasAttribute('colWidths'):
431                         assert length == len(node.getAttribute('colWidths').split(','))
432                         colwidths = [utils.unit_get(f.strip()) for f in node.getAttribute('colWidths').split(',')]
433                 if node.hasAttribute('rowHeights'):
434                         rowheights = [utils.unit_get(f.strip()) for f in node.getAttribute('rowHeights').split(',')]
435                 table = platypus.Table(data = data, colWidths=colwidths, rowHeights=rowheights, **(utils.attr_get(node, ['splitByRow'] ,{'repeatRows':'int','repeatCols':'int'})))
436                 if node.hasAttribute('style'):
437                         table.setStyle(self.styles.table_styles[node.getAttribute('style')])
438                 return table
439
440         def _illustration(self, node):
441                 class Illustration(platypus.flowables.Flowable):
442                         def __init__(self, node, styles, self2):
443                                 self.node = node
444                                 self.styles = styles
445                                 self.width = utils.unit_get(node.getAttribute('width'))
446                                 self.height = utils.unit_get(node.getAttribute('height'))
447                                 self.self2 = self2
448                         def wrap(self, *args):
449                                 return (self.width, self.height)
450                         def draw(self):
451                                 canvas = self.canv
452                                 drw = _rml_draw(self.node, self.styles, images=self.self2.images, path=self.self2.path)
453                                 drw.render(self.canv, None)
454                 return Illustration(node, self.styles, self)
455
456         def _flowable(self, node):
457                 if node.localName=='para':
458                         style = self.styles.para_style_get(node)
459                         return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
460                 elif node.localName=='barCode':
461                         try:
462                                 from reportlab.extensions.barcode import code128
463                                 from reportlab.extensions.barcode import code39
464                                 from reportlab.extensions.barcode import code93
465                                 from reportlab.extensions.barcode import common
466                                 from reportlab.extensions.barcode import fourstate
467                                 from reportlab.extensions.barcode import usps
468                         except Exception, e:
469                                 print 'Warning: Reportlab barcode extension not installed !'
470                                 return None
471                         args = utils.attr_get(node, [], {'ratio':'float','xdim':'unit','height':'unit','checksum':'bool','quiet':'bool'})
472                         codes = {
473                                 'codabar': lambda x: common.Codabar(x, **args),
474                                 'code11': lambda x: common.Code11(x, **args),
475                                 'code128': lambda x: code128.Code128(x, **args),
476                                 'standard39': lambda x: code39.Standard39(x, **args),
477                                 'standard93': lambda x: code93.Standard93(x, **args),
478                                 'i2of5': lambda x: common.I2of5(x, **args),
479                                 'extended39': lambda x: code39.Extended39(x, **args),
480                                 'extended93': lambda x: code93.Extended93(x, **args),
481                                 'msi': lambda x: common.MSI(x, **args),
482                                 'fim': lambda x: usps.FIM(x, **args),
483                                 'postnet': lambda x: usps.POSTNET(x, **args),
484                         }
485                         code = 'code128'
486                         if node.hasAttribute('code'):
487                                 code = node.getAttribute('code').lower()
488                         return codes[code](self._textual(node))
489                 elif node.localName=='name':
490                         self.styles.names[ node.getAttribute('id')] = node.getAttribute('value')
491                         return None
492                 elif node.localName=='xpre':
493                         style = self.styles.para_style_get(node)
494                         return platypus.XPreformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int','frags':'int'})))
495                 elif node.localName=='pre':
496                         style = self.styles.para_style_get(node)
497                         return platypus.Preformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int'})))
498                 elif node.localName=='illustration':
499                         return  self._illustration(node)
500                 elif node.localName=='blockTable':
501                         return  self._table(node)
502                 elif node.localName=='title':
503                         styles = reportlab.lib.styles.getSampleStyleSheet()
504                         style = styles['Title']
505                         return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
506                 elif node.localName=='h1':
507                         styles = reportlab.lib.styles.getSampleStyleSheet()
508                         style = styles['Heading1']
509                         return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
510                 elif node.localName=='h2':
511                         styles = reportlab.lib.styles.getSampleStyleSheet()
512                         style = styles['Heading2']
513                         return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
514                 elif node.localName=='h3':
515                         styles = reportlab.lib.styles.getSampleStyleSheet()
516                         style = styles['Heading3']
517                         return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
518                 elif node.localName=='image':
519                         if not node.hasAttribute('file'):
520                                 name = '/tmp/image_%d.jpg' % (int(node.getAttribute('name')),)
521                                 file(name,'wb+').write( self.doc.images[node.getAttribute('name')].read() )
522                                 return platypus.Image(name, mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
523                         else:
524                                 return platypus.Image(node.getAttribute('file'), mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
525
526                         from reportlab.lib.utils import ImageReader
527                         name = str(node.getAttribute('file'))
528                         img = ImageReader(name)
529                         (sx,sy) = img.getSize()
530
531                         args = {}
532                         for tag in ('width','height'):
533                                 if node.hasAttribute(tag):
534                                         args[tag] = utils.unit_get(node.getAttribute(tag))
535                         if ('width' in args) and (not 'height' in args):
536                                 args['height'] = sy * args['width'] / sx
537                         elif ('height' in args) and (not 'width' in args):
538                                 args['width'] = sx * args['height'] / sy
539                         elif ('width' in args) and ('height' in args):
540                                 if (float(args['width'])/args['height'])>(float(sx)>sy):
541                                         args['width'] = sx * args['height'] / sy
542                                 else:
543                                         args['height'] = sy * args['width'] / sx
544                         return platypus.Image(name, mask=(250,255,250,255,250,255), **args)
545                 elif node.localName=='spacer':
546                         if node.hasAttribute('width'):
547                                 width = utils.unit_get(node.getAttribute('width'))
548                         else:
549                                 width = utils.unit_get('1cm')
550                         length = utils.unit_get(node.getAttribute('length'))
551                         return platypus.Spacer(width=width, height=length)
552                 elif node.localName=='section':
553                         return self.render(node)
554                 elif node.localName in ('pageBreak', 'nextPage'):
555                         return platypus.PageBreak()
556                 elif node.localName=='condPageBreak':
557                         return platypus.CondPageBreak(**(utils.attr_get(node, ['height'])))
558                 elif node.localName=='setNextTemplate':
559                         return platypus.NextPageTemplate(str(node.getAttribute('name')))
560                 elif node.localName=='nextFrame':
561                         return platypus.CondPageBreak(1000)           # TODO: change the 1000 !
562                 elif node.localName == 'currentFrame':
563                         #return platypus.CondPageBreak(1000)           # TODO: change the 1000 !
564                         from reportlab.platypus.doctemplate import CurrentFrameFlowable
565                         return CurrentFrameFlowable(str(node.getAttribute('name')))
566                 elif node.localName == 'frameEnd':
567                         return False
568                         return EndFrameFlowable()
569                 else:
570                         sys.stderr.write('Warning: flowable not yet implemented: %s !\n' % (node.localName,))
571                         return None
572
573         def render(self, node_story):
574                 story = []
575                 node = node_story.firstChild
576                 while node:
577                         if node.nodeType == node.ELEMENT_NODE:
578                                 flow = self._flowable(node) 
579                                 if flow:
580                                         if type(flow) == type([]):
581                                                 story = story + flow
582                                         else:
583                                                 story.append(flow)
584                         node = node.nextSibling
585                 return story
586
587 from reportlab.platypus.doctemplate import ActionFlowable
588
589 class EndFrameFlowable(ActionFlowable):
590         def __init__(self,resume=0):
591                 ActionFlowable.__init__(self,('frameEnd',resume))
592
593 class TinyDocTemplate(platypus.BaseDocTemplate):
594
595         def ___handle_pageBegin(self):
596                 self.page = self.page + 1
597                 self.pageTemplate.beforeDrawPage(self.canv,self)
598                 self.pageTemplate.checkPageSize(self.canv,self)
599                 self.pageTemplate.onPage(self.canv,self)
600                 for f in self.pageTemplate.frames: f._reset()
601                 self.beforePage()
602                 #keep a count of flowables added to this page.  zero indicates bad stuff
603                 self._curPageFlowableCount = 0
604                 if hasattr(self,'_nextFrameIndex'):
605                         del self._nextFrameIndex
606                 for f in self.pageTemplate.frames:
607                         if f.id == 'first':
608                                 self.frame = f
609                                 break
610                 self.handle_frameBegin()
611
612 class _rml_template(object):
613         def __init__(self, out, node, doc, images={}, path='.'):
614                 self.images= images
615                 self.path = path
616                 if not node.hasAttribute('pageSize'):
617                         pageSize = (utils.unit_get('21cm'), utils.unit_get('29.7cm'))
618                 else:
619                         ps = map(lambda x:x.strip(), node.getAttribute('pageSize').replace(')', '').replace('(', '').split(','))
620                         pageSize = ( utils.unit_get(ps[0]),utils.unit_get(ps[1]) )
621                 cm = reportlab.lib.units.cm
622                 self.doc_tmpl = TinyDocTemplate(out, pagesize=pageSize, **utils.attr_get(node, ['leftMargin','rightMargin','topMargin','bottomMargin'], {'allowSplitting':'int','showBoundary':'bool','title':'str','author':'str'}))
623                 self.page_templates = []
624                 self.styles = doc.styles
625                 self.doc = doc
626                 pts = node.getElementsByTagName('pageTemplate')
627                 for pt in pts:
628                         frames = []
629                         for frame_el in pt.getElementsByTagName('frame'):
630                                 frame = platypus.Frame( **(utils.attr_get(frame_el, ['x1','y1', 'width','height', 'leftPadding', 'rightPadding', 'bottomPadding', 'topPadding'], {'id':'str', 'showBoundary':'bool'})) )
631                                 frames.append( frame )
632                         gr = pt.getElementsByTagName('pageGraphics')
633                         if len(gr):
634                                 drw = _rml_draw(gr[0], self.doc, images=images)
635                                 self.page_templates.append( platypus.PageTemplate(frames=frames, onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) ))
636                         else:
637                                 self.page_templates.append( platypus.PageTemplate(frames=frames, **utils.attr_get(pt, [], {'id':'str'}) ))
638                 self.doc_tmpl.addPageTemplates(self.page_templates)
639
640         def render(self, node_stories):
641                 fis = []
642                 r = _rml_flowable(self.doc,images=self.images, path=self.path)
643                 for node_story in node_stories:
644                         fis += r.render(node_story)
645                         fis.append(platypus.PageBreak())
646                 self.doc_tmpl.build(fis)
647
648 def parseString(data, fout=None, images={}, path='.'):
649         r = _rml_doc(data, images, path)
650         if fout:
651                 fp = file(fout,'wb')
652                 r.render(fp)
653                 fp.close()
654                 return fout
655         else:
656                 fp = StringIO.StringIO()
657                 r.render(fp)
658                 return fp.getvalue()
659
660 def trml2pdf_help():
661         print 'Usage: trml2pdf input.rml >output.pdf'
662         print 'Render the standard input (RML) and output a PDF file'
663         sys.exit(0)
664
665 if __name__=="__main__":
666         if len(sys.argv)>1:
667                 if sys.argv[1]=='--help':
668                         trml2pdf_help()
669                 print parseString(file(sys.argv[1], 'r').read()),
670         else:
671                 print 'Usage: trml2pdf input.rml >output.pdf'
672                 print 'Try \'trml2pdf --help\' for more information.'