Removed bad files
[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                         s = self.images[node.getAttribute('name')]
305                 else:
306                         if node.getAttribute('file') in self.images:
307                                 s = StringIO.StringIO(self.images[node.getAttribute('file')])
308                         else:
309                                 try:
310                                         u = urllib.urlopen(str(node.getAttribute('file')))
311                                         s = StringIO.StringIO(u.read())
312                                 except:
313                                         u = file(os.path.join(self.path,str(node.getAttribute('file'))), 'rb')
314                                         s = StringIO.StringIO(u.read())
315                 img = ImageReader(s)
316                 (sx,sy) = img.getSize()
317
318                 args = {}
319                 for tag in ('width','height','x','y'):
320                         if node.hasAttribute(tag):
321                                 args[tag] = utils.unit_get(node.getAttribute(tag))
322                 if ('width' in args) and (not 'height' in args):
323                         args['height'] = sy * args['width'] / sx
324                 elif ('height' in args) and (not 'width' in args):
325                         args['width'] = sx * args['height'] / sy
326                 elif ('width' in args) and ('height' in args):
327                         if (float(args['width'])/args['height'])>(float(sx)>sy):
328                                 args['width'] = sx * args['height'] / sy
329                         else:
330                                 args['height'] = sy * args['width'] / sx
331                 self.canvas.drawImage(img, **args)
332
333         def _path(self, node):
334                 self.path = self.canvas.beginPath()
335                 self.path.moveTo(**utils.attr_get(node, ['x','y']))
336                 for n in node.childNodes:
337                         if n.nodeType == node.ELEMENT_NODE:
338                                 if n.localName=='moveto':
339                                         vals = utils.text_get(n).split()
340                                         self.path.moveTo(utils.unit_get(vals[0]), utils.unit_get(vals[1]))
341                                 elif n.localName=='curvesto':
342                                         vals = utils.text_get(n).split()
343                                         while len(vals)>5:
344                                                 pos=[]
345                                                 while len(pos)<6:
346                                                         pos.append(utils.unit_get(vals.pop(0)))
347                                                 self.path.curveTo(*pos)
348                         elif (n.nodeType == node.TEXT_NODE):
349                                 data = n.data.split()               # Not sure if I must merge all TEXT_NODE ?
350                                 while len(data)>1:
351                                         x = utils.unit_get(data.pop(0))
352                                         y = utils.unit_get(data.pop(0))
353                                         self.path.lineTo(x,y)
354                 if (not node.hasAttribute('close')) or utils.bool_get(node.getAttribute('close')):
355                         self.path.close()
356                 self.canvas.drawPath(self.path, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
357
358         def render(self, node):
359                 tags = {
360                         'drawCentredString': self._drawCenteredString,
361                         'drawRightString': self._drawRightString,
362                         'drawString': self._drawString,
363                         'rect': self._rect,
364                         'ellipse': self._ellipse,
365                         'lines': self._lines,
366                         'grid': self._grid,
367                         'curves': self._curves,
368                         'fill': lambda node: self.canvas.setFillColor(color.get(node.getAttribute('color'))),
369                         'stroke': lambda node: self.canvas.setStrokeColor(color.get(node.getAttribute('color'))),
370                         'setFont': lambda node: self.canvas.setFont(node.getAttribute('name'), utils.unit_get(node.getAttribute('size'))),
371                         'place': self._place,
372                         'circle': self._circle,
373                         'lineMode': self._line_mode,
374                         'path': self._path,
375                         'rotate': lambda node: self.canvas.rotate(float(node.getAttribute('degrees'))),
376                         'translate': self._translate,
377                         'image': self._image
378                 }
379                 for nd in node.childNodes:
380                         if nd.nodeType==nd.ELEMENT_NODE:
381                                 for tag in tags:
382                                         if nd.localName==tag:
383                                                 tags[tag](nd)
384                                                 break
385
386 class _rml_draw(object):
387         def __init__(self, node, styles, images={}, path='.'):
388                 self.node = node
389                 self.styles = styles
390                 self.canvas = None
391                 self.images = images
392                 self.path = path
393
394         def render(self, canvas, doc):
395                 canvas.saveState()
396                 cnv = _rml_canvas(canvas, doc, self.styles, images=self.images, path=self.path)
397                 cnv.render(self.node)
398                 canvas.restoreState()
399
400 class _rml_flowable(object):
401         def __init__(self, doc, images={}, path='.'):
402                 self.doc = doc
403                 self.styles = doc.styles
404                 self.images = images
405                 self.path = path
406
407         def _textual(self, node):
408                 rc = ''
409                 for n in node.childNodes:
410                         if n.nodeType == node.ELEMENT_NODE:
411                                 if n.localName == 'getName':
412                                         newNode = self.doc.dom.createTextNode(self.styles.names.get(n.getAttribute('id'),'Unknown name'))
413                                         node.insertBefore(newNode, n)
414                                         node.removeChild(n)
415                                 elif n.localName == 'pageNumber':
416                                         rc += '<pageNumber/>'            # TODO: change this !
417                                 else:
418                                         #CHECKME: I wonder if this is useful since we don't stock the result. Maybe for the getName tag?
419                                         self._textual(n)
420                                 rc += n.toxml()
421                         elif n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
422                                 rc += str2xml(n.data)
423                 return rc.encode(encoding, 'replace')
424
425         def _table(self, node):
426                 length = 0
427                 colwidths = None
428                 rowheights = None
429                 data = []
430                 childs = _child_get(node,'tr')
431                 if not childs:
432                         return None
433                 for tr in childs:
434                         data2 = []
435                         for td in _child_get(tr, 'td'):
436                                 flow = []
437                                 for n in td.childNodes:
438                                         if n.nodeType==node.ELEMENT_NODE:
439                                                 flow.append( self._flowable(n) )
440                                 if not len(flow):
441                                         flow = self._textual(td)
442                                 data2.append( flow )
443                         if len(data2)>length:
444                                 length=len(data2)
445                                 for ab in data:
446                                         while len(ab)<length:
447                                                 ab.append('')
448                         while len(data2)<length:
449                                 data2.append('')
450                         data.append( data2 )
451                 if node.hasAttribute('colWidths'):
452                         assert length == len(node.getAttribute('colWidths').split(','))
453                         colwidths = [utils.unit_get(f.strip()) for f in node.getAttribute('colWidths').split(',')]
454                 if node.hasAttribute('rowHeights'):
455                         rowheights = [utils.unit_get(f.strip()) for f in node.getAttribute('rowHeights').split(',')]
456                 table = platypus.Table(data = data, colWidths=colwidths, rowHeights=rowheights, **(utils.attr_get(node, ['splitByRow'] ,{'repeatRows':'int','repeatCols':'int'})))
457                 if node.hasAttribute('style'):
458                         table.setStyle(self.styles.table_styles[node.getAttribute('style')])
459                 return table
460
461         def _illustration(self, node):
462                 class Illustration(platypus.flowables.Flowable):
463                         def __init__(self, node, styles, self2):
464                                 self.node = node
465                                 self.styles = styles
466                                 self.width = utils.unit_get(node.getAttribute('width'))
467                                 self.height = utils.unit_get(node.getAttribute('height'))
468                                 self.self2 = self2
469                         def wrap(self, *args):
470                                 return (self.width, self.height)
471                         def draw(self):
472                                 canvas = self.canv
473                                 drw = _rml_draw(self.node, self.styles, images=self.self2.images, path=self.self2.path)
474                                 drw.render(self.canv, None)
475                 return Illustration(node, self.styles, self)
476
477         def _textual_image(self, node):
478                 import base64
479                 rc = ''
480                 for n in node.childNodes:
481                         if n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
482                                 rc += n.data
483                 return base64.decodestring(rc)
484
485         def _flowable(self, node):
486                 if node.localName=='para':
487                         style = self.styles.para_style_get(node)
488                         return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
489                 elif node.localName=='barCode':
490                         try:
491                                 from reportlab.graphics.barcode import code128
492                                 from reportlab.graphics.barcode import code39
493                                 from reportlab.graphics.barcode import code93
494                                 from reportlab.graphics.barcode import common
495                                 from reportlab.graphics.barcode import fourstate
496                                 from reportlab.graphics.barcode import usps
497                         except Exception, e:
498                                 print 'Warning: Reportlab barcode extension not installed !'
499                                 return None
500                         args = utils.attr_get(node, [], {'ratio':'float','xdim':'unit','height':'unit','checksum':'bool','quiet':'bool'})
501                         codes = {
502                                 'codabar': lambda x: common.Codabar(x, **args),
503                                 'code11': lambda x: common.Code11(x, **args),
504                                 'code128': lambda x: code128.Code128(x, **args),
505                                 'standard39': lambda x: code39.Standard39(x, **args),
506                                 'standard93': lambda x: code93.Standard93(x, **args),
507                                 'i2of5': lambda x: common.I2of5(x, **args),
508                                 'extended39': lambda x: code39.Extended39(x, **args),
509                                 'extended93': lambda x: code93.Extended93(x, **args),
510                                 'msi': lambda x: common.MSI(x, **args),
511                                 'fim': lambda x: usps.FIM(x, **args),
512                                 'postnet': lambda x: usps.POSTNET(x, **args),
513                         }
514                         code = 'code128'
515                         if node.hasAttribute('code'):
516                                 code = node.getAttribute('code').lower()
517                         return codes[code](self._textual(node))
518                 elif node.localName=='name':
519                         self.styles.names[ node.getAttribute('id')] = node.getAttribute('value')
520                         return None
521                 elif node.localName=='xpre':
522                         style = self.styles.para_style_get(node)
523                         return platypus.XPreformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int','frags':'int'})))
524                 elif node.localName=='pre':
525                         style = self.styles.para_style_get(node)
526                         return platypus.Preformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int'})))
527                 elif node.localName=='illustration':
528                         return  self._illustration(node)
529                 elif node.localName=='blockTable':
530                         return  self._table(node)
531                 elif node.localName=='title':
532                         styles = reportlab.lib.styles.getSampleStyleSheet()
533                         style = styles['Title']
534                         return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
535                 elif node.localName=='h1':
536                         styles = reportlab.lib.styles.getSampleStyleSheet()
537                         style = styles['Heading1']
538                         return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
539                 elif node.localName=='h2':
540                         styles = reportlab.lib.styles.getSampleStyleSheet()
541                         style = styles['Heading2']
542                         return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
543                 elif node.localName=='h3':
544                         styles = reportlab.lib.styles.getSampleStyleSheet()
545                         style = styles['Heading3']
546                         return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
547                 elif node.localName=='image':
548                         if not node.hasAttribute('file'):
549                                 if node.hasAttribute('name'):
550                                         image_data = self.doc.images[node.getAttribute('name')].read()
551                                 else:
552                                         import base64
553                                         image_data = base64.decodestring(node.firstChild.nodeValue)
554                                 image = StringIO.StringIO(image_data)
555                                 return platypus.Image(image, mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
556                         else:
557                                 return platypus.Image(node.getAttribute('file'), mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
558
559                         from reportlab.lib.utils import ImageReader
560                         name = str(node.getAttribute('file'))
561                         img = ImageReader(name)
562                         (sx,sy) = img.getSize()
563
564                         args = {}
565                         for tag in ('width','height'):
566                                 if node.hasAttribute(tag):
567                                         args[tag] = utils.unit_get(node.getAttribute(tag))
568                         if ('width' in args) and (not 'height' in args):
569                                 args['height'] = sy * args['width'] / sx
570                         elif ('height' in args) and (not 'width' in args):
571                                 args['width'] = sx * args['height'] / sy
572                         elif ('width' in args) and ('height' in args):
573                                 if (float(args['width'])/args['height'])>(float(sx)>sy):
574                                         args['width'] = sx * args['height'] / sy
575                                 else:
576                                         args['height'] = sy * args['width'] / sx
577                         return platypus.Image(name, mask=(250,255,250,255,250,255), **args)
578                 elif node.localName=='spacer':
579                         if node.hasAttribute('width'):
580                                 width = utils.unit_get(node.getAttribute('width'))
581                         else:
582                                 width = utils.unit_get('1cm')
583                         length = utils.unit_get(node.getAttribute('length'))
584                         return platypus.Spacer(width=width, height=length)
585                 elif node.localName=='section':
586                         return self.render(node)
587                 elif node.localName in ('pageBreak', 'nextPage'):
588                         return platypus.PageBreak()
589                 elif node.localName=='condPageBreak':
590                         return platypus.CondPageBreak(**(utils.attr_get(node, ['height'])))
591                 elif node.localName=='setNextTemplate':
592                         return platypus.NextPageTemplate(str(node.getAttribute('name')))
593                 elif node.localName=='nextFrame':
594                         return platypus.CondPageBreak(1000)           # TODO: change the 1000 !
595                 elif node.localName == 'setNextFrame':
596                         from reportlab.platypus.doctemplate import NextFrameFlowable
597                         return NextFrameFlowable(str(node.getAttribute('name')))
598                 elif node.localName == 'currentFrame':
599                         from reportlab.platypus.doctemplate import CurrentFrameFlowable
600                         return CurrentFrameFlowable(str(node.getAttribute('name')))
601                 elif node.localName == 'frameEnd':
602                         return EndFrameFlowable()
603                 else:
604                         sys.stderr.write('Warning: flowable not yet implemented: %s !\n' % (node.localName,))
605                         return None
606
607         def render(self, node_story):
608                 story = []
609                 node = node_story.firstChild
610                 while node:
611                         if node.nodeType == node.ELEMENT_NODE:
612                                 flow = self._flowable(node) 
613                                 if flow:
614                                         if type(flow) == type([]):
615                                                 story = story + flow
616                                         else:
617                                                 story.append(flow)
618                         node = node.nextSibling
619                 return story
620
621 from reportlab.platypus.doctemplate import ActionFlowable
622
623 class EndFrameFlowable(ActionFlowable):
624         def __init__(self,resume=0):
625                 ActionFlowable.__init__(self,('frameEnd',resume))
626
627 class TinyDocTemplate(platypus.BaseDocTemplate):
628
629         def ___handle_pageBegin(self):
630                 self.page = self.page + 1
631                 self.pageTemplate.beforeDrawPage(self.canv,self)
632                 self.pageTemplate.checkPageSize(self.canv,self)
633                 self.pageTemplate.onPage(self.canv,self)
634                 for f in self.pageTemplate.frames: f._reset()
635                 self.beforePage()
636                 #keep a count of flowables added to this page.  zero indicates bad stuff
637                 self._curPageFlowableCount = 0
638                 if hasattr(self,'_nextFrameIndex'):
639                         del self._nextFrameIndex
640                 for f in self.pageTemplate.frames:
641                         if f.id == 'first':
642                                 self.frame = f
643                                 break
644                 self.handle_frameBegin()
645
646 class _rml_template(object):
647         def __init__(self, out, node, doc, images={}, path='.'):
648                 self.images= images
649                 self.path = path
650                 if not node.hasAttribute('pageSize'):
651                         pageSize = (utils.unit_get('21cm'), utils.unit_get('29.7cm'))
652                 else:
653                         ps = map(lambda x:x.strip(), node.getAttribute('pageSize').replace(')', '').replace('(', '').split(','))
654                         pageSize = ( utils.unit_get(ps[0]),utils.unit_get(ps[1]) )
655                 cm = reportlab.lib.units.cm
656                 self.doc_tmpl = TinyDocTemplate(out, pagesize=pageSize, **utils.attr_get(node, ['leftMargin','rightMargin','topMargin','bottomMargin'], {'allowSplitting':'int','showBoundary':'bool','title':'str','author':'str'}))
657                 self.page_templates = []
658                 self.styles = doc.styles
659                 self.doc = doc
660                 pts = node.getElementsByTagName('pageTemplate')
661                 for pt in pts:
662                         frames = []
663                         for frame_el in pt.getElementsByTagName('frame'):
664                                 frame = platypus.Frame( **(utils.attr_get(frame_el, ['x1','y1', 'width','height', 'leftPadding', 'rightPadding', 'bottomPadding', 'topPadding'], {'id':'str', 'showBoundary':'bool'})) )
665                                 if utils.attr_get(frame_el, ['last']):
666                                         frame.lastFrame = True
667                                 frames.append( frame )
668                         gr = pt.getElementsByTagName('pageGraphics')
669                         if len(gr):
670                                 drw = _rml_draw(gr[0], self.doc, images=images, path=self.path)
671                                 self.page_templates.append( platypus.PageTemplate(frames=frames, onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) ))
672                         else:
673                                 self.page_templates.append( platypus.PageTemplate(frames=frames, **utils.attr_get(pt, [], {'id':'str'}) ))
674                 self.doc_tmpl.addPageTemplates(self.page_templates)
675
676         def render(self, node_stories):
677                 fis = []
678                 r = _rml_flowable(self.doc,images=self.images, path=self.path)
679                 for node_story in node_stories:
680                         fis += r.render(node_story)
681                         fis.append(platypus.PageBreak())
682                 self.doc_tmpl.build(fis)
683
684 def parseString(data, fout=None, images={}, path='.'):
685         r = _rml_doc(data, images, path)
686         if fout:
687                 fp = file(fout,'wb')
688                 r.render(fp)
689                 fp.close()
690                 return fout
691         else:
692                 fp = StringIO.StringIO()
693                 r.render(fp)
694                 return fp.getvalue()
695
696 def trml2pdf_help():
697         print 'Usage: trml2pdf input.rml >output.pdf'
698         print 'Render the standard input (RML) and output a PDF file'
699         sys.exit(0)
700
701 if __name__=="__main__":
702         if len(sys.argv)>1:
703                 if sys.argv[1]=='--help':
704                         trml2pdf_help()
705                 print parseString(file(sys.argv[1], 'r').read()),
706         else:
707                 print 'Usage: trml2pdf input.rml >output.pdf'
708                 print 'Try \'trml2pdf --help\' for more information.'