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