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)
421 elif n.localName == 'pageNumber':
422 rc += '<pageNumber/>' # TODO: change this !
424 #CHECKME: I wonder if this is useful since we don't stock the result. Maybe for the getName tag?
427 elif n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
428 rc += str2xml(n.data)
429 return rc.encode(encoding, 'replace')
431 def _table(self, node):
436 childs = _child_get(node,'tr')
441 for td in _child_get(tr, 'td'):
443 for n in td.childNodes:
444 if n.nodeType==node.ELEMENT_NODE:
445 flow.append( self._flowable(n) )
447 flow = self._textual(td)
449 if len(data2)>length:
452 while len(ab)<length:
454 while len(data2)<length:
457 if node.hasAttribute('colWidths'):
458 assert length == len(node.getAttribute('colWidths').split(','))
459 colwidths = [utils.unit_get(f.strip()) for f in node.getAttribute('colWidths').split(',')]
460 if node.hasAttribute('rowHeights'):
461 rowheights = [utils.unit_get(f.strip()) for f in node.getAttribute('rowHeights').split(',')]
462 if len(rowheights) == 1:
463 rowheights = rowheights[0]
464 table = platypus.Table(data = data, colWidths=colwidths, rowHeights=rowheights, **(utils.attr_get(node, ['splitByRow'] ,{'repeatRows':'int','repeatCols':'int'})))
465 if node.hasAttribute('style'):
466 table.setStyle(self.styles.table_styles[node.getAttribute('style')])
469 def _illustration(self, node):
470 class Illustration(platypus.flowables.Flowable):
471 def __init__(self, node, styles, self2):
474 self.width = utils.unit_get(node.getAttribute('width'))
475 self.height = utils.unit_get(node.getAttribute('height'))
477 def wrap(self, *args):
478 return (self.width, self.height)
481 drw = _rml_draw(self.node, self.styles, images=self.self2.images, path=self.self2.path)
482 drw.render(self.canv, None)
483 return Illustration(node, self.styles, self)
485 def _textual_image(self, node):
488 for n in node.childNodes:
489 if n.nodeType in (node.CDATA_SECTION_NODE, node.TEXT_NODE):
491 return base64.decodestring(rc)
493 def _flowable(self, node):
494 if node.localName=='para':
495 style = self.styles.para_style_get(node)
496 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
497 elif node.localName=='barCode':
499 from reportlab.graphics.barcode import code128
500 from reportlab.graphics.barcode import code39
501 from reportlab.graphics.barcode import code93
502 from reportlab.graphics.barcode import common
503 from reportlab.graphics.barcode import fourstate
504 from reportlab.graphics.barcode import usps
506 print 'Warning: Reportlab barcode extension not installed !'
508 args = utils.attr_get(node, [], {'ratio':'float','xdim':'unit','height':'unit','checksum':'bool','quiet':'bool'})
510 'codabar': lambda x: common.Codabar(x, **args),
511 'code11': lambda x: common.Code11(x, **args),
512 'code128': lambda x: code128.Code128(x, **args),
513 'standard39': lambda x: code39.Standard39(x, **args),
514 'standard93': lambda x: code93.Standard93(x, **args),
515 'i2of5': lambda x: common.I2of5(x, **args),
516 'extended39': lambda x: code39.Extended39(x, **args),
517 'extended93': lambda x: code93.Extended93(x, **args),
518 'msi': lambda x: common.MSI(x, **args),
519 'fim': lambda x: usps.FIM(x, **args),
520 'postnet': lambda x: usps.POSTNET(x, **args),
523 if node.hasAttribute('code'):
524 code = node.getAttribute('code').lower()
525 return codes[code](self._textual(node))
526 elif node.localName=='name':
527 self.styles.names[ node.getAttribute('id')] = node.getAttribute('value')
529 elif node.localName=='xpre':
530 style = self.styles.para_style_get(node)
531 return platypus.XPreformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int','frags':'int'})))
532 elif node.localName=='pre':
533 style = self.styles.para_style_get(node)
534 return platypus.Preformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int'})))
535 elif node.localName=='illustration':
536 return self._illustration(node)
537 elif node.localName=='blockTable':
538 return self._table(node)
539 elif node.localName=='title':
540 styles = reportlab.lib.styles.getSampleStyleSheet()
541 style = styles['Title']
542 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
543 elif node.localName=='h1':
544 styles = reportlab.lib.styles.getSampleStyleSheet()
545 style = styles['Heading1']
546 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
547 elif node.localName=='h2':
548 styles = reportlab.lib.styles.getSampleStyleSheet()
549 style = styles['Heading2']
550 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
551 elif node.localName=='h3':
552 styles = reportlab.lib.styles.getSampleStyleSheet()
553 style = styles['Heading3']
554 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
555 elif node.localName=='image':
556 if not node.hasAttribute('file'):
557 if node.hasAttribute('name'):
558 image_data = self.doc.images[node.getAttribute('name')].read()
561 image_data = base64.decodestring(node.firstChild.nodeValue)
562 image = StringIO.StringIO(image_data)
563 return platypus.Image(image, mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
565 return platypus.Image(node.getAttribute('file'), mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
567 from reportlab.lib.utils import ImageReader
568 name = str(node.getAttribute('file'))
569 img = ImageReader(name)
570 (sx,sy) = img.getSize()
573 for tag in ('width','height'):
574 if node.hasAttribute(tag):
575 args[tag] = utils.unit_get(node.getAttribute(tag))
576 if ('width' in args) and (not 'height' in args):
577 args['height'] = sy * args['width'] / sx
578 elif ('height' in args) and (not 'width' in args):
579 args['width'] = sx * args['height'] / sy
580 elif ('width' in args) and ('height' in args):
581 if (float(args['width'])/args['height'])>(float(sx)>sy):
582 args['width'] = sx * args['height'] / sy
584 args['height'] = sy * args['width'] / sx
585 return platypus.Image(name, mask=(250,255,250,255,250,255), **args)
586 elif node.localName=='spacer':
587 if node.hasAttribute('width'):
588 width = utils.unit_get(node.getAttribute('width'))
590 width = utils.unit_get('1cm')
591 length = utils.unit_get(node.getAttribute('length'))
592 return platypus.Spacer(width=width, height=length)
593 elif node.localName=='section':
594 return self.render(node)
595 elif node.localName in ('pageBreak', 'nextPage'):
596 return platypus.PageBreak()
597 elif node.localName=='condPageBreak':
598 return platypus.CondPageBreak(**(utils.attr_get(node, ['height'])))
599 elif node.localName=='setNextTemplate':
600 return platypus.NextPageTemplate(str(node.getAttribute('name')))
601 elif node.localName=='nextFrame':
602 return platypus.CondPageBreak(1000) # TODO: change the 1000 !
603 elif node.localName == 'setNextFrame':
604 from reportlab.platypus.doctemplate import NextFrameFlowable
605 return NextFrameFlowable(str(node.getAttribute('name')))
606 elif node.localName == 'currentFrame':
607 from reportlab.platypus.doctemplate import CurrentFrameFlowable
608 return CurrentFrameFlowable(str(node.getAttribute('name')))
609 elif node.localName == 'frameEnd':
610 return EndFrameFlowable()
612 sys.stderr.write('Warning: flowable not yet implemented: %s !\n' % (node.localName,))
615 def render(self, node_story):
617 node = node_story.firstChild
619 if node.nodeType == node.ELEMENT_NODE:
620 flow = self._flowable(node)
622 if type(flow) == type([]):
626 node = node.nextSibling
629 from reportlab.platypus.doctemplate import ActionFlowable
631 class EndFrameFlowable(ActionFlowable):
632 def __init__(self,resume=0):
633 ActionFlowable.__init__(self,('frameEnd',resume))
635 class TinyDocTemplate(platypus.BaseDocTemplate):
637 def ___handle_pageBegin(self):
638 self.page = self.page + 1
639 self.pageTemplate.beforeDrawPage(self.canv,self)
640 self.pageTemplate.checkPageSize(self.canv,self)
641 self.pageTemplate.onPage(self.canv,self)
642 for f in self.pageTemplate.frames: f._reset()
644 #keep a count of flowables added to this page. zero indicates bad stuff
645 self._curPageFlowableCount = 0
646 if hasattr(self,'_nextFrameIndex'):
647 del self._nextFrameIndex
648 for f in self.pageTemplate.frames:
652 self.handle_frameBegin()
654 class _rml_template(object):
655 def __init__(self, out, node, doc, images={}, path='.'):
658 if not node.hasAttribute('pageSize'):
659 pageSize = (utils.unit_get('21cm'), utils.unit_get('29.7cm'))
661 ps = map(lambda x:x.strip(), node.getAttribute('pageSize').replace(')', '').replace('(', '').split(','))
662 pageSize = ( utils.unit_get(ps[0]),utils.unit_get(ps[1]) )
663 cm = reportlab.lib.units.cm
664 self.doc_tmpl = TinyDocTemplate(out, pagesize=pageSize, **utils.attr_get(node, ['leftMargin','rightMargin','topMargin','bottomMargin'], {'allowSplitting':'int','showBoundary':'bool','title':'str','author':'str'}))
665 self.page_templates = []
666 self.styles = doc.styles
668 pts = node.getElementsByTagName('pageTemplate')
671 for frame_el in pt.getElementsByTagName('frame'):
672 frame = platypus.Frame( **(utils.attr_get(frame_el, ['x1','y1', 'width','height', 'leftPadding', 'rightPadding', 'bottomPadding', 'topPadding'], {'id':'str', 'showBoundary':'bool'})) )
673 if utils.attr_get(frame_el, ['last']):
674 frame.lastFrame = True
675 frames.append( frame )
676 gr = pt.getElementsByTagName('pageGraphics')
678 drw = _rml_draw(gr[0], self.doc, images=images, path=self.path)
679 self.page_templates.append( platypus.PageTemplate(frames=frames, onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) ))
681 self.page_templates.append( platypus.PageTemplate(frames=frames, **utils.attr_get(pt, [], {'id':'str'}) ))
682 self.doc_tmpl.addPageTemplates(self.page_templates)
684 def render(self, node_stories):
686 r = _rml_flowable(self.doc,images=self.images, path=self.path)
687 for node_story in node_stories:
688 fis += r.render(node_story)
689 fis.append(platypus.PageBreak())
690 self.doc_tmpl.build(fis)
692 def parseString(data, fout=None, images={}, path='.'):
693 r = _rml_doc(data, images, path)
700 fp = StringIO.StringIO()
705 print 'Usage: trml2pdf input.rml >output.pdf'
706 print 'Render the standard input (RML) and output a PDF file'
709 if __name__=="__main__":
711 if sys.argv[1]=='--help':
713 print parseString(file(sys.argv[1], 'r').read()),
715 print 'Usage: trml2pdf input.rml >output.pdf'
716 print 'Try \'trml2pdf --help\' for more information.'