Fix barcode on report and re-enable "Products Labels"
[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 == 'currentFrame':
596                         #return platypus.CondPageBreak(1000)           # TODO: change the 1000 !
597                         from reportlab.platypus.doctemplate import CurrentFrameFlowable
598                         return CurrentFrameFlowable(str(node.getAttribute('name')))
599                 elif node.localName == 'frameEnd':
600                         return False
601                         return EndFrameFlowable()
602                 else:
603                         sys.stderr.write('Warning: flowable not yet implemented: %s !\n' % (node.localName,))
604                         return None
605
606         def render(self, node_story):
607                 story = []
608                 node = node_story.firstChild
609                 while node:
610                         if node.nodeType == node.ELEMENT_NODE:
611                                 flow = self._flowable(node) 
612                                 if flow:
613                                         if type(flow) == type([]):
614                                                 story = story + flow
615                                         else:
616                                                 story.append(flow)
617                         node = node.nextSibling
618                 return story
619
620 from reportlab.platypus.doctemplate import ActionFlowable
621
622 class EndFrameFlowable(ActionFlowable):
623         def __init__(self,resume=0):
624                 ActionFlowable.__init__(self,('frameEnd',resume))
625
626 class TinyDocTemplate(platypus.BaseDocTemplate):
627
628         def ___handle_pageBegin(self):
629                 self.page = self.page + 1
630                 self.pageTemplate.beforeDrawPage(self.canv,self)
631                 self.pageTemplate.checkPageSize(self.canv,self)
632                 self.pageTemplate.onPage(self.canv,self)
633                 for f in self.pageTemplate.frames: f._reset()
634                 self.beforePage()
635                 #keep a count of flowables added to this page.  zero indicates bad stuff
636                 self._curPageFlowableCount = 0
637                 if hasattr(self,'_nextFrameIndex'):
638                         del self._nextFrameIndex
639                 for f in self.pageTemplate.frames:
640                         if f.id == 'first':
641                                 self.frame = f
642                                 break
643                 self.handle_frameBegin()
644
645 class _rml_template(object):
646         def __init__(self, out, node, doc, images={}, path='.'):
647                 self.images= images
648                 self.path = path
649                 if not node.hasAttribute('pageSize'):
650                         pageSize = (utils.unit_get('21cm'), utils.unit_get('29.7cm'))
651                 else:
652                         ps = map(lambda x:x.strip(), node.getAttribute('pageSize').replace(')', '').replace('(', '').split(','))
653                         pageSize = ( utils.unit_get(ps[0]),utils.unit_get(ps[1]) )
654                 cm = reportlab.lib.units.cm
655                 self.doc_tmpl = TinyDocTemplate(out, pagesize=pageSize, **utils.attr_get(node, ['leftMargin','rightMargin','topMargin','bottomMargin'], {'allowSplitting':'int','showBoundary':'bool','title':'str','author':'str'}))
656                 self.page_templates = []
657                 self.styles = doc.styles
658                 self.doc = doc
659                 pts = node.getElementsByTagName('pageTemplate')
660                 for pt in pts:
661                         frames = []
662                         for frame_el in pt.getElementsByTagName('frame'):
663                                 frame = platypus.Frame( **(utils.attr_get(frame_el, ['x1','y1', 'width','height', 'leftPadding', 'rightPadding', 'bottomPadding', 'topPadding'], {'id':'str', 'showBoundary':'bool'})) )
664                                 frames.append( frame )
665                         gr = pt.getElementsByTagName('pageGraphics')
666                         if len(gr):
667                                 drw = _rml_draw(gr[0], self.doc, images=images)
668                                 self.page_templates.append( platypus.PageTemplate(frames=frames, onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) ))
669                         else:
670                                 self.page_templates.append( platypus.PageTemplate(frames=frames, **utils.attr_get(pt, [], {'id':'str'}) ))
671                 self.doc_tmpl.addPageTemplates(self.page_templates)
672
673         def render(self, node_stories):
674                 fis = []
675                 r = _rml_flowable(self.doc,images=self.images, path=self.path)
676                 for node_story in node_stories:
677                         fis += r.render(node_story)
678                         fis.append(platypus.PageBreak())
679                 self.doc_tmpl.build(fis)
680
681 def parseString(data, fout=None, images={}, path='.'):
682         r = _rml_doc(data, images, path)
683         if fout:
684                 fp = file(fout,'wb')
685                 r.render(fp)
686                 fp.close()
687                 return fout
688         else:
689                 fp = StringIO.StringIO()
690                 r.render(fp)
691                 return fp.getvalue()
692
693 def trml2pdf_help():
694         print 'Usage: trml2pdf input.rml >output.pdf'
695         print 'Render the standard input (RML) and output a PDF file'
696         sys.exit(0)
697
698 if __name__=="__main__":
699         if len(sys.argv)>1:
700                 if sys.argv[1]=='--help':
701                         trml2pdf_help()
702                 print parseString(file(sys.argv[1], 'r').read()),
703         else:
704                 print 'Usage: trml2pdf input.rml >output.pdf'
705                 print 'Try \'trml2pdf --help\' for more information.'