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