2 # -*- coding: utf-8 -*-
4 # trml2pdf - An RML to PDF converter
5 # Copyright (C) 2003, Fabien Pinckaers, UCL, FSA
7 # Richard Waid <richard@iopen.net>
9 # This library is free software; you can redistribute it and/or
10 # modify it under the terms of the GNU Lesser General Public
11 # License as published by the Free Software Foundation; either
12 # version 2.1 of the License, or (at your option) any later version.
14 # This library is distributed in the hope that it will be useful,
15 # but WITHOUT ANY WARRANTY; without even the implied warranty of
16 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
17 # Lesser General Public License for more details.
19 # You should have received a copy of the GNU Lesser General Public
20 # License along with this library; if not, write to the Free Software
21 # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
25 import xml.dom.minidom
29 from reportlab.pdfgen import canvas
30 from reportlab import platypus
37 # Change this to UTF-8 if you plan tu use Reportlab's UTF-8 support
39 # reportlab use "code page 1252" encoding by default. cfr reportlab user guide p.46
43 return s.replace('&', '&').replace('<', '<').replace('>', '>')
45 def _child_get(node, childs):
47 for n in node.childNodes:
48 if (n.nodeType==n.ELEMENT_NODE) and (n.localName==childs):
52 class _rml_styles(object):
53 def __init__(self, nodes):
56 self.table_styles = {}
58 for style in node.getElementsByTagName('blockTableStyle'):
59 self.table_styles[style.getAttribute('id')] = self._table_style_get(style)
60 for style in node.getElementsByTagName('paraStyle'):
61 self.styles[style.getAttribute('name')] = self._para_style_get(style)
62 for variable in node.getElementsByTagName('initialize'):
63 for name in variable.getElementsByTagName('name'):
64 self.names[ name.getAttribute('id')] = name.getAttribute('value')
66 def _para_style_update(self, style, node):
67 for attr in ['textColor', 'backColor', 'bulletColor']:
68 if node.hasAttribute(attr):
69 style.__dict__[attr] = color.get(node.getAttribute(attr))
70 for attr in ['fontName', 'bulletFontName', 'bulletText']:
71 if node.hasAttribute(attr):
72 style.__dict__[attr] = node.getAttribute(attr)
73 for attr in ['fontSize', 'leftIndent', 'rightIndent', 'spaceBefore', 'spaceAfter', 'firstLineIndent', 'bulletIndent', 'bulletFontSize', 'leading']:
74 if node.hasAttribute(attr):
75 style.__dict__[attr] = utils.unit_get(node.getAttribute(attr))
76 if node.hasAttribute('alignment'):
78 'right':reportlab.lib.enums.TA_RIGHT,
79 'center':reportlab.lib.enums.TA_CENTER,
80 'justify':reportlab.lib.enums.TA_JUSTIFY
82 style.alignment = align.get(node.getAttribute('alignment').lower(), reportlab.lib.enums.TA_LEFT)
85 def _table_style_get(self, style_node):
87 for node in style_node.childNodes:
88 if node.nodeType==node.ELEMENT_NODE:
89 start = utils.tuple_int_get(node, 'start', (0,0) )
90 stop = utils.tuple_int_get(node, 'stop', (-1,-1) )
91 if node.localName=='blockValign':
92 styles.append(('VALIGN', start, stop, str(node.getAttribute('value'))))
93 elif node.localName=='blockFont':
94 styles.append(('FONT', start, stop, str(node.getAttribute('name'))))
95 elif node.localName=='blockTextColor':
96 styles.append(('TEXTCOLOR', start, stop, color.get(str(node.getAttribute('colorName')))))
97 elif node.localName=='blockLeading':
98 styles.append(('LEADING', start, stop, utils.unit_get(node.getAttribute('length'))))
99 elif node.localName=='blockAlignment':
100 styles.append(('ALIGNMENT', start, stop, str(node.getAttribute('value'))))
101 elif node.localName=='blockSpan':
102 styles.append(('SPAN', start, stop))
103 elif node.localName=='blockLeftPadding':
104 styles.append(('LEFTPADDING', start, stop, utils.unit_get(node.getAttribute('length'))))
105 elif node.localName=='blockRightPadding':
106 styles.append(('RIGHTPADDING', start, stop, utils.unit_get(node.getAttribute('length'))))
107 elif node.localName=='blockTopPadding':
108 styles.append(('TOPPADDING', start, stop, utils.unit_get(node.getAttribute('length'))))
109 elif node.localName=='blockBottomPadding':
110 styles.append(('BOTTOMPADDING', start, stop, utils.unit_get(node.getAttribute('length'))))
111 elif node.localName=='blockBackground':
112 styles.append(('BACKGROUND', start, stop, color.get(node.getAttribute('colorName'))))
113 if node.hasAttribute('size'):
114 styles.append(('FONTSIZE', start, stop, utils.unit_get(node.getAttribute('size'))))
115 elif node.localName=='lineStyle':
116 kind = node.getAttribute('kind')
117 kind_list = [ 'GRID', 'BOX', 'OUTLINE', 'INNERGRID', 'LINEBELOW', 'LINEABOVE','LINEBEFORE', 'LINEAFTER' ]
118 assert kind in kind_list
120 if node.hasAttribute('thickness'):
121 thick = float(node.getAttribute('thickness'))
122 styles.append((kind, start, stop, thick, color.get(node.getAttribute('colorName'))))
123 return platypus.tables.TableStyle(styles)
125 def _para_style_get(self, node):
126 styles = reportlab.lib.styles.getSampleStyleSheet()
127 style = copy.deepcopy(styles["Normal"])
128 self._para_style_update(style, node)
131 def para_style_get(self, node):
133 if node.hasAttribute('style'):
134 if node.getAttribute('style') in self.styles:
135 style = copy.deepcopy(self.styles[node.getAttribute('style')])
137 sys.stderr.write('Warning: style not found, %s - setting default!\n' % (node.getAttribute('style'),) )
139 styles = reportlab.lib.styles.getSampleStyleSheet()
140 style = copy.deepcopy(styles['Normal'])
141 return self._para_style_update(style, node)
143 class _rml_doc(object):
144 def __init__(self, data, images={}, path='.'):
145 self.dom = xml.dom.minidom.parseString(data)
146 self.filename = self.dom.documentElement.getAttribute('filename')
150 def docinit(self, els):
151 from reportlab.lib.fonts import addMapping
152 from reportlab.pdfbase import pdfmetrics
153 from reportlab.pdfbase.ttfonts import TTFont
156 for font in node.getElementsByTagName('registerFont'):
157 name = font.getAttribute('fontName').encode('ascii')
158 fname = font.getAttribute('fontFile').encode('ascii')
159 pdfmetrics.registerFont(TTFont(name, fname ))
160 addMapping(name, 0, 0, name) #normal
161 addMapping(name, 0, 1, name) #italic
162 addMapping(name, 1, 0, name) #bold
163 addMapping(name, 1, 1, name) #italic and bold
165 def _textual_image(self, node):
168 for n in node.childNodes:
169 if n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
171 return base64.decodestring(rc)
173 def _images(self, el):
175 for node in el.getElementsByTagName('image'):
176 result[node.getAttribute('name')] = self._textual_image(node)
179 def render(self, out):
180 el = self.dom.documentElement.getElementsByTagName('docinit')
184 el = self.dom.documentElement.getElementsByTagName('stylesheet')
185 self.styles = _rml_styles(el)
187 el = self.dom.documentElement.getElementsByTagName('images')
189 self.images.update( self._images(el[0]) )
191 el = self.dom.documentElement.getElementsByTagName('template')
193 pt_obj = _rml_template(out, el[0], self, images=self.images, path=self.path)
194 pt_obj.render(self.dom.documentElement.getElementsByTagName('story'))
196 self.canvas = canvas.Canvas(out)
197 pd = self.dom.documentElement.getElementsByTagName('pageDrawing')[0]
198 pd_obj = _rml_canvas(self.canvas, None, self, self.images, path=self.path)
200 self.canvas.showPage()
203 class _rml_canvas(object):
204 def __init__(self, canvas, doc_tmpl=None, doc=None, images={}, path='.'):
206 self.styles = doc.styles
207 self.doc_tmpl = doc_tmpl
212 def _textual(self, node):
214 for n in node.childNodes:
215 if n.nodeType == n.ELEMENT_NODE:
216 if n.localName == 'pageNumber':
217 rc += str(self.canvas.getPageNumber())
218 elif n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
219 # this doesn't need to be "entities" encoded like flowables need to
221 return rc.encode(encoding, 'replace')
223 def _drawString(self, node):
224 self.canvas.drawString(text=self._textual(node), **utils.attr_get(node, ['x','y']))
225 def _drawCenteredString(self, node):
226 self.canvas.drawCentredString(text=self._textual(node), **utils.attr_get(node, ['x','y']))
227 def _drawRightString(self, node):
228 self.canvas.drawRightString(text=self._textual(node), **utils.attr_get(node, ['x','y']))
229 def _rect(self, node):
230 if node.hasAttribute('round'):
231 self.canvas.roundRect(radius=utils.unit_get(node.getAttribute('round')), **utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
233 self.canvas.rect(**utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
235 def _ellipse(self, node):
236 x1 = utils.unit_get(node.getAttribute('x'))
237 x2 = utils.unit_get(node.getAttribute('width'))
238 y1 = utils.unit_get(node.getAttribute('y'))
239 y2 = utils.unit_get(node.getAttribute('height'))
240 self.canvas.ellipse(x1,y1,x2,y2, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
241 def _curves(self, node):
242 line_str = utils.text_get(node).split()
244 while len(line_str)>7:
245 self.canvas.bezier(*[utils.unit_get(l) for l in line_str[0:8]])
246 line_str = line_str[8:]
247 def _lines(self, node):
248 line_str = utils.text_get(node).split()
250 while len(line_str)>3:
251 lines.append([utils.unit_get(l) for l in line_str[0:4]])
252 line_str = line_str[4:]
253 self.canvas.lines(lines)
254 def _grid(self, node):
255 xlist = [utils.unit_get(s) for s in node.getAttribute('xs').split(',')]
256 ylist = [utils.unit_get(s) for s in node.getAttribute('ys').split(',')]
257 self.canvas.grid(xlist, ylist)
258 def _translate(self, node):
261 if node.hasAttribute('dx'):
262 dx = utils.unit_get(node.getAttribute('dx'))
263 if node.hasAttribute('dy'):
264 dy = utils.unit_get(node.getAttribute('dy'))
265 self.canvas.translate(dx,dy)
267 def _circle(self, node):
268 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'}))
270 def _place(self, node):
271 flows = _rml_flowable(self.doc, images=self.images, path=self.path).render(node)
272 infos = utils.attr_get(node, ['x','y','width','height'])
274 infos['y']+=infos['height']
276 w,h = flow.wrap(infos['width'], infos['height'])
277 if w<=infos['width'] and h<=infos['height']:
279 flow.drawOn(self.canvas,infos['x'],infos['y'])
282 raise ValueError, "Not enough space"
284 def _line_mode(self, node):
285 ljoin = {'round':1, 'mitered':0, 'bevelled':2}
286 lcap = {'default':0, 'round':1, 'square':2}
287 if node.hasAttribute('width'):
288 self.canvas.setLineWidth(utils.unit_get(node.getAttribute('width')))
289 if node.hasAttribute('join'):
290 self.canvas.setLineJoin(ljoin[node.getAttribute('join')])
291 if node.hasAttribute('cap'):
292 self.canvas.setLineCap(lcap[node.getAttribute('cap')])
293 if node.hasAttribute('miterLimit'):
294 self.canvas.setDash(utils.unit_get(node.getAttribute('miterLimit')))
295 if node.hasAttribute('dash'):
296 dashes = node.getAttribute('dash').split(',')
297 for x in range(len(dashes)):
298 dashes[x]=utils.unit_get(dashes[x])
299 self.canvas.setDash(node.getAttribute('dash').split(','))
301 def _image(self, node):
303 from reportlab.lib.utils import ImageReader
305 if not node.hasAttribute('file'):
306 if node.getAttribute('name') in self.images:
307 scontent = self.images[node.getAttribute('name')]
308 s = StringIO.StringIO(scontent)
312 if node.getAttribute('file') in self.images:
313 s = StringIO.StringIO(self.images[node.getAttribute('file')])
316 u = urllib.urlopen(str(node.getAttribute('file')))
317 s = StringIO.StringIO(u.read())
319 u = file(os.path.join(self.path,str(node.getAttribute('file'))), 'rb')
320 s = StringIO.StringIO(u.read())
322 (sx,sy) = img.getSize()
325 for tag in ('width','height','x','y'):
326 if node.hasAttribute(tag):
327 args[tag] = utils.unit_get(node.getAttribute(tag))
328 if ('width' in args) and (not 'height' in args):
329 args['height'] = sy * args['width'] / sx
330 elif ('height' in args) and (not 'width' in args):
331 args['width'] = sx * args['height'] / sy
332 elif ('width' in args) and ('height' in args):
333 if (float(args['width'])/args['height'])>(float(sx)>sy):
334 args['width'] = sx * args['height'] / sy
336 args['height'] = sy * args['width'] / sx
337 self.canvas.drawImage(img, **args)
339 def _path(self, node):
340 self.path = self.canvas.beginPath()
341 self.path.moveTo(**utils.attr_get(node, ['x','y']))
342 for n in node.childNodes:
343 if n.nodeType == node.ELEMENT_NODE:
344 if n.localName=='moveto':
345 vals = utils.text_get(n).split()
346 self.path.moveTo(utils.unit_get(vals[0]), utils.unit_get(vals[1]))
347 elif n.localName=='curvesto':
348 vals = utils.text_get(n).split()
352 pos.append(utils.unit_get(vals.pop(0)))
353 self.path.curveTo(*pos)
354 elif (n.nodeType == node.TEXT_NODE):
355 data = n.data.split() # Not sure if I must merge all TEXT_NODE ?
357 x = utils.unit_get(data.pop(0))
358 y = utils.unit_get(data.pop(0))
359 self.path.lineTo(x,y)
360 if (not node.hasAttribute('close')) or utils.bool_get(node.getAttribute('close')):
362 self.canvas.drawPath(self.path, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
364 def render(self, node):
366 'drawCentredString': self._drawCenteredString,
367 'drawRightString': self._drawRightString,
368 'drawString': self._drawString,
370 'ellipse': self._ellipse,
371 'lines': self._lines,
373 'curves': self._curves,
374 'fill': lambda node: self.canvas.setFillColor(color.get(node.getAttribute('color'))),
375 'stroke': lambda node: self.canvas.setStrokeColor(color.get(node.getAttribute('color'))),
376 'setFont': lambda node: self.canvas.setFont(node.getAttribute('name'), utils.unit_get(node.getAttribute('size'))),
377 'place': self._place,
378 'circle': self._circle,
379 'lineMode': self._line_mode,
381 'rotate': lambda node: self.canvas.rotate(float(node.getAttribute('degrees'))),
382 'translate': self._translate,
385 for nd in node.childNodes:
386 if nd.nodeType==nd.ELEMENT_NODE:
388 if nd.localName==tag:
392 class _rml_draw(object):
393 def __init__(self, node, styles, images={}, path='.'):
400 def render(self, canvas, doc):
402 cnv = _rml_canvas(canvas, doc, self.styles, images=self.images, path=self.path)
403 cnv.render(self.node)
404 canvas.restoreState()
406 class _rml_flowable(object):
407 def __init__(self, doc, images={}, path='.'):
409 self.styles = doc.styles
413 def _textual(self, node):
415 for n in node.childNodes:
416 if n.nodeType == node.ELEMENT_NODE:
417 if n.localName == 'getName':
418 newNode = self.doc.dom.createTextNode(self.styles.names.get(n.getAttribute('id'),'Unknown name'))
419 node.insertBefore(newNode, n)
422 elif n.localName == 'pageNumber':
423 rc += '<pageNumber/>' # TODO: change this !
425 #CHECKME: I wonder if this is useful since we don't stock the result. Maybe for the getName tag?
428 elif n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
429 rc += str2xml(n.data)
430 return rc.encode(encoding, 'replace')
432 def _table(self, node):
437 childs = _child_get(node,'tr')
442 for td in _child_get(tr, 'td'):
444 for n in td.childNodes:
445 if n.nodeType==node.ELEMENT_NODE:
446 flow.append( self._flowable(n) )
448 flow = self._textual(td)
450 if len(data2)>length:
453 while len(ab)<length:
455 while len(data2)<length:
458 if node.hasAttribute('colWidths'):
459 assert length == len(node.getAttribute('colWidths').split(','))
460 colwidths = [utils.unit_get(f.strip()) for f in node.getAttribute('colWidths').split(',')]
461 if node.hasAttribute('rowHeights'):
462 rowheights = [utils.unit_get(f.strip()) for f in node.getAttribute('rowHeights').split(',')]
463 if len(rowheights) == 1:
464 rowheights = rowheights[0]
465 table = platypus.Table(data = data, colWidths=colwidths, rowHeights=rowheights, **(utils.attr_get(node, ['splitByRow'] ,{'repeatRows':'int','repeatCols':'int'})))
466 if node.hasAttribute('style'):
467 table.setStyle(self.styles.table_styles[node.getAttribute('style')])
470 def _illustration(self, node):
471 class Illustration(platypus.flowables.Flowable):
472 def __init__(self, node, styles, self2):
475 self.width = utils.unit_get(node.getAttribute('width'))
476 self.height = utils.unit_get(node.getAttribute('height'))
478 def wrap(self, *args):
479 return (self.width, self.height)
482 drw = _rml_draw(self.node, self.styles, images=self.self2.images, path=self.self2.path)
483 drw.render(self.canv, None)
484 return Illustration(node, self.styles, self)
486 def _textual_image(self, node):
489 for n in node.childNodes:
490 if n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
492 return base64.decodestring(rc)
494 def _flowable(self, node):
495 if node.localName=='para':
496 style = self.styles.para_style_get(node)
497 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
498 elif node.localName=='barCode':
500 from reportlab.graphics.barcode import code128
501 from reportlab.graphics.barcode import code39
502 from reportlab.graphics.barcode import code93
503 from reportlab.graphics.barcode import common
504 from reportlab.graphics.barcode import fourstate
505 from reportlab.graphics.barcode import usps
507 print 'Warning: Reportlab barcode extension not installed !'
509 args = utils.attr_get(node, [], {'ratio':'float','xdim':'unit','height':'unit','checksum':'bool','quiet':'bool'})
511 'codabar': lambda x: common.Codabar(x, **args),
512 'code11': lambda x: common.Code11(x, **args),
513 'code128': lambda x: code128.Code128(x, **args),
514 'standard39': lambda x: code39.Standard39(x, **args),
515 'standard93': lambda x: code93.Standard93(x, **args),
516 'i2of5': lambda x: common.I2of5(x, **args),
517 'extended39': lambda x: code39.Extended39(x, **args),
518 'extended93': lambda x: code93.Extended93(x, **args),
519 'msi': lambda x: common.MSI(x, **args),
520 'fim': lambda x: usps.FIM(x, **args),
521 'postnet': lambda x: usps.POSTNET(x, **args),
524 if node.hasAttribute('code'):
525 code = node.getAttribute('code').lower()
526 return codes[code](self._textual(node))
527 elif node.localName=='name':
528 self.styles.names[ node.getAttribute('id')] = node.getAttribute('value')
530 elif node.localName=='xpre':
531 style = self.styles.para_style_get(node)
532 return platypus.XPreformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int','frags':'int'})))
533 elif node.localName=='pre':
534 style = self.styles.para_style_get(node)
535 return platypus.Preformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int'})))
536 elif node.localName=='illustration':
537 return self._illustration(node)
538 elif node.localName=='blockTable':
539 return self._table(node)
540 elif node.localName=='title':
541 styles = reportlab.lib.styles.getSampleStyleSheet()
542 style = styles['Title']
543 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
544 elif node.localName=='h1':
545 styles = reportlab.lib.styles.getSampleStyleSheet()
546 style = styles['Heading1']
547 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
548 elif node.localName=='h2':
549 styles = reportlab.lib.styles.getSampleStyleSheet()
550 style = styles['Heading2']
551 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
552 elif node.localName=='h3':
553 styles = reportlab.lib.styles.getSampleStyleSheet()
554 style = styles['Heading3']
555 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
556 elif node.localName=='image':
557 if not node.hasAttribute('file'):
558 if node.hasAttribute('name'):
559 image_data = self.doc.images[node.getAttribute('name')].read()
562 image_data = base64.decodestring(node.firstChild.nodeValue)
563 image = StringIO.StringIO(image_data)
564 return platypus.Image(image, mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
566 return platypus.Image(node.getAttribute('file'), mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
568 from reportlab.lib.utils import ImageReader
569 name = str(node.getAttribute('file'))
570 img = ImageReader(name)
571 (sx,sy) = img.getSize()
574 for tag in ('width','height'):
575 if node.hasAttribute(tag):
576 args[tag] = utils.unit_get(node.getAttribute(tag))
577 if ('width' in args) and (not 'height' in args):
578 args['height'] = sy * args['width'] / sx
579 elif ('height' in args) and (not 'width' in args):
580 args['width'] = sx * args['height'] / sy
581 elif ('width' in args) and ('height' in args):
582 if (float(args['width'])/args['height'])>(float(sx)>sy):
583 args['width'] = sx * args['height'] / sy
585 args['height'] = sy * args['width'] / sx
586 return platypus.Image(name, mask=(250,255,250,255,250,255), **args)
587 elif node.localName=='spacer':
588 if node.hasAttribute('width'):
589 width = utils.unit_get(node.getAttribute('width'))
591 width = utils.unit_get('1cm')
592 length = utils.unit_get(node.getAttribute('length'))
593 return platypus.Spacer(width=width, height=length)
594 elif node.localName=='section':
595 return self.render(node)
596 elif node.localName in ('pageBreak', 'nextPage'):
597 return platypus.PageBreak()
598 elif node.localName=='condPageBreak':
599 return platypus.CondPageBreak(**(utils.attr_get(node, ['height'])))
600 elif node.localName=='setNextTemplate':
601 return platypus.NextPageTemplate(str(node.getAttribute('name')))
602 elif node.localName=='nextFrame':
603 return platypus.CondPageBreak(1000) # TODO: change the 1000 !
604 elif node.localName == 'setNextFrame':
605 from reportlab.platypus.doctemplate import NextFrameFlowable
606 return NextFrameFlowable(str(node.getAttribute('name')))
607 elif node.localName == 'currentFrame':
608 from reportlab.platypus.doctemplate import CurrentFrameFlowable
609 return CurrentFrameFlowable(str(node.getAttribute('name')))
610 elif node.localName == 'frameEnd':
611 return EndFrameFlowable()
613 sys.stderr.write('Warning: flowable not yet implemented: %s !\n' % (node.localName,))
616 def render(self, node_story):
618 node = node_story.firstChild
620 if node.nodeType == node.ELEMENT_NODE:
621 flow = self._flowable(node)
623 if type(flow) == type([]):
627 node = node.nextSibling
630 from reportlab.platypus.doctemplate import ActionFlowable
632 class EndFrameFlowable(ActionFlowable):
633 def __init__(self,resume=0):
634 ActionFlowable.__init__(self,('frameEnd',resume))
636 class TinyDocTemplate(platypus.BaseDocTemplate):
638 def ___handle_pageBegin(self):
639 self.page = self.page + 1
640 self.pageTemplate.beforeDrawPage(self.canv,self)
641 self.pageTemplate.checkPageSize(self.canv,self)
642 self.pageTemplate.onPage(self.canv,self)
643 for f in self.pageTemplate.frames: f._reset()
645 #keep a count of flowables added to this page. zero indicates bad stuff
646 self._curPageFlowableCount = 0
647 if hasattr(self,'_nextFrameIndex'):
648 del self._nextFrameIndex
649 for f in self.pageTemplate.frames:
653 self.handle_frameBegin()
655 class _rml_template(object):
656 def __init__(self, out, node, doc, images={}, path='.'):
659 if not node.hasAttribute('pageSize'):
660 pageSize = (utils.unit_get('21cm'), utils.unit_get('29.7cm'))
662 ps = map(lambda x:x.strip(), node.getAttribute('pageSize').replace(')', '').replace('(', '').split(','))
663 pageSize = ( utils.unit_get(ps[0]),utils.unit_get(ps[1]) )
664 cm = reportlab.lib.units.cm
665 self.doc_tmpl = TinyDocTemplate(out, pagesize=pageSize, **utils.attr_get(node, ['leftMargin','rightMargin','topMargin','bottomMargin'], {'allowSplitting':'int','showBoundary':'bool','title':'str','author':'str'}))
666 self.page_templates = []
667 self.styles = doc.styles
669 pts = node.getElementsByTagName('pageTemplate')
672 for frame_el in pt.getElementsByTagName('frame'):
673 frame = platypus.Frame( **(utils.attr_get(frame_el, ['x1','y1', 'width','height', 'leftPadding', 'rightPadding', 'bottomPadding', 'topPadding'], {'id':'str', 'showBoundary':'bool'})) )
674 if utils.attr_get(frame_el, ['last']):
675 frame.lastFrame = True
676 frames.append( frame )
677 gr = pt.getElementsByTagName('pageGraphics')
679 drw = _rml_draw(gr[0], self.doc, images=images, path=self.path)
680 self.page_templates.append( platypus.PageTemplate(frames=frames, onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) ))
682 self.page_templates.append( platypus.PageTemplate(frames=frames, **utils.attr_get(pt, [], {'id':'str'}) ))
683 self.doc_tmpl.addPageTemplates(self.page_templates)
685 def render(self, node_stories):
687 r = _rml_flowable(self.doc,images=self.images, path=self.path)
688 for node_story in node_stories:
689 fis += r.render(node_story)
690 fis.append(platypus.PageBreak())
691 self.doc_tmpl.build(fis)
693 def parseString(data, fout=None, images={}, path='.'):
694 r = _rml_doc(data, images, path)
701 fp = StringIO.StringIO()
706 print 'Usage: trml2pdf input.rml >output.pdf'
707 print 'Render the standard input (RML) and output a PDF file'
710 if __name__=="__main__":
712 if sys.argv[1]=='--help':
714 print parseString(file(sys.argv[1], 'r').read()),
716 print 'Usage: trml2pdf input.rml >output.pdf'
717 print 'Try \'trml2pdf --help\' for more information.'