1 # -*- encoding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
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.
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.
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/>.
21 ##############################################################################
23 # trml2pdf - An RML to PDF converter
24 # Copyright (C) 2003, Fabien Pinckaers, UCL, FSA
26 # Richard Waid <richard@iopen.net>
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.
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.
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
43 from StringIO import StringIO
44 import xml.dom.minidom
49 from reportlab.pdfgen import canvas
50 from reportlab import platypus
57 # Change this to UTF-8 if you plan tu use Reportlab's UTF-8 support
59 # reportlab use "code page 1252" encoding by default. cfr reportlab user guide p.46
63 return s.replace('&', '&').replace('<', '<').replace('>', '>')
65 def _child_get(node, childs):
67 for n in node.childNodes:
68 if (n.nodeType==n.ELEMENT_NODE) and (n.localName==childs):
72 class PageCount(platypus.Flowable):
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()))
79 class PageReset(platypus.Flowable):
81 self.canv._pageNumber = 0
83 class _rml_styles(object):
84 def __init__(self, nodes):
87 self.table_styles = {}
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')
97 def _para_style_update(self, node):
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'):
112 'right':reportlab.lib.enums.TA_RIGHT,
113 'center':reportlab.lib.enums.TA_CENTER,
114 'justify':reportlab.lib.enums.TA_JUSTIFY
116 data['alignment'] = align.get(node.getAttribute('alignment').lower(), reportlab.lib.enums.TA_LEFT)
119 def _table_style_get(self, style_node):
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
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)
159 def para_style_get(self, node):
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])
167 sys.stderr.write('Warning: style not found, %s - setting default!\n' % (node.getAttribute('style'),) )
169 styles = reportlab.lib.styles.getSampleStyleSheet()
170 style = copy.deepcopy(styles['Normal'])
171 style.__dict__.update(self._para_style_update(node))
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')
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
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
197 def _textual_image(self, node):
200 for n in node.childNodes:
201 if n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
203 return base64.decodestring(rc)
205 def _images(self, el):
207 for node in el.getElementsByTagName('image'):
208 result[node.getAttribute('name')] = self._textual_image(node)
211 def render(self, out):
212 el = self.dom.documentElement.getElementsByTagName('docinit')
216 el = self.dom.documentElement.getElementsByTagName('stylesheet')
217 self.styles = _rml_styles(el)
219 el = self.dom.documentElement.getElementsByTagName('images')
221 self.images.update( self._images(el[0]) )
223 el = self.dom.documentElement.getElementsByTagName('template')
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'))
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)
233 self.canvas.showPage()
236 class _rml_canvas(object):
237 def __init__(self, canvas, doc_tmpl=None, doc=None, images={}, path='.', title=None):
239 self.styles = doc.styles
240 self.doc_tmpl = doc_tmpl
246 self.canvas.setTitle(self.title)
248 def _textual(self, node, x=0, y=0):
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
255 rc += str(seq.next(n.getAttribute('id')))
256 if n.localName == 'pageCount':
258 self.canvas.translate(x,y)
259 self.canvas.doForm('pageCount')
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
267 return rc.encode(encoding, 'replace')
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'}))
282 self.canvas.rect(**utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
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()
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()
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):
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)
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'}))
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'])
323 infos['y']+=infos['height']
325 w,h = flow.wrap(infos['width'], infos['height'])
326 if w<=infos['width'] and h<=infos['height']:
328 flow.drawOn(self.canvas,infos['x'],infos['y'])
331 raise ValueError, "Not enough space"
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(','))
350 def _image(self, node):
352 from reportlab.lib.utils import ImageReader
355 if not node.hasAttribute('file'):
357 if node.hasAttribute('name'):
358 image_data = self.images[node.getAttribute('name')]
359 s = cStringIO.StringIO(image_data)
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)
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')])
372 u = urllib.urlopen(str(node.getAttribute('file')))
374 u = file(os.path.join(self.path,str(node.getAttribute('file'))), 'rb')
375 s = cStringIO.StringIO(u.read())
377 (sx,sy) = img.getSize()
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
391 args['height'] = sy * args['width'] / sx
392 self.canvas.drawImage(img, **args)
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()
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 ?
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')):
417 self.canvas.drawPath(self.path, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
419 def render(self, node):
421 'drawCentredString': self._drawCenteredString,
422 'drawRightString': self._drawRightString,
423 'drawString': self._drawString,
425 'ellipse': self._ellipse,
426 'lines': self._lines,
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,
436 'rotate': lambda node: self.canvas.rotate(float(node.getAttribute('degrees'))),
437 'translate': self._translate,
440 for nd in node.childNodes:
441 if nd.nodeType==nd.ELEMENT_NODE:
443 if nd.localName==tag:
447 class _rml_draw(object):
448 def __init__(self, node, styles, images={}, path='.', title=None):
454 self.canvas_title = title
456 def render(self, canvas, doc):
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()
462 class _rml_flowable(object):
463 def __init__(self, doc, images={}, path='.', title=None):
465 self.styles = doc.styles
470 def _textual(self, node):
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)
479 elif n.localName == 'pageNumber':
480 rc += '<pageNumber/>' # TODO: change this !
482 #CHECKME: I wonder if this is useful since we don't stock the result. Maybe for the getName tag?
485 elif n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
486 rc += str2xml(n.data)
487 return rc.encode(encoding, 'replace')
489 def _table(self, node):
494 childs = _child_get(node,'tr')
501 if tr.hasAttribute('style'):
502 st = copy.deepcopy(self.styles.table_styles[tr.getAttribute('style')])
507 if tr.hasAttribute('paraStyle'):
508 paraStyle = self.styles.styles[tr.getAttribute('paraStyle')]
512 for td in _child_get(tr, 'td'):
513 if td.hasAttribute('style'):
514 st = copy.deepcopy(self.styles.table_styles[td.getAttribute('style')])
521 if td.hasAttribute('paraStyle'):
523 paraStyle = self.styles.styles[td.getAttribute('paraStyle')]
527 for n in td.childNodes:
528 if n.nodeType==node.ELEMENT_NODE:
529 fl = self._flowable(n, extra_style=paraStyle)
532 flow = self._textual(td)
534 if len(data2)>length:
537 while len(ab)<length:
539 while len(data2)<length:
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')])
557 def _illustration(self, node):
558 class Illustration(platypus.flowables.Flowable):
559 def __init__(self, node, styles, self2):
562 self.width = utils.unit_get(node.getAttribute('width'))
563 self.height = utils.unit_get(node.getAttribute('height'))
565 def wrap(self, *args):
566 return (self.width, self.height)
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)
573 def _textual_image(self, node):
576 for n in node.childNodes:
577 if n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
579 return base64.decodestring(rc)
581 def _flowable(self, node, extra_style=None):
582 if node.localName=='para':
583 style = self.styles.para_style_get(node)
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':
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
596 print 'Warning: Reportlab barcode extension not installed !'
598 args = utils.attr_get(node, [], {'ratio':'float','xdim':'unit','height':'unit','checksum':'bool','quiet':'bool'})
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),
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')
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()
643 image_data = base64.decodestring(node.firstChild.nodeValue)
645 image.write(image_data)
646 return platypus.Image(image, mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
648 return platypus.Image(node.getAttribute('file'), mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
650 from reportlab.lib.utils import ImageReader
651 name = str(node.getAttribute('file'))
652 img = ImageReader(name)
653 (sx,sy) = img.getSize()
656 for tag in ('width','height'):
657 if node.hasAttribute(tag):
658 args[tag] = utils.unit_get(node.getAttribute(tag))
659 if ('width' in args) and (not 'height' in args):
660 args['height'] = sy * args['width'] / sx
661 elif ('height' in args) and (not 'width' in args):
662 args['width'] = sx * args['height'] / sy
663 elif ('width' in args) and ('height' in args):
664 if (float(args['width'])/args['height'])>(float(sx)>sy):
665 args['width'] = sx * args['height'] / sy
667 args['height'] = sy * args['width'] / sx
668 return platypus.Image(name, mask=(250,255,250,255,250,255), **args)
669 elif node.localName=='spacer':
670 if node.hasAttribute('width'):
671 width = utils.unit_get(node.getAttribute('width'))
673 width = utils.unit_get('1cm')
674 length = utils.unit_get(node.getAttribute('length'))
675 return platypus.Spacer(width=width, height=length)
676 elif node.localName=='section':
677 return self.render(node)
678 elif node.localName == 'pageNumberReset':
680 elif node.localName in ('pageBreak', 'nextPage'):
681 return platypus.PageBreak()
682 elif node.localName=='condPageBreak':
683 return platypus.CondPageBreak(**(utils.attr_get(node, ['height'])))
684 elif node.localName=='setNextTemplate':
685 return platypus.NextPageTemplate(str(node.getAttribute('name')))
686 elif node.localName=='nextFrame':
687 return platypus.CondPageBreak(1000) # TODO: change the 1000 !
688 elif node.localName == 'setNextFrame':
689 from reportlab.platypus.doctemplate import NextFrameFlowable
690 return NextFrameFlowable(str(node.getAttribute('name')))
691 elif node.localName == 'currentFrame':
692 from reportlab.platypus.doctemplate import CurrentFrameFlowable
693 return CurrentFrameFlowable(str(node.getAttribute('name')))
694 elif node.localName == 'frameEnd':
695 return EndFrameFlowable()
696 elif node.localName == 'hr':
697 width_hr=node.hasAttribute('width') and node.getAttribute('width') or '100%'
698 color_hr=node.hasAttribute('color') and node.getAttribute('color') or 'black'
699 thickness_hr=node.hasAttribute('thickness') and node.getAttribute('thickness') or 1
700 lineCap_hr=node.hasAttribute('lineCap') and node.getAttribute('lineCap') or 'round'
701 return platypus.flowables.HRFlowable(width=width_hr,color=color.get(color_hr),thickness=float(thickness_hr),lineCap=str(lineCap_hr))
703 sys.stderr.write('Warning: flowable not yet implemented: %s !\n' % (node.localName,))
706 def render(self, node_story):
708 node = node_story.firstChild
710 if node.nodeType == node.ELEMENT_NODE:
711 flow = self._flowable(node)
713 if type(flow) == type([]):
717 node = node.nextSibling
720 from reportlab.platypus.doctemplate import ActionFlowable
722 class EndFrameFlowable(ActionFlowable):
723 def __init__(self,resume=0):
724 ActionFlowable.__init__(self,('frameEnd',resume))
726 class TinyDocTemplate(platypus.BaseDocTemplate):
727 def ___handle_pageBegin(self):
728 self.page = self.page + 1
729 self.pageTemplate.beforeDrawPage(self.canv,self)
730 self.pageTemplate.checkPageSize(self.canv,self)
731 self.pageTemplate.onPage(self.canv,self)
732 for f in self.pageTemplate.frames: f._reset()
734 #keep a count of flowables added to this page. zero indicates bad stuff
735 self._curPageFlowableCount = 0
736 if hasattr(self,'_nextFrameIndex'):
737 del self._nextFrameIndex
738 for f in self.pageTemplate.frames:
742 self.handle_frameBegin()
743 def afterFlowable(self, flowable):
744 if isinstance(flowable, PageReset):
745 self.canv._pageNumber = 0
747 class _rml_template(object):
748 def __init__(self, out, node, doc, images={}, path='.', title=None):
752 if not node.hasAttribute('pageSize'):
753 pageSize = (utils.unit_get('21cm'), utils.unit_get('29.7cm'))
755 ps = map(lambda x:x.strip(), node.getAttribute('pageSize').replace(')', '').replace('(', '').split(','))
756 pageSize = ( utils.unit_get(ps[0]),utils.unit_get(ps[1]) )
757 cm = reportlab.lib.units.cm
758 self.doc_tmpl = TinyDocTemplate(out, pagesize=pageSize, **utils.attr_get(node, ['leftMargin','rightMargin','topMargin','bottomMargin'], {'allowSplitting':'int','showBoundary':'bool','title':'str','author':'str'}))
759 self.page_templates = []
760 self.styles = doc.styles
762 pts = node.getElementsByTagName('pageTemplate')
765 for frame_el in pt.getElementsByTagName('frame'):
766 frame = platypus.Frame( **(utils.attr_get(frame_el, ['x1','y1', 'width','height', 'leftPadding', 'rightPadding', 'bottomPadding', 'topPadding'], {'id':'str', 'showBoundary':'bool'})) )
767 if utils.attr_get(frame_el, ['last']):
768 frame.lastFrame = True
769 frames.append( frame )
770 gr = pt.getElementsByTagName('pageGraphics')
772 drw = _rml_draw(gr[0], self.doc, images=images, path=self.path, title=self.title)
773 self.page_templates.append( platypus.PageTemplate(frames=frames, onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) ))
775 drw = _rml_draw(node,self.doc,title=self.title)
776 self.page_templates.append( platypus.PageTemplate(frames=frames,onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) ))
777 self.doc_tmpl.addPageTemplates(self.page_templates)
779 def render(self, node_stories):
781 r = _rml_flowable(self.doc,images=self.images, path=self.path, title=self.title)
782 for node_story in node_stories:
783 fis += r.render(node_story)
784 if node_story==node_stories[-1]:
786 fis.append(PageCount())
788 fis.append(platypus.PageBreak())
790 self.doc_tmpl.build(fis)
792 def parseString(data, fout=None, images={}, path='.',title=None):
793 r = _rml_doc(data, images, path, title=title)
805 print 'Usage: trml2pdf input.rml >output.pdf'
806 print 'Render the standard input (RML) and output a PDF file'
809 if __name__=="__main__":
811 if sys.argv[1]=='--help':
813 print parseString(file(sys.argv[1], 'r').read()),
815 print 'Usage: trml2pdf input.rml >output.pdf'
816 print 'Try \'trml2pdf --help\' for more information.'
818 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: