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