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 s = StringIO.StringIO(image_data)
342 if node.getAttribute('file') in self.images:
343 s = StringIO.StringIO(self.images[node.getAttribute('file')])
346 u = urllib.urlopen(str(node.getAttribute('file')))
347 s = StringIO.StringIO(u.read())
349 u = file(os.path.join(self.path,str(node.getAttribute('file'))), 'rb')
350 s = StringIO.StringIO(u.read())
352 (sx,sy) = img.getSize()
355 for tag in ('width','height','x','y'):
356 if node.hasAttribute(tag):
357 args[tag] = utils.unit_get(node.getAttribute(tag))
358 if ('width' in args) and (not 'height' in args):
359 args['height'] = sy * args['width'] / sx
360 elif ('height' in args) and (not 'width' in args):
361 args['width'] = sx * args['height'] / sy
362 elif ('width' in args) and ('height' in args):
363 if (float(args['width'])/args['height'])>(float(sx)>sy):
364 args['width'] = sx * args['height'] / sy
366 args['height'] = sy * args['width'] / sx
367 self.canvas.drawImage(img, **args)
369 def _path(self, node):
370 self.path = self.canvas.beginPath()
371 self.path.moveTo(**utils.attr_get(node, ['x','y']))
372 for n in node.childNodes:
373 if n.nodeType == node.ELEMENT_NODE:
374 if n.localName=='moveto':
375 vals = utils.text_get(n).split()
376 self.path.moveTo(utils.unit_get(vals[0]), utils.unit_get(vals[1]))
377 elif n.localName=='curvesto':
378 vals = utils.text_get(n).split()
382 pos.append(utils.unit_get(vals.pop(0)))
383 self.path.curveTo(*pos)
384 elif (n.nodeType == node.TEXT_NODE):
385 data = n.data.split() # Not sure if I must merge all TEXT_NODE ?
387 x = utils.unit_get(data.pop(0))
388 y = utils.unit_get(data.pop(0))
389 self.path.lineTo(x,y)
390 if (not node.hasAttribute('close')) or utils.bool_get(node.getAttribute('close')):
392 self.canvas.drawPath(self.path, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
394 def render(self, node):
396 'drawCentredString': self._drawCenteredString,
397 'drawRightString': self._drawRightString,
398 'drawString': self._drawString,
400 'ellipse': self._ellipse,
401 'lines': self._lines,
403 'curves': self._curves,
404 'fill': lambda node: self.canvas.setFillColor(color.get(node.getAttribute('color'))),
405 'stroke': lambda node: self.canvas.setStrokeColor(color.get(node.getAttribute('color'))),
406 'setFont': lambda node: self.canvas.setFont(node.getAttribute('name'), utils.unit_get(node.getAttribute('size'))),
407 'place': self._place,
408 'circle': self._circle,
409 'lineMode': self._line_mode,
411 'rotate': lambda node: self.canvas.rotate(float(node.getAttribute('degrees'))),
412 'translate': self._translate,
415 for nd in node.childNodes:
416 if nd.nodeType==nd.ELEMENT_NODE:
418 if nd.localName==tag:
422 class _rml_draw(object):
423 def __init__(self, node, styles, images={}, path='.'):
430 def render(self, canvas, doc):
432 cnv = _rml_canvas(canvas, doc, self.styles, images=self.images, path=self.path)
433 cnv.render(self.node)
434 canvas.restoreState()
436 class _rml_flowable(object):
437 def __init__(self, doc, images={}, path='.'):
439 self.styles = doc.styles
443 def _textual(self, node):
445 for n in node.childNodes:
446 if n.nodeType == node.ELEMENT_NODE:
447 if n.localName == 'getName':
448 newNode = self.doc.dom.createTextNode(self.styles.names.get(n.getAttribute('id'),'Unknown name'))
449 node.insertBefore(newNode, n)
452 elif n.localName == 'pageNumber':
453 rc += '<pageNumber/>' # TODO: change this !
455 #CHECKME: I wonder if this is useful since we don't stock the result. Maybe for the getName tag?
458 elif n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
459 rc += str2xml(n.data)
460 return rc.encode(encoding, 'replace')
462 def _table(self, node):
467 childs = _child_get(node,'tr')
472 for td in _child_get(tr, 'td'):
474 for n in td.childNodes:
475 if n.nodeType==node.ELEMENT_NODE:
476 flow.append( self._flowable(n) )
478 flow = self._textual(td)
480 if len(data2)>length:
483 while len(ab)<length:
485 while len(data2)<length:
488 if node.hasAttribute('colWidths'):
489 assert length == len(node.getAttribute('colWidths').split(','))
490 colwidths = [utils.unit_get(f.strip()) for f in node.getAttribute('colWidths').split(',')]
491 if node.hasAttribute('rowHeights'):
492 rowheights = [utils.unit_get(f.strip()) for f in node.getAttribute('rowHeights').split(',')]
493 if len(rowheights) == 1:
494 rowheights = rowheights[0]
495 table = platypus.Table(data = data, colWidths=colwidths, rowHeights=rowheights, **(utils.attr_get(node, ['splitByRow'] ,{'repeatRows':'int','repeatCols':'int'})))
496 if node.hasAttribute('style'):
497 table.setStyle(self.styles.table_styles[node.getAttribute('style')])
500 def _illustration(self, node):
501 class Illustration(platypus.flowables.Flowable):
502 def __init__(self, node, styles, self2):
505 self.width = utils.unit_get(node.getAttribute('width'))
506 self.height = utils.unit_get(node.getAttribute('height'))
508 def wrap(self, *args):
509 return (self.width, self.height)
512 drw = _rml_draw(self.node, self.styles, images=self.self2.images, path=self.self2.path)
513 drw.render(self.canv, None)
514 return Illustration(node, self.styles, self)
516 def _textual_image(self, node):
519 for n in node.childNodes:
520 if n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
522 return base64.decodestring(rc)
524 def _flowable(self, node):
525 if node.localName=='para':
526 style = self.styles.para_style_get(node)
527 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
528 elif node.localName=='barCode':
530 from reportlab.graphics.barcode import code128
531 from reportlab.graphics.barcode import code39
532 from reportlab.graphics.barcode import code93
533 from reportlab.graphics.barcode import common
534 from reportlab.graphics.barcode import fourstate
535 from reportlab.graphics.barcode import usps
537 print 'Warning: Reportlab barcode extension not installed !'
539 args = utils.attr_get(node, [], {'ratio':'float','xdim':'unit','height':'unit','checksum':'bool','quiet':'bool'})
541 'codabar': lambda x: common.Codabar(x, **args),
542 'code11': lambda x: common.Code11(x, **args),
543 'code128': lambda x: code128.Code128(x, **args),
544 'standard39': lambda x: code39.Standard39(x, **args),
545 'standard93': lambda x: code93.Standard93(x, **args),
546 'i2of5': lambda x: common.I2of5(x, **args),
547 'extended39': lambda x: code39.Extended39(x, **args),
548 'extended93': lambda x: code93.Extended93(x, **args),
549 'msi': lambda x: common.MSI(x, **args),
550 'fim': lambda x: usps.FIM(x, **args),
551 'postnet': lambda x: usps.POSTNET(x, **args),
554 if node.hasAttribute('code'):
555 code = node.getAttribute('code').lower()
556 return codes[code](self._textual(node))
557 elif node.localName=='name':
558 self.styles.names[ node.getAttribute('id')] = node.getAttribute('value')
560 elif node.localName=='xpre':
561 style = self.styles.para_style_get(node)
562 return platypus.XPreformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int','frags':'int'})))
563 elif node.localName=='pre':
564 style = self.styles.para_style_get(node)
565 return platypus.Preformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int'})))
566 elif node.localName=='illustration':
567 return self._illustration(node)
568 elif node.localName=='blockTable':
569 return self._table(node)
570 elif node.localName=='title':
571 styles = reportlab.lib.styles.getSampleStyleSheet()
572 style = styles['Title']
573 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
574 elif node.localName=='h1':
575 styles = reportlab.lib.styles.getSampleStyleSheet()
576 style = styles['Heading1']
577 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
578 elif node.localName=='h2':
579 styles = reportlab.lib.styles.getSampleStyleSheet()
580 style = styles['Heading2']
581 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
582 elif node.localName=='h3':
583 styles = reportlab.lib.styles.getSampleStyleSheet()
584 style = styles['Heading3']
585 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
586 elif node.localName=='image':
587 if not node.hasAttribute('file'):
588 if node.hasAttribute('name'):
589 image_data = self.doc.images[node.getAttribute('name')].read()
592 image_data = base64.decodestring(node.firstChild.nodeValue)
593 image = StringIO.StringIO(image_data)
594 return platypus.Image(image, mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
596 return platypus.Image(node.getAttribute('file'), mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
598 from reportlab.lib.utils import ImageReader
599 name = str(node.getAttribute('file'))
600 img = ImageReader(name)
601 (sx,sy) = img.getSize()
604 for tag in ('width','height'):
605 if node.hasAttribute(tag):
606 args[tag] = utils.unit_get(node.getAttribute(tag))
607 if ('width' in args) and (not 'height' in args):
608 args['height'] = sy * args['width'] / sx
609 elif ('height' in args) and (not 'width' in args):
610 args['width'] = sx * args['height'] / sy
611 elif ('width' in args) and ('height' in args):
612 if (float(args['width'])/args['height'])>(float(sx)>sy):
613 args['width'] = sx * args['height'] / sy
615 args['height'] = sy * args['width'] / sx
616 return platypus.Image(name, mask=(250,255,250,255,250,255), **args)
617 elif node.localName=='spacer':
618 if node.hasAttribute('width'):
619 width = utils.unit_get(node.getAttribute('width'))
621 width = utils.unit_get('1cm')
622 length = utils.unit_get(node.getAttribute('length'))
623 return platypus.Spacer(width=width, height=length)
624 elif node.localName=='section':
625 return self.render(node)
626 elif node.localName in ('pageBreak', 'nextPage'):
627 return platypus.PageBreak()
628 elif node.localName=='condPageBreak':
629 return platypus.CondPageBreak(**(utils.attr_get(node, ['height'])))
630 elif node.localName=='setNextTemplate':
631 return platypus.NextPageTemplate(str(node.getAttribute('name')))
632 elif node.localName=='nextFrame':
633 return platypus.CondPageBreak(1000) # TODO: change the 1000 !
634 elif node.localName == 'setNextFrame':
635 from reportlab.platypus.doctemplate import NextFrameFlowable
636 return NextFrameFlowable(str(node.getAttribute('name')))
637 elif node.localName == 'currentFrame':
638 from reportlab.platypus.doctemplate import CurrentFrameFlowable
639 return CurrentFrameFlowable(str(node.getAttribute('name')))
640 elif node.localName == 'frameEnd':
641 return EndFrameFlowable()
643 sys.stderr.write('Warning: flowable not yet implemented: %s !\n' % (node.localName,))
646 def render(self, node_story):
648 node = node_story.firstChild
650 if node.nodeType == node.ELEMENT_NODE:
651 flow = self._flowable(node)
653 if type(flow) == type([]):
657 node = node.nextSibling
660 from reportlab.platypus.doctemplate import ActionFlowable
662 class EndFrameFlowable(ActionFlowable):
663 def __init__(self,resume=0):
664 ActionFlowable.__init__(self,('frameEnd',resume))
666 class TinyDocTemplate(platypus.BaseDocTemplate):
668 def ___handle_pageBegin(self):
669 self.page = self.page + 1
670 self.pageTemplate.beforeDrawPage(self.canv,self)
671 self.pageTemplate.checkPageSize(self.canv,self)
672 self.pageTemplate.onPage(self.canv,self)
673 for f in self.pageTemplate.frames: f._reset()
675 #keep a count of flowables added to this page. zero indicates bad stuff
676 self._curPageFlowableCount = 0
677 if hasattr(self,'_nextFrameIndex'):
678 del self._nextFrameIndex
679 for f in self.pageTemplate.frames:
683 self.handle_frameBegin()
685 class _rml_template(object):
686 def __init__(self, out, node, doc, images={}, path='.'):
689 if not node.hasAttribute('pageSize'):
690 pageSize = (utils.unit_get('21cm'), utils.unit_get('29.7cm'))
692 ps = map(lambda x:x.strip(), node.getAttribute('pageSize').replace(')', '').replace('(', '').split(','))
693 pageSize = ( utils.unit_get(ps[0]),utils.unit_get(ps[1]) )
694 cm = reportlab.lib.units.cm
695 self.doc_tmpl = TinyDocTemplate(out, pagesize=pageSize, **utils.attr_get(node, ['leftMargin','rightMargin','topMargin','bottomMargin'], {'allowSplitting':'int','showBoundary':'bool','title':'str','author':'str'}))
696 self.page_templates = []
697 self.styles = doc.styles
699 pts = node.getElementsByTagName('pageTemplate')
702 for frame_el in pt.getElementsByTagName('frame'):
703 frame = platypus.Frame( **(utils.attr_get(frame_el, ['x1','y1', 'width','height', 'leftPadding', 'rightPadding', 'bottomPadding', 'topPadding'], {'id':'str', 'showBoundary':'bool'})) )
704 if utils.attr_get(frame_el, ['last']):
705 frame.lastFrame = True
706 frames.append( frame )
707 gr = pt.getElementsByTagName('pageGraphics')
709 drw = _rml_draw(gr[0], self.doc, images=images, path=self.path)
710 self.page_templates.append( platypus.PageTemplate(frames=frames, onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) ))
712 self.page_templates.append( platypus.PageTemplate(frames=frames, **utils.attr_get(pt, [], {'id':'str'}) ))
713 self.doc_tmpl.addPageTemplates(self.page_templates)
715 def render(self, node_stories):
717 r = _rml_flowable(self.doc,images=self.images, path=self.path)
718 for node_story in node_stories:
719 fis += r.render(node_story)
720 fis.append(platypus.PageBreak())
721 self.doc_tmpl.build(fis)
723 def parseString(data, fout=None, images={}, path='.'):
724 r = _rml_doc(data, images, path)
731 fp = StringIO.StringIO()
736 print 'Usage: trml2pdf input.rml >output.pdf'
737 print 'Render the standard input (RML) and output a PDF file'
740 if __name__=="__main__":
742 if sys.argv[1]=='--help':
744 print parseString(file(sys.argv[1], 'r').read()),
746 print 'Usage: trml2pdf input.rml >output.pdf'
747 print 'Try \'trml2pdf --help\' for more information.'