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