1 ##############################################################################
3 # Copyright (c) 2004-2008 Tiny SPRL (http://tiny.be) All Rights Reserved.
7 # WARNING: This program as such is intended to be used by professional
8 # programmers who take the whole responsability of assessing all potential
9 # consequences resulting from its eventual inadequacies and bugs
10 # End users who are looking for a ready-to-use solution with commercial
11 # garantees and support are strongly adviced to contract a Free Software
14 # This program is Free Software; you can redistribute it and/or
15 # modify it under the terms of the GNU General Public License
16 # as published by the Free Software Foundation; either version 2
17 # of the License, or (at your option) any later version.
19 # This program is distributed in the hope that it will be useful,
20 # but WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
22 # GNU General Public License for more details.
24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
27 ###############################################################################
29 # -*- coding: utf-8 -*-
31 # trml2pdf - An RML to PDF converter
32 # Copyright (C) 2003, Fabien Pinckaers, UCL, FSA
34 # Richard Waid <richard@iopen.net>
36 # This library is free software; you can redistribute it and/or
37 # modify it under the terms of the GNU Lesser General Public
38 # License as published by the Free Software Foundation; either
39 # version 2.1 of the License, or (at your option) any later version.
41 # This library is distributed in the hope that it will be useful,
42 # but WITHOUT ANY WARRANTY; without even the implied warranty of
43 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
44 # Lesser General Public License for more details.
46 # You should have received a copy of the GNU Lesser General Public
47 # License along with this library; if not, write to the Free Software
48 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
52 import xml.dom.minidom
57 from reportlab.pdfgen import canvas
58 from reportlab import platypus
65 # Change this to UTF-8 if you plan tu use Reportlab's UTF-8 support
67 # reportlab use "code page 1252" encoding by default. cfr reportlab user guide p.46
71 return s.replace('&', '&').replace('<', '<').replace('>', '>')
73 def _child_get(node, childs):
75 for n in node.childNodes:
76 if (n.nodeType==n.ELEMENT_NODE) and (n.localName==childs):
80 class PageCount(platypus.Flowable):
82 self.canv.beginForm("pageCount")
83 self.canv.setFont("Helvetica", utils.unit_get(8)
84 self.canv.drawString(0, 0, str(self.canv.getPageNumber()))
87 class PageReset(platypus.Flowable):
89 self.canv._pageNumber = 0
91 class _rml_styles(object):
92 def __init__(self, nodes):
95 self.table_styles = {}
97 for style in node.getElementsByTagName('blockTableStyle'):
98 self.table_styles[style.getAttribute('id')] = self._table_style_get(style)
99 for style in node.getElementsByTagName('paraStyle'):
100 self.styles[style.getAttribute('name')] = self._para_style_update(style)
101 for variable in node.getElementsByTagName('initialize'):
102 for name in variable.getElementsByTagName('name'):
103 self.names[ name.getAttribute('id')] = name.getAttribute('value')
105 def _para_style_update(self, node):
107 for attr in ['textColor', 'backColor', 'bulletColor', 'borderColor']:
108 if node.hasAttribute(attr):
109 data[attr] = color.get(node.getAttribute(attr))
110 for attr in ['fontName', 'bulletFontName', 'bulletText']:
111 if node.hasAttribute(attr):
112 data[attr] = node.getAttribute(attr)
113 for attr in ['fontSize', 'leftIndent', 'rightIndent', 'spaceBefore', 'spaceAfter',
114 'firstLineIndent', 'bulletIndent', 'bulletFontSize', 'leading',
115 'borderWidth','borderPadding','borderRadius']:
116 if node.hasAttribute(attr):
117 data[attr] = utils.unit_get(node.getAttribute(attr))
118 if node.hasAttribute('alignment'):
120 'right':reportlab.lib.enums.TA_RIGHT,
121 'center':reportlab.lib.enums.TA_CENTER,
122 'justify':reportlab.lib.enums.TA_JUSTIFY
124 data['alignment'] = align.get(node.getAttribute('alignment').lower(), reportlab.lib.enums.TA_LEFT)
127 def _table_style_get(self, style_node):
129 for node in style_node.childNodes:
130 if node.nodeType==node.ELEMENT_NODE:
131 start = utils.tuple_int_get(node, 'start', (0,0) )
132 stop = utils.tuple_int_get(node, 'stop', (-1,-1) )
133 if node.localName=='blockValign':
134 styles.append(('VALIGN', start, stop, str(node.getAttribute('value'))))
135 elif node.localName=='blockFont':
136 styles.append(('FONT', start, stop, str(node.getAttribute('name'))))
137 elif node.localName=='blockTextColor':
138 styles.append(('TEXTCOLOR', start, stop, color.get(str(node.getAttribute('colorName')))))
139 elif node.localName=='blockLeading':
140 styles.append(('LEADING', start, stop, utils.unit_get(node.getAttribute('length'))))
141 elif node.localName=='blockAlignment':
142 styles.append(('ALIGNMENT', start, stop, str(node.getAttribute('value'))))
143 elif node.localName=='blockSpan':
144 styles.append(('SPAN', start, stop))
145 elif node.localName=='blockLeftPadding':
146 styles.append(('LEFTPADDING', start, stop, utils.unit_get(node.getAttribute('length'))))
147 elif node.localName=='blockRightPadding':
148 styles.append(('RIGHTPADDING', start, stop, utils.unit_get(node.getAttribute('length'))))
149 elif node.localName=='blockTopPadding':
150 styles.append(('TOPPADDING', start, stop, utils.unit_get(node.getAttribute('length'))))
151 elif node.localName=='blockBottomPadding':
152 styles.append(('BOTTOMPADDING', start, stop, utils.unit_get(node.getAttribute('length'))))
153 elif node.localName=='blockBackground':
154 styles.append(('BACKGROUND', start, stop, color.get(node.getAttribute('colorName'))))
155 if node.hasAttribute('size'):
156 styles.append(('FONTSIZE', start, stop, utils.unit_get(node.getAttribute('size'))))
157 elif node.localName=='lineStyle':
158 kind = node.getAttribute('kind')
159 kind_list = [ 'GRID', 'BOX', 'OUTLINE', 'INNERGRID', 'LINEBELOW', 'LINEABOVE','LINEBEFORE', 'LINEAFTER' ]
160 assert kind in kind_list
162 if node.hasAttribute('thickness'):
163 thick = float(node.getAttribute('thickness'))
164 styles.append((kind, start, stop, thick, color.get(node.getAttribute('colorName'))))
165 return platypus.tables.TableStyle(styles)
167 def para_style_get(self, node):
169 if node.hasAttribute('style'):
170 if node.getAttribute('style') in self.styles:
171 styles = reportlab.lib.styles.getSampleStyleSheet()
172 sname = node.getAttribute('style')
173 style = reportlab.lib.styles.ParagraphStyle(sname, styles["Normal"], **self.styles[sname])
175 sys.stderr.write('Warning: style not found, %s - setting default!\n' % (node.getAttribute('style'),) )
177 styles = reportlab.lib.styles.getSampleStyleSheet()
178 style = copy.deepcopy(styles['Normal'])
179 style.__dict__.update(self._para_style_update(node))
182 class _rml_doc(object):
183 def __init__(self, data, images={}, path='.'):
184 self.dom = xml.dom.minidom.parseString(data)
185 self.filename = self.dom.documentElement.getAttribute('filename')
189 def docinit(self, els):
190 from reportlab.lib.fonts import addMapping
191 from reportlab.pdfbase import pdfmetrics
192 from reportlab.pdfbase.ttfonts import TTFont
195 for font in node.getElementsByTagName('registerFont'):
196 name = font.getAttribute('fontName').encode('ascii')
197 fname = font.getAttribute('fontFile').encode('ascii')
198 pdfmetrics.registerFont(TTFont(name, fname ))
199 addMapping(name, 0, 0, name) #normal
200 addMapping(name, 0, 1, name) #italic
201 addMapping(name, 1, 0, name) #bold
202 addMapping(name, 1, 1, name) #italic and bold
204 def _textual_image(self, node):
207 for n in node.childNodes:
208 if n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
210 return base64.decodestring(rc)
212 def _images(self, el):
214 for node in el.getElementsByTagName('image'):
215 result[node.getAttribute('name')] = self._textual_image(node)
218 def render(self, out):
219 el = self.dom.documentElement.getElementsByTagName('docinit')
223 el = self.dom.documentElement.getElementsByTagName('stylesheet')
224 self.styles = _rml_styles(el)
226 el = self.dom.documentElement.getElementsByTagName('images')
228 self.images.update( self._images(el[0]) )
230 el = self.dom.documentElement.getElementsByTagName('template')
232 pt_obj = _rml_template(out, el[0], self, images=self.images, path=self.path)
233 pt_obj.render(self.dom.documentElement.getElementsByTagName('story'))
235 self.canvas = canvas.Canvas(out)
236 pd = self.dom.documentElement.getElementsByTagName('pageDrawing')[0]
237 pd_obj = _rml_canvas(self.canvas, None, self, self.images, path=self.path)
240 self.canvas.showPage()
243 class _rml_canvas(object):
244 def __init__(self, canvas, doc_tmpl=None, doc=None, images={}, path='.'):
246 self.styles = doc.styles
247 self.doc_tmpl = doc_tmpl
252 def _textual(self, node, x=0, y=0):
254 for n in node.childNodes:
255 if n.nodeType == n.ELEMENT_NODE:
256 if n.localName == 'seq':
257 from reportlab.lib.sequencer import getSequencer
259 rc += str(seq.next(n.getAttribute('id')))
260 if n.localName == 'pageCount':
262 self.canvas.translate(x,y)
263 self.canvas.doForm('pageCount')
265 self.canvas.translate(-x,-y)
266 if n.localName == 'pageNumber':
267 rc += str(self.canvas.getPageNumber())
268 elif n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
269 # this doesn't need to be "entities" encoded like flowables need to
271 return rc.encode(encoding, 'replace')
273 def _drawString(self, node):
274 v = utils.attr_get(node, ['x','y'])
275 self.canvas.drawString(text=self._textual(node, **v), **v)
276 def _drawCenteredString(self, node):
277 v = utils.attr_get(node, ['x','y'])
278 self.canvas.drawCentredString(text=self._textual(node, **v), **v)
279 def _drawRightString(self, node):
280 v = utils.attr_get(node, ['x','y'])
281 self.canvas.drawRightString(text=self._textual(node, **v), **v)
282 def _rect(self, node):
283 if node.hasAttribute('round'):
284 self.canvas.roundRect(radius=utils.unit_get(node.getAttribute('round')), **utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
286 self.canvas.rect(**utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
288 def _ellipse(self, node):
289 x1 = utils.unit_get(node.getAttribute('x'))
290 x2 = utils.unit_get(node.getAttribute('width'))
291 y1 = utils.unit_get(node.getAttribute('y'))
292 y2 = utils.unit_get(node.getAttribute('height'))
293 self.canvas.ellipse(x1,y1,x2,y2, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
294 def _curves(self, node):
295 line_str = utils.text_get(node).split()
297 while len(line_str)>7:
298 self.canvas.bezier(*[utils.unit_get(l) for l in line_str[0:8]])
299 line_str = line_str[8:]
300 def _lines(self, node):
301 line_str = utils.text_get(node).split()
303 while len(line_str)>3:
304 lines.append([utils.unit_get(l) for l in line_str[0:4]])
305 line_str = line_str[4:]
306 self.canvas.lines(lines)
307 def _grid(self, node):
308 xlist = [utils.unit_get(s) for s in node.getAttribute('xs').split(',')]
309 ylist = [utils.unit_get(s) for s in node.getAttribute('ys').split(',')]
310 self.canvas.grid(xlist, ylist)
311 def _translate(self, node):
314 if node.hasAttribute('dx'):
315 dx = utils.unit_get(node.getAttribute('dx'))
316 if node.hasAttribute('dy'):
317 dy = utils.unit_get(node.getAttribute('dy'))
318 self.canvas.translate(dx,dy)
320 def _circle(self, node):
321 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'}))
323 def _place(self, node):
324 flows = _rml_flowable(self.doc, images=self.images, path=self.path).render(node)
325 infos = utils.attr_get(node, ['x','y','width','height'])
327 infos['y']+=infos['height']
329 w,h = flow.wrap(infos['width'], infos['height'])
330 if w<=infos['width'] and h<=infos['height']:
332 flow.drawOn(self.canvas,infos['x'],infos['y'])
335 raise ValueError, "Not enough space"
337 def _line_mode(self, node):
338 ljoin = {'round':1, 'mitered':0, 'bevelled':2}
339 lcap = {'default':0, 'round':1, 'square':2}
340 if node.hasAttribute('width'):
341 self.canvas.setLineWidth(utils.unit_get(node.getAttribute('width')))
342 if node.hasAttribute('join'):
343 self.canvas.setLineJoin(ljoin[node.getAttribute('join')])
344 if node.hasAttribute('cap'):
345 self.canvas.setLineCap(lcap[node.getAttribute('cap')])
346 if node.hasAttribute('miterLimit'):
347 self.canvas.setDash(utils.unit_get(node.getAttribute('miterLimit')))
348 if node.hasAttribute('dash'):
349 dashes = node.getAttribute('dash').split(',')
350 for x in range(len(dashes)):
351 dashes[x]=utils.unit_get(dashes[x])
352 self.canvas.setDash(node.getAttribute('dash').split(','))
354 def _image(self, node):
356 from reportlab.lib.utils import ImageReader
358 if not node.hasAttribute('file'):
360 if node.hasAttribute('name'):
361 image_data = self.images[node.getAttribute('name')]
362 s = StringIO.StringIO(image_data)
365 image_data = base64.decodestring(node.firstChild.nodeValue)
366 if not image_data: return False
367 s = StringIO.StringIO(image_data)
369 if node.getAttribute('file') in self.images:
370 s = StringIO.StringIO(self.images[node.getAttribute('file')])
373 u = urllib.urlopen(str(node.getAttribute('file')))
374 s = StringIO.StringIO(u.read())
376 u = file(os.path.join(self.path,str(node.getAttribute('file'))), 'rb')
377 s = StringIO.StringIO(u.read())
379 (sx,sy) = img.getSize()
382 for tag in ('width','height','x','y'):
383 if node.hasAttribute(tag):
384 args[tag] = utils.unit_get(node.getAttribute(tag))
385 if ('width' in args) and (not 'height' in args):
386 args['height'] = sy * args['width'] / sx
387 elif ('height' in args) and (not 'width' in args):
388 args['width'] = sx * args['height'] / sy
389 elif ('width' in args) and ('height' in args):
390 if (float(args['width'])/args['height'])>(float(sx)>sy):
391 args['width'] = sx * args['height'] / sy
393 args['height'] = sy * args['width'] / sx
394 self.canvas.drawImage(img, **args)
396 def _path(self, node):
397 self.path = self.canvas.beginPath()
398 self.path.moveTo(**utils.attr_get(node, ['x','y']))
399 for n in node.childNodes:
400 if n.nodeType == node.ELEMENT_NODE:
401 if n.localName=='moveto':
402 vals = utils.text_get(n).split()
403 self.path.moveTo(utils.unit_get(vals[0]), utils.unit_get(vals[1]))
404 elif n.localName=='curvesto':
405 vals = utils.text_get(n).split()
409 pos.append(utils.unit_get(vals.pop(0)))
410 self.path.curveTo(*pos)
411 elif (n.nodeType == node.TEXT_NODE):
412 data = n.data.split() # Not sure if I must merge all TEXT_NODE ?
414 x = utils.unit_get(data.pop(0))
415 y = utils.unit_get(data.pop(0))
416 self.path.lineTo(x,y)
417 if (not node.hasAttribute('close')) or utils.bool_get(node.getAttribute('close')):
419 self.canvas.drawPath(self.path, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
421 def render(self, node):
423 'drawCentredString': self._drawCenteredString,
424 'drawRightString': self._drawRightString,
425 'drawString': self._drawString,
427 'ellipse': self._ellipse,
428 'lines': self._lines,
430 'curves': self._curves,
431 'fill': lambda node: self.canvas.setFillColor(color.get(node.getAttribute('color'))),
432 'stroke': lambda node: self.canvas.setStrokeColor(color.get(node.getAttribute('color'))),
433 'setFont': lambda node: self.canvas.setFont(node.getAttribute('name'), utils.unit_get(node.getAttribute('size'))),
434 'place': self._place,
435 'circle': self._circle,
436 'lineMode': self._line_mode,
438 'rotate': lambda node: self.canvas.rotate(float(node.getAttribute('degrees'))),
439 'translate': self._translate,
442 for nd in node.childNodes:
443 if nd.nodeType==nd.ELEMENT_NODE:
445 if nd.localName==tag:
449 class _rml_draw(object):
450 def __init__(self, node, styles, images={}, path='.'):
457 def render(self, canvas, doc):
459 cnv = _rml_canvas(canvas, doc, self.styles, images=self.images, path=self.path)
460 cnv.render(self.node)
461 canvas.restoreState()
463 class _rml_flowable(object):
464 def __init__(self, doc, images={}, path='.'):
466 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.Table(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)
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)
644 image = StringIO.StringIO(image_data)
645 return platypus.Image(image, mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
647 return platypus.Image(node.getAttribute('file'), mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
649 from reportlab.lib.utils import ImageReader
650 name = str(node.getAttribute('file'))
651 img = ImageReader(name)
652 (sx,sy) = img.getSize()
655 for tag in ('width','height'):
656 if node.hasAttribute(tag):
657 args[tag] = utils.unit_get(node.getAttribute(tag))
658 if ('width' in args) and (not 'height' in args):
659 args['height'] = sy * args['width'] / sx
660 elif ('height' in args) and (not 'width' in args):
661 args['width'] = sx * args['height'] / sy
662 elif ('width' in args) and ('height' in args):
663 if (float(args['width'])/args['height'])>(float(sx)>sy):
664 args['width'] = sx * args['height'] / sy
666 args['height'] = sy * args['width'] / sx
667 return platypus.Image(name, mask=(250,255,250,255,250,255), **args)
668 elif node.localName=='spacer':
669 if node.hasAttribute('width'):
670 width = utils.unit_get(node.getAttribute('width'))
672 width = utils.unit_get('1cm')
673 length = utils.unit_get(node.getAttribute('length'))
674 return platypus.Spacer(width=width, height=length)
675 elif node.localName=='section':
676 return self.render(node)
677 elif node.localName == 'pageNumberReset':
679 elif node.localName in ('pageBreak', 'nextPage'):
680 return platypus.PageBreak()
681 elif node.localName=='condPageBreak':
682 return platypus.CondPageBreak(**(utils.attr_get(node, ['height'])))
683 elif node.localName=='setNextTemplate':
684 return platypus.NextPageTemplate(str(node.getAttribute('name')))
685 elif node.localName=='nextFrame':
686 return platypus.CondPageBreak(1000) # TODO: change the 1000 !
687 elif node.localName == 'setNextFrame':
688 from reportlab.platypus.doctemplate import NextFrameFlowable
689 return NextFrameFlowable(str(node.getAttribute('name')))
690 elif node.localName == 'currentFrame':
691 from reportlab.platypus.doctemplate import CurrentFrameFlowable
692 return CurrentFrameFlowable(str(node.getAttribute('name')))
693 elif node.localName == 'frameEnd':
694 return EndFrameFlowable()
696 sys.stderr.write('Warning: flowable not yet implemented: %s !\n' % (node.localName,))
699 def render(self, node_story):
701 node = node_story.firstChild
703 if node.nodeType == node.ELEMENT_NODE:
704 flow = self._flowable(node)
706 if type(flow) == type([]):
710 node = node.nextSibling
713 from reportlab.platypus.doctemplate import ActionFlowable
715 class EndFrameFlowable(ActionFlowable):
716 def __init__(self,resume=0):
717 ActionFlowable.__init__(self,('frameEnd',resume))
719 class TinyDocTemplate(platypus.BaseDocTemplate):
720 def ___handle_pageBegin(self):
721 self.page = self.page + 1
722 self.pageTemplate.beforeDrawPage(self.canv,self)
723 self.pageTemplate.checkPageSize(self.canv,self)
724 self.pageTemplate.onPage(self.canv,self)
725 for f in self.pageTemplate.frames: f._reset()
727 #keep a count of flowables added to this page. zero indicates bad stuff
728 self._curPageFlowableCount = 0
729 if hasattr(self,'_nextFrameIndex'):
730 del self._nextFrameIndex
731 for f in self.pageTemplate.frames:
735 self.handle_frameBegin()
736 def afterFlowable(self, flowable):
737 if isinstance(flowable, PageReset):
738 self.canv._pageNumber = 0
740 class _rml_template(object):
741 def __init__(self, out, node, doc, images={}, path='.'):
744 if not node.hasAttribute('pageSize'):
745 pageSize = (utils.unit_get('21cm'), utils.unit_get('29.7cm'))
747 ps = map(lambda x:x.strip(), node.getAttribute('pageSize').replace(')', '').replace('(', '').split(','))
748 pageSize = ( utils.unit_get(ps[0]),utils.unit_get(ps[1]) )
749 cm = reportlab.lib.units.cm
750 self.doc_tmpl = TinyDocTemplate(out, pagesize=pageSize, **utils.attr_get(node, ['leftMargin','rightMargin','topMargin','bottomMargin'], {'allowSplitting':'int','showBoundary':'bool','title':'str','author':'str'}))
751 self.page_templates = []
752 self.styles = doc.styles
754 pts = node.getElementsByTagName('pageTemplate')
757 for frame_el in pt.getElementsByTagName('frame'):
758 frame = platypus.Frame( **(utils.attr_get(frame_el, ['x1','y1', 'width','height', 'leftPadding', 'rightPadding', 'bottomPadding', 'topPadding'], {'id':'str', 'showBoundary':'bool'})) )
759 if utils.attr_get(frame_el, ['last']):
760 frame.lastFrame = True
761 frames.append( frame )
762 gr = pt.getElementsByTagName('pageGraphics')
764 drw = _rml_draw(gr[0], self.doc, images=images, path=self.path)
765 self.page_templates.append( platypus.PageTemplate(frames=frames, onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) ))
767 self.page_templates.append( platypus.PageTemplate(frames=frames, **utils.attr_get(pt, [], {'id':'str'}) ))
768 self.doc_tmpl.addPageTemplates(self.page_templates)
770 def render(self, node_stories):
772 r = _rml_flowable(self.doc,images=self.images, path=self.path)
773 for node_story in node_stories:
774 fis += r.render(node_story)
775 if node_story==node_stories[-1]:
777 fis.append(PageCount())
779 fis.append(platypus.PageBreak())
781 self.doc_tmpl.build(fis)
783 def parseString(data, fout=None, images={}, path='.'):
784 r = _rml_doc(data, images, path)
791 fp = StringIO.StringIO()
796 print 'Usage: trml2pdf input.rml >output.pdf'
797 print 'Render the standard input (RML) and output a PDF file'
800 if __name__=="__main__":
802 if sys.argv[1]=='--help':
804 print parseString(file(sys.argv[1], 'r').read()),
806 print 'Usage: trml2pdf input.rml >output.pdf'
807 print 'Try \'trml2pdf --help\' for more information.'