1 # -*- encoding: utf-8 -*-
2 ##############################################################################
4 # Copyright (c) 2004-2008 Tiny SPRL (http://tiny.be) All Rights Reserved.
8 # WARNING: This program as such is intended to be used by professional
9 # programmers who take the whole responsability of assessing all potential
10 # consequences resulting from its eventual inadequacies and bugs
11 # End users who are looking for a ready-to-use solution with commercial
12 # garantees and support are strongly adviced to contract a Free Software
15 # This program is Free Software; you can redistribute it and/or
16 # modify it under the terms of the GNU General Public License
17 # as published by the Free Software Foundation; either version 2
18 # of the License, or (at your option) any later version.
20 # This program is distributed in the hope that it will be useful,
21 # but WITHOUT ANY WARRANTY; without even the implied warranty of
22 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23 # GNU General Public License for more details.
25 # You should have received a copy of the GNU General Public License
26 # along with this program; if not, write to the Free Software
27 # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
28 ###############################################################################
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(str(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
253 self.canvas.setTitle(ftitle)
255 def _textual(self, node, x=0, y=0):
257 for n in node.childNodes:
258 if n.nodeType == n.ELEMENT_NODE:
259 if n.localName == 'seq':
260 from reportlab.lib.sequencer import getSequencer
262 rc += str(seq.next(n.getAttribute('id')))
263 if n.localName == 'pageCount':
265 self.canvas.translate(x,y)
266 self.canvas.doForm('pageCount')
268 self.canvas.translate(-x,-y)
269 if n.localName == 'pageNumber':
270 rc += str(self.canvas.getPageNumber())
271 elif n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
272 # this doesn't need to be "entities" encoded like flowables need to
274 return rc.encode(encoding, 'replace')
276 def _drawString(self, node):
277 v = utils.attr_get(node, ['x','y'])
278 self.canvas.drawString(text=self._textual(node, **v), **v)
279 def _drawCenteredString(self, node):
280 v = utils.attr_get(node, ['x','y'])
281 self.canvas.drawCentredString(text=self._textual(node, **v), **v)
282 def _drawRightString(self, node):
283 v = utils.attr_get(node, ['x','y'])
284 self.canvas.drawRightString(text=self._textual(node, **v), **v)
285 def _rect(self, node):
286 if node.hasAttribute('round'):
287 self.canvas.roundRect(radius=utils.unit_get(node.getAttribute('round')), **utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
289 self.canvas.rect(**utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
291 def _ellipse(self, node):
292 x1 = utils.unit_get(node.getAttribute('x'))
293 x2 = utils.unit_get(node.getAttribute('width'))
294 y1 = utils.unit_get(node.getAttribute('y'))
295 y2 = utils.unit_get(node.getAttribute('height'))
296 self.canvas.ellipse(x1,y1,x2,y2, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
297 def _curves(self, node):
298 line_str = utils.text_get(node).split()
300 while len(line_str)>7:
301 self.canvas.bezier(*[utils.unit_get(l) for l in line_str[0:8]])
302 line_str = line_str[8:]
303 def _lines(self, node):
304 line_str = utils.text_get(node).split()
306 while len(line_str)>3:
307 lines.append([utils.unit_get(l) for l in line_str[0:4]])
308 line_str = line_str[4:]
309 self.canvas.lines(lines)
310 def _grid(self, node):
311 xlist = [utils.unit_get(s) for s in node.getAttribute('xs').split(',')]
312 ylist = [utils.unit_get(s) for s in node.getAttribute('ys').split(',')]
313 self.canvas.grid(xlist, ylist)
314 def _translate(self, node):
317 if node.hasAttribute('dx'):
318 dx = utils.unit_get(node.getAttribute('dx'))
319 if node.hasAttribute('dy'):
320 dy = utils.unit_get(node.getAttribute('dy'))
321 self.canvas.translate(dx,dy)
323 def _circle(self, node):
324 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'}))
326 def _place(self, node):
327 flows = _rml_flowable(self.doc, images=self.images, path=self.path).render(node)
328 infos = utils.attr_get(node, ['x','y','width','height'])
330 infos['y']+=infos['height']
332 w,h = flow.wrap(infos['width'], infos['height'])
333 if w<=infos['width'] and h<=infos['height']:
335 flow.drawOn(self.canvas,infos['x'],infos['y'])
338 raise ValueError, "Not enough space"
340 def _line_mode(self, node):
341 ljoin = {'round':1, 'mitered':0, 'bevelled':2}
342 lcap = {'default':0, 'round':1, 'square':2}
343 if node.hasAttribute('width'):
344 self.canvas.setLineWidth(utils.unit_get(node.getAttribute('width')))
345 if node.hasAttribute('join'):
346 self.canvas.setLineJoin(ljoin[node.getAttribute('join')])
347 if node.hasAttribute('cap'):
348 self.canvas.setLineCap(lcap[node.getAttribute('cap')])
349 if node.hasAttribute('miterLimit'):
350 self.canvas.setDash(utils.unit_get(node.getAttribute('miterLimit')))
351 if node.hasAttribute('dash'):
352 dashes = node.getAttribute('dash').split(',')
353 for x in range(len(dashes)):
354 dashes[x]=utils.unit_get(dashes[x])
355 self.canvas.setDash(node.getAttribute('dash').split(','))
357 def _image(self, node):
359 from reportlab.lib.utils import ImageReader
361 if not node.hasAttribute('file'):
363 if node.hasAttribute('name'):
364 image_data = self.images[node.getAttribute('name')]
365 s = StringIO.StringIO(image_data)
368 image_data = base64.decodestring(node.firstChild.nodeValue)
369 if not image_data: return False
370 s = StringIO.StringIO(image_data)
372 if node.getAttribute('file') in self.images:
373 s = StringIO.StringIO(self.images[node.getAttribute('file')])
376 u = urllib.urlopen(str(node.getAttribute('file')))
377 s = StringIO.StringIO(u.read())
379 u = file(os.path.join(self.path,str(node.getAttribute('file'))), 'rb')
380 s = StringIO.StringIO(u.read())
382 (sx,sy) = img.getSize()
385 for tag in ('width','height','x','y'):
386 if node.hasAttribute(tag):
387 args[tag] = utils.unit_get(node.getAttribute(tag))
388 if ('width' in args) and (not 'height' in args):
389 args['height'] = sy * args['width'] / sx
390 elif ('height' in args) and (not 'width' in args):
391 args['width'] = sx * args['height'] / sy
392 elif ('width' in args) and ('height' in args):
393 if (float(args['width'])/args['height'])>(float(sx)>sy):
394 args['width'] = sx * args['height'] / sy
396 args['height'] = sy * args['width'] / sx
397 self.canvas.drawImage(img, **args)
399 def _path(self, node):
400 self.path = self.canvas.beginPath()
401 self.path.moveTo(**utils.attr_get(node, ['x','y']))
402 for n in node.childNodes:
403 if n.nodeType == node.ELEMENT_NODE:
404 if n.localName=='moveto':
405 vals = utils.text_get(n).split()
406 self.path.moveTo(utils.unit_get(vals[0]), utils.unit_get(vals[1]))
407 elif n.localName=='curvesto':
408 vals = utils.text_get(n).split()
412 pos.append(utils.unit_get(vals.pop(0)))
413 self.path.curveTo(*pos)
414 elif (n.nodeType == node.TEXT_NODE):
415 data = n.data.split() # Not sure if I must merge all TEXT_NODE ?
417 x = utils.unit_get(data.pop(0))
418 y = utils.unit_get(data.pop(0))
419 self.path.lineTo(x,y)
420 if (not node.hasAttribute('close')) or utils.bool_get(node.getAttribute('close')):
422 self.canvas.drawPath(self.path, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
424 def render(self, node):
426 'drawCentredString': self._drawCenteredString,
427 'drawRightString': self._drawRightString,
428 'drawString': self._drawString,
430 'ellipse': self._ellipse,
431 'lines': self._lines,
433 'curves': self._curves,
434 'fill': lambda node: self.canvas.setFillColor(color.get(node.getAttribute('color'))),
435 'stroke': lambda node: self.canvas.setStrokeColor(color.get(node.getAttribute('color'))),
436 'setFont': lambda node: self.canvas.setFont(node.getAttribute('name'), utils.unit_get(node.getAttribute('size'))),
437 'place': self._place,
438 'circle': self._circle,
439 'lineMode': self._line_mode,
441 'rotate': lambda node: self.canvas.rotate(float(node.getAttribute('degrees'))),
442 'translate': self._translate,
445 for nd in node.childNodes:
446 if nd.nodeType==nd.ELEMENT_NODE:
448 if nd.localName==tag:
452 class _rml_draw(object):
453 def __init__(self, node, styles, images={}, path='.'):
460 def render(self, canvas, doc):
462 cnv = _rml_canvas(canvas, doc, self.styles, images=self.images, path=self.path)
463 cnv.render(self.node)
464 canvas.restoreState()
466 class _rml_flowable(object):
467 def __init__(self, doc, images={}, path='.'):
469 self.styles = doc.styles
473 def _textual(self, node):
475 for n in node.childNodes:
476 if n.nodeType == node.ELEMENT_NODE:
477 if n.localName == 'getName':
478 newNode = self.doc.dom.createTextNode(self.styles.names.get(n.getAttribute('id'),'Unknown name'))
479 node.insertBefore(newNode, n)
482 elif n.localName == 'pageNumber':
483 rc += '<pageNumber/>' # TODO: change this !
485 #CHECKME: I wonder if this is useful since we don't stock the result. Maybe for the getName tag?
488 elif n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
489 rc += str2xml(n.data)
490 return rc.encode(encoding, 'replace')
492 def _table(self, node):
497 childs = _child_get(node,'tr')
504 if tr.hasAttribute('style'):
505 st = copy.deepcopy(self.styles.table_styles[tr.getAttribute('style')])
510 if tr.hasAttribute('paraStyle'):
511 paraStyle = self.styles.styles[tr.getAttribute('paraStyle')]
515 for td in _child_get(tr, 'td'):
516 if td.hasAttribute('style'):
517 st = copy.deepcopy(self.styles.table_styles[td.getAttribute('style')])
524 if td.hasAttribute('paraStyle'):
526 paraStyle = self.styles.styles[td.getAttribute('paraStyle')]
530 for n in td.childNodes:
531 if n.nodeType==node.ELEMENT_NODE:
532 fl = self._flowable(n, extra_style=paraStyle)
535 flow = self._textual(td)
537 if len(data2)>length:
540 while len(ab)<length:
542 while len(data2)<length:
546 if node.hasAttribute('colWidths'):
547 assert length == len(node.getAttribute('colWidths').split(','))
548 colwidths = [utils.unit_get(f.strip()) for f in node.getAttribute('colWidths').split(',')]
549 if node.hasAttribute('rowHeights'):
550 rowheights = [utils.unit_get(f.strip()) for f in node.getAttribute('rowHeights').split(',')]
551 if len(rowheights) == 1:
552 rowheights = rowheights[0]
553 table = platypus.LongTable(data = data, colWidths=colwidths, rowHeights=rowheights, **(utils.attr_get(node, ['splitByRow'] ,{'repeatRows':'int','repeatCols':'int'})))
554 if node.hasAttribute('style'):
555 table.setStyle(self.styles.table_styles[node.getAttribute('style')])
560 def _illustration(self, node):
561 class Illustration(platypus.flowables.Flowable):
562 def __init__(self, node, styles, self2):
565 self.width = utils.unit_get(node.getAttribute('width'))
566 self.height = utils.unit_get(node.getAttribute('height'))
568 def wrap(self, *args):
569 return (self.width, self.height)
572 drw = _rml_draw(self.node, self.styles, images=self.self2.images, path=self.self2.path)
573 drw.render(self.canv, None)
574 return Illustration(node, self.styles, self)
576 def _textual_image(self, node):
579 for n in node.childNodes:
580 if n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
582 return base64.decodestring(rc)
584 def _flowable(self, node, extra_style=None):
585 if node.localName=='para':
586 style = self.styles.para_style_get(node)
588 style.__dict__.update(extra_style)
589 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
590 elif node.localName=='barCode':
592 from reportlab.graphics.barcode import code128
593 from reportlab.graphics.barcode import code39
594 from reportlab.graphics.barcode import code93
595 from reportlab.graphics.barcode import common
596 from reportlab.graphics.barcode import fourstate
597 from reportlab.graphics.barcode import usps
599 print 'Warning: Reportlab barcode extension not installed !'
601 args = utils.attr_get(node, [], {'ratio':'float','xdim':'unit','height':'unit','checksum':'bool','quiet':'bool'})
603 'codabar': lambda x: common.Codabar(x, **args),
604 'code11': lambda x: common.Code11(x, **args),
605 'code128': lambda x: code128.Code128(x, **args),
606 'standard39': lambda x: code39.Standard39(x, **args),
607 'standard93': lambda x: code93.Standard93(x, **args),
608 'i2of5': lambda x: common.I2of5(x, **args),
609 'extended39': lambda x: code39.Extended39(x, **args),
610 'extended93': lambda x: code93.Extended93(x, **args),
611 'msi': lambda x: common.MSI(x, **args),
612 'fim': lambda x: usps.FIM(x, **args),
613 'postnet': lambda x: usps.POSTNET(x, **args),
616 if node.hasAttribute('code'):
617 code = node.getAttribute('code').lower()
618 return codes[code](self._textual(node))
619 elif node.localName=='name':
620 self.styles.names[ node.getAttribute('id')] = node.getAttribute('value')
622 elif node.localName=='xpre':
623 style = self.styles.para_style_get(node)
624 return platypus.XPreformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int','frags':'int'})))
625 elif node.localName=='pre':
626 style = self.styles.para_style_get(node)
627 return platypus.Preformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int'})))
628 elif node.localName=='illustration':
629 return self._illustration(node)
630 elif node.localName=='blockTable':
631 return self._table(node)
632 elif node.localName=='title':
633 styles = reportlab.lib.styles.getSampleStyleSheet()
634 style = styles['Title']
635 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
636 elif re.match('^h([1-9]+[0-9]*)$', node.localName):
637 styles = reportlab.lib.styles.getSampleStyleSheet()
638 style = styles['Heading'+str(node.localName[1:])]
639 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
640 elif node.localName=='image':
641 if not node.hasAttribute('file'):
642 if node.hasAttribute('name'):
643 image_data = self.doc.images[node.getAttribute('name')].read()
646 image_data = base64.decodestring(node.firstChild.nodeValue)
647 image = StringIO.StringIO(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()
699 sys.stderr.write('Warning: flowable not yet implemented: %s !\n' % (node.localName,))
702 def render(self, node_story):
704 node = node_story.firstChild
706 if node.nodeType == node.ELEMENT_NODE:
707 flow = self._flowable(node)
709 if type(flow) == type([]):
713 node = node.nextSibling
716 from reportlab.platypus.doctemplate import ActionFlowable
718 class EndFrameFlowable(ActionFlowable):
719 def __init__(self,resume=0):
720 ActionFlowable.__init__(self,('frameEnd',resume))
722 class TinyDocTemplate(platypus.BaseDocTemplate):
723 def ___handle_pageBegin(self):
724 self.page = self.page + 1
725 self.pageTemplate.beforeDrawPage(self.canv,self)
726 self.pageTemplate.checkPageSize(self.canv,self)
727 self.pageTemplate.onPage(self.canv,self)
728 for f in self.pageTemplate.frames: f._reset()
730 #keep a count of flowables added to this page. zero indicates bad stuff
731 self._curPageFlowableCount = 0
732 if hasattr(self,'_nextFrameIndex'):
733 del self._nextFrameIndex
734 for f in self.pageTemplate.frames:
738 self.handle_frameBegin()
739 def afterFlowable(self, flowable):
740 if isinstance(flowable, PageReset):
741 self.canv._pageNumber = 0
743 class _rml_template(object):
744 def __init__(self, out, node, doc, images={}, path='.'):
747 if not node.hasAttribute('pageSize'):
748 pageSize = (utils.unit_get('21cm'), utils.unit_get('29.7cm'))
750 ps = map(lambda x:x.strip(), node.getAttribute('pageSize').replace(')', '').replace('(', '').split(','))
751 pageSize = ( utils.unit_get(ps[0]),utils.unit_get(ps[1]) )
752 cm = reportlab.lib.units.cm
753 self.doc_tmpl = TinyDocTemplate(out, pagesize=pageSize, **utils.attr_get(node, ['leftMargin','rightMargin','topMargin','bottomMargin'], {'allowSplitting':'int','showBoundary':'bool','title':'str','author':'str'}))
754 self.page_templates = []
755 self.styles = doc.styles
757 pts = node.getElementsByTagName('pageTemplate')
760 for frame_el in pt.getElementsByTagName('frame'):
761 frame = platypus.Frame( **(utils.attr_get(frame_el, ['x1','y1', 'width','height', 'leftPadding', 'rightPadding', 'bottomPadding', 'topPadding'], {'id':'str', 'showBoundary':'bool'})) )
762 if utils.attr_get(frame_el, ['last']):
763 frame.lastFrame = True
764 frames.append( frame )
765 gr = pt.getElementsByTagName('pageGraphics')
767 drw = _rml_draw(gr[0], self.doc, images=images, path=self.path)
768 self.page_templates.append( platypus.PageTemplate(frames=frames, onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) ))
770 self.page_templates.append( platypus.PageTemplate(frames=frames, **utils.attr_get(pt, [], {'id':'str'}) ))
771 self.doc_tmpl.addPageTemplates(self.page_templates)
773 def render(self, node_stories):
775 r = _rml_flowable(self.doc,images=self.images, path=self.path)
776 for node_story in node_stories:
777 fis += r.render(node_story)
778 if node_story==node_stories[-1]:
780 fis.append(PageCount())
782 fis.append(platypus.PageBreak())
784 self.doc_tmpl.build(fis)
786 def parseString(data, fout=None, images={}, path='.',title=None):
787 r = _rml_doc(data, images, path)
796 fp = StringIO.StringIO()
801 print 'Usage: trml2pdf input.rml >output.pdf'
802 print 'Render the standard input (RML) and output a PDF file'
805 if __name__=="__main__":
807 if sys.argv[1]=='--help':
809 print parseString(file(sys.argv[1], 'r').read()),
811 print 'Usage: trml2pdf input.rml >output.pdf'
812 print 'Try \'trml2pdf --help\' for more information.'
814 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: