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)
646 image.write(image_data)
648 return platypus.Image(image, mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
650 return platypus.Image(node.getAttribute('file'), mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
652 from reportlab.lib.utils import ImageReader
653 name = str(node.getAttribute('file'))
654 img = ImageReader(name)
655 (sx,sy) = img.getSize()
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
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'))
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':
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))
705 sys.stderr.write('Warning: flowable not yet implemented: %s !\n' % (node.localName,))
708 def render(self, node_story):
710 node = node_story.firstChild
712 if node.nodeType == node.ELEMENT_NODE:
713 flow = self._flowable(node)
715 if type(flow) == type([]):
719 node = node.nextSibling
722 from reportlab.platypus.doctemplate import ActionFlowable
724 class EndFrameFlowable(ActionFlowable):
725 def __init__(self,resume=0):
726 ActionFlowable.__init__(self,('frameEnd',resume))
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()
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:
744 self.handle_frameBegin()
745 def afterFlowable(self, flowable):
746 if isinstance(flowable, PageReset):
747 self.canv._pageNumber = 0
749 class _rml_template(object):
750 def __init__(self, out, node, doc, images={}, path='.', title=None):
754 if not node.hasAttribute('pageSize'):
755 pageSize = (utils.unit_get('21cm'), utils.unit_get('29.7cm'))
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
764 pts = node.getElementsByTagName('pageTemplate')
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')
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'}) ))
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)
781 def render(self, node_stories):
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]:
788 fis.append(PageCount())
790 fis.append(platypus.PageBreak())
792 self.doc_tmpl.build(fis)
794 def parseString(data, fout=None, images={}, path='.',title=None):
795 r = _rml_doc(data, images, path, title=title)
807 print 'Usage: trml2pdf input.rml >output.pdf'
808 print 'Render the standard input (RML) and output a PDF file'
811 if __name__=="__main__":
813 if sys.argv[1]=='--help':
815 print parseString(file(sys.argv[1], 'r').read()),
817 print 'Usage: trml2pdf input.rml >output.pdf'
818 print 'Try \'trml2pdf --help\' for more information.'
820 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: