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
56 from reportlab.pdfgen import canvas
57 from reportlab import platypus
64 # Change this to UTF-8 if you plan tu use Reportlab's UTF-8 support
66 # reportlab use "code page 1252" encoding by default. cfr reportlab user guide p.46
70 return s.replace('&', '&').replace('<', '<').replace('>', '>')
72 def _child_get(node, childs):
74 for n in node.childNodes:
75 if (n.nodeType==n.ELEMENT_NODE) and (n.localName==childs):
79 class _rml_styles(object):
80 def __init__(self, nodes):
83 self.table_styles = {}
85 for style in node.getElementsByTagName('blockTableStyle'):
86 self.table_styles[style.getAttribute('id')] = self._table_style_get(style)
87 for style in node.getElementsByTagName('paraStyle'):
88 self.styles[style.getAttribute('name')] = self._para_style_get(style)
89 for variable in node.getElementsByTagName('initialize'):
90 for name in variable.getElementsByTagName('name'):
91 self.names[ name.getAttribute('id')] = name.getAttribute('value')
93 def _para_style_update(self, style, node):
94 for attr in ['textColor', 'backColor', 'bulletColor']:
95 if node.hasAttribute(attr):
96 style.__dict__[attr] = color.get(node.getAttribute(attr))
97 for attr in ['fontName', 'bulletFontName', 'bulletText']:
98 if node.hasAttribute(attr):
99 style.__dict__[attr] = node.getAttribute(attr)
100 for attr in ['fontSize', 'leftIndent', 'rightIndent', 'spaceBefore', 'spaceAfter', 'firstLineIndent', 'bulletIndent', 'bulletFontSize', 'leading']:
101 if node.hasAttribute(attr):
102 style.__dict__[attr] = utils.unit_get(node.getAttribute(attr))
103 if node.hasAttribute('alignment'):
105 'right':reportlab.lib.enums.TA_RIGHT,
106 'center':reportlab.lib.enums.TA_CENTER,
107 'justify':reportlab.lib.enums.TA_JUSTIFY
109 style.alignment = align.get(node.getAttribute('alignment').lower(), reportlab.lib.enums.TA_LEFT)
112 def _table_style_get(self, style_node):
114 for node in style_node.childNodes:
115 if node.nodeType==node.ELEMENT_NODE:
116 start = utils.tuple_int_get(node, 'start', (0,0) )
117 stop = utils.tuple_int_get(node, 'stop', (-1,-1) )
118 if node.localName=='blockValign':
119 styles.append(('VALIGN', start, stop, str(node.getAttribute('value'))))
120 elif node.localName=='blockFont':
121 styles.append(('FONT', start, stop, str(node.getAttribute('name'))))
122 elif node.localName=='blockTextColor':
123 styles.append(('TEXTCOLOR', start, stop, color.get(str(node.getAttribute('colorName')))))
124 elif node.localName=='blockLeading':
125 styles.append(('LEADING', start, stop, utils.unit_get(node.getAttribute('length'))))
126 elif node.localName=='blockAlignment':
127 styles.append(('ALIGNMENT', start, stop, str(node.getAttribute('value'))))
128 elif node.localName=='blockSpan':
129 styles.append(('SPAN', start, stop))
130 elif node.localName=='blockLeftPadding':
131 styles.append(('LEFTPADDING', start, stop, utils.unit_get(node.getAttribute('length'))))
132 elif node.localName=='blockRightPadding':
133 styles.append(('RIGHTPADDING', start, stop, utils.unit_get(node.getAttribute('length'))))
134 elif node.localName=='blockTopPadding':
135 styles.append(('TOPPADDING', start, stop, utils.unit_get(node.getAttribute('length'))))
136 elif node.localName=='blockBottomPadding':
137 styles.append(('BOTTOMPADDING', start, stop, utils.unit_get(node.getAttribute('length'))))
138 elif node.localName=='blockBackground':
139 styles.append(('BACKGROUND', start, stop, color.get(node.getAttribute('colorName'))))
140 if node.hasAttribute('size'):
141 styles.append(('FONTSIZE', start, stop, utils.unit_get(node.getAttribute('size'))))
142 elif node.localName=='lineStyle':
143 kind = node.getAttribute('kind')
144 kind_list = [ 'GRID', 'BOX', 'OUTLINE', 'INNERGRID', 'LINEBELOW', 'LINEABOVE','LINEBEFORE', 'LINEAFTER' ]
145 assert kind in kind_list
147 if node.hasAttribute('thickness'):
148 thick = float(node.getAttribute('thickness'))
149 styles.append((kind, start, stop, thick, color.get(node.getAttribute('colorName'))))
150 return platypus.tables.TableStyle(styles)
152 def _para_style_get(self, node):
153 styles = reportlab.lib.styles.getSampleStyleSheet()
154 style = copy.deepcopy(styles["Normal"])
155 self._para_style_update(style, node)
158 def para_style_get(self, node):
160 if node.hasAttribute('style'):
161 if node.getAttribute('style') in self.styles:
162 style = copy.deepcopy(self.styles[node.getAttribute('style')])
164 sys.stderr.write('Warning: style not found, %s - setting default!\n' % (node.getAttribute('style'),) )
166 styles = reportlab.lib.styles.getSampleStyleSheet()
167 style = copy.deepcopy(styles['Normal'])
168 return self._para_style_update(style, node)
170 class _rml_doc(object):
171 def __init__(self, data, images={}, path='.'):
172 self.dom = xml.dom.minidom.parseString(data)
173 self.filename = self.dom.documentElement.getAttribute('filename')
177 def docinit(self, els):
178 from reportlab.lib.fonts import addMapping
179 from reportlab.pdfbase import pdfmetrics
180 from reportlab.pdfbase.ttfonts import TTFont
183 for font in node.getElementsByTagName('registerFont'):
184 name = font.getAttribute('fontName').encode('ascii')
185 fname = font.getAttribute('fontFile').encode('ascii')
186 pdfmetrics.registerFont(TTFont(name, fname ))
187 addMapping(name, 0, 0, name) #normal
188 addMapping(name, 0, 1, name) #italic
189 addMapping(name, 1, 0, name) #bold
190 addMapping(name, 1, 1, name) #italic and bold
192 def _textual_image(self, node):
195 for n in node.childNodes:
196 if n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
198 return base64.decodestring(rc)
200 def _images(self, el):
202 for node in el.getElementsByTagName('image'):
203 result[node.getAttribute('name')] = self._textual_image(node)
206 def render(self, out):
207 el = self.dom.documentElement.getElementsByTagName('docinit')
211 el = self.dom.documentElement.getElementsByTagName('stylesheet')
212 self.styles = _rml_styles(el)
214 el = self.dom.documentElement.getElementsByTagName('images')
216 self.images.update( self._images(el[0]) )
218 el = self.dom.documentElement.getElementsByTagName('template')
220 pt_obj = _rml_template(out, el[0], self, images=self.images, path=self.path)
221 pt_obj.render(self.dom.documentElement.getElementsByTagName('story'))
223 self.canvas = canvas.Canvas(out)
224 pd = self.dom.documentElement.getElementsByTagName('pageDrawing')[0]
225 pd_obj = _rml_canvas(self.canvas, None, self, self.images, path=self.path)
227 self.canvas.showPage()
230 class _rml_canvas(object):
231 def __init__(self, canvas, doc_tmpl=None, doc=None, images={}, path='.'):
233 self.styles = doc.styles
234 self.doc_tmpl = doc_tmpl
239 def _textual(self, node):
241 for n in node.childNodes:
242 if n.nodeType == n.ELEMENT_NODE:
243 if n.localName == 'pageNumber':
244 rc += str(self.canvas.getPageNumber())
245 elif n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
246 # this doesn't need to be "entities" encoded like flowables need to
248 return rc.encode(encoding, 'replace')
250 def _drawString(self, node):
251 self.canvas.drawString(text=self._textual(node), **utils.attr_get(node, ['x','y']))
252 def _drawCenteredString(self, node):
253 self.canvas.drawCentredString(text=self._textual(node), **utils.attr_get(node, ['x','y']))
254 def _drawRightString(self, node):
255 self.canvas.drawRightString(text=self._textual(node), **utils.attr_get(node, ['x','y']))
256 def _rect(self, node):
257 if node.hasAttribute('round'):
258 self.canvas.roundRect(radius=utils.unit_get(node.getAttribute('round')), **utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
260 self.canvas.rect(**utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
262 def _ellipse(self, node):
263 x1 = utils.unit_get(node.getAttribute('x'))
264 x2 = utils.unit_get(node.getAttribute('width'))
265 y1 = utils.unit_get(node.getAttribute('y'))
266 y2 = utils.unit_get(node.getAttribute('height'))
267 self.canvas.ellipse(x1,y1,x2,y2, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
268 def _curves(self, node):
269 line_str = utils.text_get(node).split()
271 while len(line_str)>7:
272 self.canvas.bezier(*[utils.unit_get(l) for l in line_str[0:8]])
273 line_str = line_str[8:]
274 def _lines(self, node):
275 line_str = utils.text_get(node).split()
277 while len(line_str)>3:
278 lines.append([utils.unit_get(l) for l in line_str[0:4]])
279 line_str = line_str[4:]
280 self.canvas.lines(lines)
281 def _grid(self, node):
282 xlist = [utils.unit_get(s) for s in node.getAttribute('xs').split(',')]
283 ylist = [utils.unit_get(s) for s in node.getAttribute('ys').split(',')]
284 self.canvas.grid(xlist, ylist)
285 def _translate(self, node):
288 if node.hasAttribute('dx'):
289 dx = utils.unit_get(node.getAttribute('dx'))
290 if node.hasAttribute('dy'):
291 dy = utils.unit_get(node.getAttribute('dy'))
292 self.canvas.translate(dx,dy)
294 def _circle(self, node):
295 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'}))
297 def _place(self, node):
298 flows = _rml_flowable(self.doc, images=self.images, path=self.path).render(node)
299 infos = utils.attr_get(node, ['x','y','width','height'])
301 infos['y']+=infos['height']
303 w,h = flow.wrap(infos['width'], infos['height'])
304 if w<=infos['width'] and h<=infos['height']:
306 flow.drawOn(self.canvas,infos['x'],infos['y'])
309 raise ValueError, "Not enough space"
311 def _line_mode(self, node):
312 ljoin = {'round':1, 'mitered':0, 'bevelled':2}
313 lcap = {'default':0, 'round':1, 'square':2}
314 if node.hasAttribute('width'):
315 self.canvas.setLineWidth(utils.unit_get(node.getAttribute('width')))
316 if node.hasAttribute('join'):
317 self.canvas.setLineJoin(ljoin[node.getAttribute('join')])
318 if node.hasAttribute('cap'):
319 self.canvas.setLineCap(lcap[node.getAttribute('cap')])
320 if node.hasAttribute('miterLimit'):
321 self.canvas.setDash(utils.unit_get(node.getAttribute('miterLimit')))
322 if node.hasAttribute('dash'):
323 dashes = node.getAttribute('dash').split(',')
324 for x in range(len(dashes)):
325 dashes[x]=utils.unit_get(dashes[x])
326 self.canvas.setDash(node.getAttribute('dash').split(','))
328 def _image(self, node):
330 from reportlab.lib.utils import ImageReader
332 if not node.hasAttribute('file'):
334 if node.hasAttribute('name'):
335 image_data = self.images[node.getAttribute('name')]
336 s = StringIO.StringIO(image_data)
339 image_data = base64.decodestring(node.firstChild.nodeValue)
340 if not image_data: return False
341 s = StringIO.StringIO(image_data)
343 if node.getAttribute('file') in self.images:
344 s = StringIO.StringIO(self.images[node.getAttribute('file')])
347 u = urllib.urlopen(str(node.getAttribute('file')))
348 s = StringIO.StringIO(u.read())
350 u = file(os.path.join(self.path,str(node.getAttribute('file'))), 'rb')
351 s = StringIO.StringIO(u.read())
353 (sx,sy) = img.getSize()
356 for tag in ('width','height','x','y'):
357 if node.hasAttribute(tag):
358 args[tag] = utils.unit_get(node.getAttribute(tag))
359 if ('width' in args) and (not 'height' in args):
360 args['height'] = sy * args['width'] / sx
361 elif ('height' in args) and (not 'width' in args):
362 args['width'] = sx * args['height'] / sy
363 elif ('width' in args) and ('height' in args):
364 if (float(args['width'])/args['height'])>(float(sx)>sy):
365 args['width'] = sx * args['height'] / sy
367 args['height'] = sy * args['width'] / sx
368 self.canvas.drawImage(img, **args)
370 def _path(self, node):
371 self.path = self.canvas.beginPath()
372 self.path.moveTo(**utils.attr_get(node, ['x','y']))
373 for n in node.childNodes:
374 if n.nodeType == node.ELEMENT_NODE:
375 if n.localName=='moveto':
376 vals = utils.text_get(n).split()
377 self.path.moveTo(utils.unit_get(vals[0]), utils.unit_get(vals[1]))
378 elif n.localName=='curvesto':
379 vals = utils.text_get(n).split()
383 pos.append(utils.unit_get(vals.pop(0)))
384 self.path.curveTo(*pos)
385 elif (n.nodeType == node.TEXT_NODE):
386 data = n.data.split() # Not sure if I must merge all TEXT_NODE ?
388 x = utils.unit_get(data.pop(0))
389 y = utils.unit_get(data.pop(0))
390 self.path.lineTo(x,y)
391 if (not node.hasAttribute('close')) or utils.bool_get(node.getAttribute('close')):
393 self.canvas.drawPath(self.path, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
395 def render(self, node):
397 'drawCentredString': self._drawCenteredString,
398 'drawRightString': self._drawRightString,
399 'drawString': self._drawString,
401 'ellipse': self._ellipse,
402 'lines': self._lines,
404 'curves': self._curves,
405 'fill': lambda node: self.canvas.setFillColor(color.get(node.getAttribute('color'))),
406 'stroke': lambda node: self.canvas.setStrokeColor(color.get(node.getAttribute('color'))),
407 'setFont': lambda node: self.canvas.setFont(node.getAttribute('name'), utils.unit_get(node.getAttribute('size'))),
408 'place': self._place,
409 'circle': self._circle,
410 'lineMode': self._line_mode,
412 'rotate': lambda node: self.canvas.rotate(float(node.getAttribute('degrees'))),
413 'translate': self._translate,
416 for nd in node.childNodes:
417 if nd.nodeType==nd.ELEMENT_NODE:
419 if nd.localName==tag:
423 class _rml_draw(object):
424 def __init__(self, node, styles, images={}, path='.'):
431 def render(self, canvas, doc):
433 cnv = _rml_canvas(canvas, doc, self.styles, images=self.images, path=self.path)
434 cnv.render(self.node)
435 canvas.restoreState()
437 class _rml_flowable(object):
438 def __init__(self, doc, images={}, path='.'):
440 self.styles = doc.styles
444 def _textual(self, node):
446 for n in node.childNodes:
447 if n.nodeType == node.ELEMENT_NODE:
448 if n.localName == 'getName':
449 newNode = self.doc.dom.createTextNode(self.styles.names.get(n.getAttribute('id'),'Unknown name'))
450 node.insertBefore(newNode, n)
453 elif n.localName == 'pageNumber':
454 rc += '<pageNumber/>' # TODO: change this !
456 #CHECKME: I wonder if this is useful since we don't stock the result. Maybe for the getName tag?
459 elif n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
460 rc += str2xml(n.data)
461 return rc.encode(encoding, 'replace')
463 def _table(self, node):
468 childs = _child_get(node,'tr')
473 for td in _child_get(tr, 'td'):
475 for n in td.childNodes:
476 if n.nodeType==node.ELEMENT_NODE:
477 flow.append( self._flowable(n) )
479 flow = self._textual(td)
481 if len(data2)>length:
484 while len(ab)<length:
486 while len(data2)<length:
489 if node.hasAttribute('colWidths'):
490 assert length == len(node.getAttribute('colWidths').split(','))
491 colwidths = [utils.unit_get(f.strip()) for f in node.getAttribute('colWidths').split(',')]
492 if node.hasAttribute('rowHeights'):
493 rowheights = [utils.unit_get(f.strip()) for f in node.getAttribute('rowHeights').split(',')]
494 if len(rowheights) == 1:
495 rowheights = rowheights[0]
496 table = platypus.Table(data = data, colWidths=colwidths, rowHeights=rowheights, **(utils.attr_get(node, ['splitByRow'] ,{'repeatRows':'int','repeatCols':'int'})))
497 if node.hasAttribute('style'):
498 table.setStyle(self.styles.table_styles[node.getAttribute('style')])
501 def _illustration(self, node):
502 class Illustration(platypus.flowables.Flowable):
503 def __init__(self, node, styles, self2):
506 self.width = utils.unit_get(node.getAttribute('width'))
507 self.height = utils.unit_get(node.getAttribute('height'))
509 def wrap(self, *args):
510 return (self.width, self.height)
513 drw = _rml_draw(self.node, self.styles, images=self.self2.images, path=self.self2.path)
514 drw.render(self.canv, None)
515 return Illustration(node, self.styles, self)
517 def _textual_image(self, node):
520 for n in node.childNodes:
521 if n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
523 return base64.decodestring(rc)
525 def _flowable(self, node):
526 if node.localName=='para':
527 style = self.styles.para_style_get(node)
528 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
529 elif node.localName=='barCode':
531 from reportlab.graphics.barcode import code128
532 from reportlab.graphics.barcode import code39
533 from reportlab.graphics.barcode import code93
534 from reportlab.graphics.barcode import common
535 from reportlab.graphics.barcode import fourstate
536 from reportlab.graphics.barcode import usps
538 print 'Warning: Reportlab barcode extension not installed !'
540 args = utils.attr_get(node, [], {'ratio':'float','xdim':'unit','height':'unit','checksum':'bool','quiet':'bool'})
542 'codabar': lambda x: common.Codabar(x, **args),
543 'code11': lambda x: common.Code11(x, **args),
544 'code128': lambda x: code128.Code128(x, **args),
545 'standard39': lambda x: code39.Standard39(x, **args),
546 'standard93': lambda x: code93.Standard93(x, **args),
547 'i2of5': lambda x: common.I2of5(x, **args),
548 'extended39': lambda x: code39.Extended39(x, **args),
549 'extended93': lambda x: code93.Extended93(x, **args),
550 'msi': lambda x: common.MSI(x, **args),
551 'fim': lambda x: usps.FIM(x, **args),
552 'postnet': lambda x: usps.POSTNET(x, **args),
555 if node.hasAttribute('code'):
556 code = node.getAttribute('code').lower()
557 return codes[code](self._textual(node))
558 elif node.localName=='name':
559 self.styles.names[ node.getAttribute('id')] = node.getAttribute('value')
561 elif node.localName=='xpre':
562 style = self.styles.para_style_get(node)
563 return platypus.XPreformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int','frags':'int'})))
564 elif node.localName=='pre':
565 style = self.styles.para_style_get(node)
566 return platypus.Preformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int'})))
567 elif node.localName=='illustration':
568 return self._illustration(node)
569 elif node.localName=='blockTable':
570 return self._table(node)
571 elif node.localName=='title':
572 styles = reportlab.lib.styles.getSampleStyleSheet()
573 style = styles['Title']
574 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
575 elif node.localName=='h1':
576 styles = reportlab.lib.styles.getSampleStyleSheet()
577 style = styles['Heading1']
578 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
579 elif node.localName=='h2':
580 styles = reportlab.lib.styles.getSampleStyleSheet()
581 style = styles['Heading2']
582 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
583 elif node.localName=='h3':
584 styles = reportlab.lib.styles.getSampleStyleSheet()
585 style = styles['Heading3']
586 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
587 elif node.localName=='image':
588 if not node.hasAttribute('file'):
589 if node.hasAttribute('name'):
590 image_data = self.doc.images[node.getAttribute('name')].read()
593 image_data = base64.decodestring(node.firstChild.nodeValue)
594 image = StringIO.StringIO(image_data)
595 return platypus.Image(image, mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
597 return platypus.Image(node.getAttribute('file'), mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
599 from reportlab.lib.utils import ImageReader
600 name = str(node.getAttribute('file'))
601 img = ImageReader(name)
602 (sx,sy) = img.getSize()
605 for tag in ('width','height'):
606 if node.hasAttribute(tag):
607 args[tag] = utils.unit_get(node.getAttribute(tag))
608 if ('width' in args) and (not 'height' in args):
609 args['height'] = sy * args['width'] / sx
610 elif ('height' in args) and (not 'width' in args):
611 args['width'] = sx * args['height'] / sy
612 elif ('width' in args) and ('height' in args):
613 if (float(args['width'])/args['height'])>(float(sx)>sy):
614 args['width'] = sx * args['height'] / sy
616 args['height'] = sy * args['width'] / sx
617 return platypus.Image(name, mask=(250,255,250,255,250,255), **args)
618 elif node.localName=='spacer':
619 if node.hasAttribute('width'):
620 width = utils.unit_get(node.getAttribute('width'))
622 width = utils.unit_get('1cm')
623 length = utils.unit_get(node.getAttribute('length'))
624 return platypus.Spacer(width=width, height=length)
625 elif node.localName=='section':
626 return self.render(node)
627 elif node.localName in ('pageBreak', 'nextPage'):
628 return platypus.PageBreak()
629 elif node.localName=='condPageBreak':
630 return platypus.CondPageBreak(**(utils.attr_get(node, ['height'])))
631 elif node.localName=='setNextTemplate':
632 return platypus.NextPageTemplate(str(node.getAttribute('name')))
633 elif node.localName=='nextFrame':
634 return platypus.CondPageBreak(1000) # TODO: change the 1000 !
635 elif node.localName == 'setNextFrame':
636 from reportlab.platypus.doctemplate import NextFrameFlowable
637 return NextFrameFlowable(str(node.getAttribute('name')))
638 elif node.localName == 'currentFrame':
639 from reportlab.platypus.doctemplate import CurrentFrameFlowable
640 return CurrentFrameFlowable(str(node.getAttribute('name')))
641 elif node.localName == 'frameEnd':
642 return EndFrameFlowable()
644 sys.stderr.write('Warning: flowable not yet implemented: %s !\n' % (node.localName,))
647 def render(self, node_story):
649 node = node_story.firstChild
651 if node.nodeType == node.ELEMENT_NODE:
652 flow = self._flowable(node)
654 if type(flow) == type([]):
658 node = node.nextSibling
661 from reportlab.platypus.doctemplate import ActionFlowable
663 class EndFrameFlowable(ActionFlowable):
664 def __init__(self,resume=0):
665 ActionFlowable.__init__(self,('frameEnd',resume))
667 class TinyDocTemplate(platypus.BaseDocTemplate):
669 def ___handle_pageBegin(self):
670 self.page = self.page + 1
671 self.pageTemplate.beforeDrawPage(self.canv,self)
672 self.pageTemplate.checkPageSize(self.canv,self)
673 self.pageTemplate.onPage(self.canv,self)
674 for f in self.pageTemplate.frames: f._reset()
676 #keep a count of flowables added to this page. zero indicates bad stuff
677 self._curPageFlowableCount = 0
678 if hasattr(self,'_nextFrameIndex'):
679 del self._nextFrameIndex
680 for f in self.pageTemplate.frames:
684 self.handle_frameBegin()
686 class _rml_template(object):
687 def __init__(self, out, node, doc, images={}, path='.'):
690 if not node.hasAttribute('pageSize'):
691 pageSize = (utils.unit_get('21cm'), utils.unit_get('29.7cm'))
693 ps = map(lambda x:x.strip(), node.getAttribute('pageSize').replace(')', '').replace('(', '').split(','))
694 pageSize = ( utils.unit_get(ps[0]),utils.unit_get(ps[1]) )
695 cm = reportlab.lib.units.cm
696 self.doc_tmpl = TinyDocTemplate(out, pagesize=pageSize, **utils.attr_get(node, ['leftMargin','rightMargin','topMargin','bottomMargin'], {'allowSplitting':'int','showBoundary':'bool','title':'str','author':'str'}))
697 self.page_templates = []
698 self.styles = doc.styles
700 pts = node.getElementsByTagName('pageTemplate')
703 for frame_el in pt.getElementsByTagName('frame'):
704 frame = platypus.Frame( **(utils.attr_get(frame_el, ['x1','y1', 'width','height', 'leftPadding', 'rightPadding', 'bottomPadding', 'topPadding'], {'id':'str', 'showBoundary':'bool'})) )
705 if utils.attr_get(frame_el, ['last']):
706 frame.lastFrame = True
707 frames.append( frame )
708 gr = pt.getElementsByTagName('pageGraphics')
710 drw = _rml_draw(gr[0], self.doc, images=images, path=self.path)
711 self.page_templates.append( platypus.PageTemplate(frames=frames, onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) ))
713 self.page_templates.append( platypus.PageTemplate(frames=frames, **utils.attr_get(pt, [], {'id':'str'}) ))
714 self.doc_tmpl.addPageTemplates(self.page_templates)
716 def render(self, node_stories):
718 r = _rml_flowable(self.doc,images=self.images, path=self.path)
719 for node_story in node_stories:
720 fis += r.render(node_story)
721 fis.append(platypus.PageBreak())
722 self.doc_tmpl.build(fis)
724 def parseString(data, fout=None, images={}, path='.'):
725 r = _rml_doc(data, images, path)
732 fp = StringIO.StringIO()
737 print 'Usage: trml2pdf input.rml >output.pdf'
738 print 'Render the standard input (RML) and output a PDF file'
741 if __name__=="__main__":
743 if sys.argv[1]=='--help':
745 print parseString(file(sys.argv[1], 'r').read()),
747 print 'Usage: trml2pdf input.rml >output.pdf'
748 print 'Try \'trml2pdf --help\' for more information.'