1 # -*- encoding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>). All Rights Reserved
8 # This program is free software: you can redistribute it and/or modify
9 # it under the terms of the GNU General Public License as published by
10 # the Free Software Foundation, either version 3 of the License, or
11 # (at your option) any later version.
13 # This program is distributed in the hope that it will be useful,
14 # but WITHOUT ANY WARRANTY; without even the implied warranty of
15 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 # GNU General Public License for more details.
18 # You should have received a copy of the GNU General Public License
19 # along with this program. If not, see <http://www.gnu.org/lic enses/>.
21 ##############################################################################
25 from StringIO import StringIO
29 from reportlab.pdfgen import canvas
30 from reportlab import platypus
35 from lxml import etree
37 from reportlab.platypus.doctemplate import ActionFlowable
41 class PageCount(platypus.Flowable):
43 self.canv.beginForm("pageCount")
44 self.canv.setFont("Helvetica", utils.unit_get(str(8)))
45 self.canv.drawString(0, 0, str(self.canv.getPageNumber()))
48 class PageReset(platypus.Flowable):
50 self.canv._pageNumber = 0
52 class _rml_styles(object,):
53 def __init__(self, nodes, localcontext):
54 self.localcontext = localcontext
57 self.table_styles = {}
59 for style in node.findall('blockTableStyle'):
60 self.table_styles[style.get('id')] = self._table_style_get(style)
61 for style in node.findall('paraStyle'):
62 self.styles[style.get('name')] = self._para_style_update(style)
63 for variable in node.findall('initialize'):
64 for name in variable.findall('name'):
65 self.names[ name.get('id')] = name.get('value')
67 def _para_style_update(self, node):
69 for attr in ['textColor', 'backColor', 'bulletColor', 'borderColor']:
71 data[attr] = color.get(node.get(attr))
72 for attr in ['fontName', 'bulletFontName', 'bulletText']:
74 data[attr] = node.get(attr)
75 for attr in ['fontSize', 'leftIndent', 'rightIndent', 'spaceBefore', 'spaceAfter',
76 'firstLineIndent', 'bulletIndent', 'bulletFontSize', 'leading',
77 'borderWidth','borderPadding','borderRadius']:
79 data[attr] = utils.unit_get(node.get(attr))
80 if node.get('alignment'):
82 'right':reportlab.lib.enums.TA_RIGHT,
83 'center':reportlab.lib.enums.TA_CENTER,
84 'justify':reportlab.lib.enums.TA_JUSTIFY
86 data['alignment'] = align.get(node.get('alignment').lower(), reportlab.lib.enums.TA_LEFT)
89 def _table_style_get(self, style_node):
91 for node in style_node.getchildren():
92 start = utils.tuple_int_get(node, 'start', (0,0) )
93 stop = utils.tuple_int_get(node, 'stop', (-1,-1) )
94 if node.tag=='blockValign':
95 styles.append(('VALIGN', start, stop, str(node.get('value'))))
96 elif node.tag=='blockFont':
97 styles.append(('FONT', start, stop, str(node.get('name'))))
98 elif node.tag=='blockTextColor':
99 styles.append(('TEXTCOLOR', start, stop, color.get(str(node.get('colorName')))))
100 elif node.tag=='blockLeading':
101 styles.append(('LEADING', start, stop, utils.unit_get(node.get('length'))))
102 elif node.tag=='blockAlignment':
103 styles.append(('ALIGNMENT', start, stop, str(node.get('value'))))
104 elif node.tag=='blockSpan':
105 styles.append(('SPAN', start, stop))
106 elif node.tag=='blockLeftPadding':
107 styles.append(('LEFTPADDING', start, stop, utils.unit_get(node.get('length'))))
108 elif node.tag=='blockRightPadding':
109 styles.append(('RIGHTPADDING', start, stop, utils.unit_get(node.get('length'))))
110 elif node.tag=='blockTopPadding':
111 styles.append(('TOPPADDING', start, stop, utils.unit_get(node.get('length'))))
112 elif node.tag=='blockBottomPadding':
113 styles.append(('BOTTOMPADDING', start, stop, utils.unit_get(node.get('length'))))
114 elif node.tag=='blockBackground':
115 styles.append(('BACKGROUND', start, stop, color.get(node.get('colorName'))))
117 styles.append(('FONTSIZE', start, stop, utils.unit_get(node.get('size'))))
118 elif node.tag=='lineStyle':
119 kind = node.get('kind')
120 kind_list = [ 'GRID', 'BOX', 'OUTLINE', 'INNERGRID', 'LINEBELOW', 'LINEABOVE','LINEBEFORE', 'LINEAFTER' ]
121 assert kind in kind_list
123 if node.get('thickness'):
124 thick = float(node.get('thickness'))
125 styles.append((kind, start, stop, thick, color.get(node.get('colorName'))))
126 return platypus.tables.TableStyle(styles)
128 def para_style_get(self, node):
130 if node.get('style'):
131 if node.get('style') in self.styles:
132 styles = reportlab.lib.styles.getSampleStyleSheet()
133 sname = node.get('style')
134 style = reportlab.lib.styles.ParagraphStyle(sname, styles["Normal"], **self.styles[sname])
136 sys.stderr.write('Warning: style not found, %s - setting default!\n' % (node.get('style'),) )
138 styles = reportlab.lib.styles.getSampleStyleSheet()
139 style = copy.deepcopy(styles['Normal'])
140 style.__dict__.update(self._para_style_update(node))
143 class _rml_doc(object):
144 def __init__(self, node, localcontext, images={}, path='.', title=None):
145 self.localcontext = localcontext
147 self.filename = self.etree.get('filename')
152 def docinit(self, els):
153 from reportlab.lib.fonts import addMapping
154 from reportlab.pdfbase import pdfmetrics
155 from reportlab.pdfbase.ttfonts import TTFont
158 for font in node.findall('registerFont'):
159 name = font.get('fontName').encode('ascii')
160 fname = font.get('fontFile').encode('ascii')
161 pdfmetrics.registerFont(TTFont(name, fname ))
162 addMapping(name, 0, 0, name) #normal
163 addMapping(name, 0, 1, name) #italic
164 addMapping(name, 1, 0, name) #bold
165 addMapping(name, 1, 1, name) #italic and bold
167 def _textual_image(self, node):
169 for n in node.getchildren():
170 rc +=( etree.tostring(n) or '') + n.tail
171 return base64.decodestring(node.tostring())
173 def _images(self, el):
175 for node in el.findall('image'):
176 rc =( node.text or '')
177 result[node.get('name')] = base64.decodestring(rc)
180 def render(self, out):
181 el = self.etree.findall('docinit')
185 el = self.etree.findall('stylesheet')
186 self.styles = _rml_styles(el,self.localcontext)
188 el = self.etree.findall('images')
190 self.images.update( self._images(el[0]) )
191 el = self.etree.findall('template')
193 pt_obj = _rml_template(self.localcontext, out, el[0], self, images=self.images, path=self.path, title=self.title)
194 el = utils._child_get(self.etree, self, 'story')
197 self.canvas = canvas.Canvas(out)
198 pd = self.etree.find('pageDrawing')[0]
199 pd_obj = _rml_canvas(self.canvas, self.localcontext, None, self, self.images, path=self.path, title=self.title)
202 self.canvas.showPage()
205 class _rml_canvas(object):
206 def __init__(self, canvas, localcontext, doc_tmpl=None, doc=None, images={}, path='.', title=None):
207 self.localcontext = localcontext
209 self.styles = doc.styles
210 self.doc_tmpl = doc_tmpl
216 self.canvas.setTitle(self.title)
218 def _textual(self, node, x=0, y=0):
219 text = node.text and node.text.encode('utf-8') or ''
220 rc = utils._process_text(self, text)
223 from reportlab.lib.sequencer import getSequencer
225 rc += str(seq.next(n.get('id')))
226 if n.tag == 'pageCount':
228 self.canvas.translate(x,y)
229 self.canvas.doForm('pageCount')
231 self.canvas.translate(-x,-y)
232 if n.tag == 'pageNumber':
233 rc += str(self.canvas.getPageNumber())
234 rc += utils._process_text(self, n.tail)
237 def _drawString(self, node):
238 v = utils.attr_get(node, ['x','y'])
239 text=self._textual(node, **v)
240 text = utils.xml2str(text)
241 self.canvas.drawString(text=text, **v)
243 def _drawCenteredString(self, node):
244 v = utils.attr_get(node, ['x','y'])
245 text=self._textual(node, **v)
246 text = utils.xml2str(text)
247 self.canvas.drawCentredString(text=text, **v)
249 def _drawRightString(self, node):
250 v = utils.attr_get(node, ['x','y'])
251 text=self._textual(node, **v)
252 text = utils.xml2str(text)
253 self.canvas.drawRightString(text=text, **v)
255 def _rect(self, node):
256 if node.get('round'):
257 self.canvas.roundRect(radius=utils.unit_get(node.get('round')), **utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
259 self.canvas.rect(**utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
261 def _ellipse(self, node):
262 x1 = utils.unit_get(node.get('x'))
263 x2 = utils.unit_get(node.get('width'))
264 y1 = utils.unit_get(node.get('y'))
265 y2 = utils.unit_get(node.get('height'))
267 self.canvas.ellipse(x1,y1,x2,y2, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
269 def _curves(self, node):
270 line_str = node.text.split()
272 while len(line_str)>7:
273 self.canvas.bezier(*[utils.unit_get(l) for l in line_str[0:8]])
274 line_str = line_str[8:]
276 def _lines(self, node):
277 line_str = node.text.split()
279 while len(line_str)>3:
280 lines.append([utils.unit_get(l) for l in line_str[0:4]])
281 line_str = line_str[4:]
282 self.canvas.lines(lines)
284 def _grid(self, node):
285 xlist = [utils.unit_get(s) for s in node.get('xs').split(',')]
286 ylist = [utils.unit_get(s) for s in node.get('ys').split(',')]
288 self.canvas.grid(xlist, ylist)
290 def _translate(self, node):
291 dx = utils.unit_get(node.get('dx')) or 0
292 dy = utils.unit_get(node.get('dy')) or 0
293 self.canvas.translate(dx,dy)
295 def _circle(self, node):
296 self.canvas.circle(x_cen=utils.unit_get(node.get('x')), y_cen=utils.unit_get(node.get('y')), r=utils.unit_get(node.get('radius')), **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
298 def _place(self, node):
299 flows = _rml_flowable(self.doc, self.localcontext, images=self.images, path=self.path, title=self.title).render(node)
300 infos = utils.attr_get(node, ['x','y','width','height'])
302 infos['y']+=infos['height']
304 w,h = flow.wrap(infos['width'], infos['height'])
305 if w<=infos['width'] and h<=infos['height']:
307 flow.drawOn(self.canvas,infos['x'],infos['y'])
310 raise ValueError, "Not enough space"
312 def _line_mode(self, node):
313 ljoin = {'round':1, 'mitered':0, 'bevelled':2}
314 lcap = {'default':0, 'round':1, 'square':2}
316 if node.get('width'):
317 self.canvas.setLineWidth(utils.unit_get(node.get('width')))
319 self.canvas.setLineJoin(ljoin[node.get('join')])
321 self.canvas.setLineCap(lcap[node.get('cap')])
322 if node.get('miterLimit'):
323 self.canvas.setDash(utils.unit_get(node.get('miterLimit')))
325 dashes = node.get('dash').split(',')
326 for x in range(len(dashes)):
327 dashes[x]=utils.unit_get(dashes[x])
328 self.canvas.setDash(node.get('dash').split(','))
330 def _image(self, node):
332 from reportlab.lib.utils import ImageReader
333 if not node.get('file') :
335 image_data = self.images[node.get('name')]
336 s = cStringIO.StringIO(image_data)
339 if self.localcontext:
340 res = utils._regex.findall(node.text)
342 newtext = eval(key, {}, self.localcontext)
344 image_data = base64.decodestring(node.text)
345 if not image_data: return False
346 s = cStringIO.StringIO(image_data)
348 if node.get('file') in self.images:
349 s = cStringIO.StringIO(self.images[node.get('file')])
352 u = urllib.urlopen(str(node.get('file')))
353 s = cStringIO.StringIO(u.read())
355 u = file(os.path.join(self.path,str(node.get('file'))), 'rb')
356 s = cStringIO.StringIO(u.read())
358 (sx,sy) = img.getSize()
361 for tag in ('width','height','x','y'):
363 args[tag] = utils.unit_get(node.get(tag))
364 if ('width' in args) and (not 'height' in args):
365 args['height'] = sy * args['width'] / sx
366 elif ('height' in args) and (not 'width' in args):
367 args['width'] = sx * args['height'] / sy
368 elif ('width' in args) and ('height' in args):
369 if (float(args['width'])/args['height'])>(float(sx)>sy):
370 args['width'] = sx * args['height'] / sy
372 args['height'] = sy * args['width'] / sx
373 self.canvas.drawImage(img, **args)
375 def _path(self, node):
376 self.path = self.canvas.beginPath()
377 self.path.moveTo(**utils.attr_get(node, ['x','y']))
378 for n in utils._child_get(node, self):
381 vals = utils.text_get(n).split()
382 self.path.moveTo(utils.unit_get(vals[0]), utils.unit_get(vals[1]))
383 elif n.tag=='curvesto':
384 vals = utils.text_get(n).split()
388 pos.append(utils.unit_get(vals.pop(0)))
389 self.path.curveTo(*pos)
391 data = n.text.split() # Not sure if I must merge all TEXT_NODE ?
393 x = utils.unit_get(data.pop(0))
394 y = utils.unit_get(data.pop(0))
395 self.path.lineTo(x,y)
396 if (not node.get('close')) or utils.bool_get(node.get('close')):
398 self.canvas.drawPath(self.path, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
400 def render(self, node):
402 'drawCentredString': self._drawCenteredString,
403 'drawRightString': self._drawRightString,
404 'drawString': self._drawString,
406 'ellipse': self._ellipse,
407 'lines': self._lines,
409 'curves': self._curves,
410 'fill': lambda node: self.canvas.setFillColor(color.get(node.get('color'))),
411 'stroke': lambda node: self.canvas.setStrokeColor(color.get(node.get('color'))),
412 'setFont': lambda node: self.canvas.setFont(node.get('name'), utils.unit_get(node.get('size'))),
413 'place': self._place,
414 'circle': self._circle,
415 'lineMode': self._line_mode,
417 'rotate': lambda node: self.canvas.rotate(float(node.get('degrees'))),
418 'translate': self._translate,
421 for n in utils._child_get(node, self):
425 class _rml_draw(object):
426 def __init__(self, localcontext ,node, styles, images={}, path='.', title=None):
427 self.localcontext = localcontext
433 self.canvas_title = title
435 def render(self, canvas, doc):
437 cnv = _rml_canvas(canvas, self.localcontext, doc, self.styles, images=self.images, path=self.path, title=self.canvas_title)
438 cnv.render(self.node)
439 canvas.restoreState()
441 class _rml_flowable(object):
442 def __init__(self, doc, localcontext, images={}, path='.', title=None):
443 self.localcontext = localcontext
445 self.styles = doc.styles
450 def _textual(self, node):
451 rc1 = utils._process_text(self, node.text or '')
452 for n in utils._child_get(node,self):
453 txt_n = copy.deepcopy(n)
454 for key in txt_n.attrib.keys():
455 if key in ('rml_except', 'rml_loop', 'rml_tag'):
456 del txt_n.attrib[key]
457 if True or not self._textual(n).isspace():
458 txt_n.text = utils.xml2str(self._textual(n))
460 rc1 += etree.tostring(txt_n)
461 #rc1 += utils._process_text(self, node.tail or '')
464 def _table(self, node):
465 childs = utils._child_get(node,self,'tr')
477 st = copy.deepcopy(self.styles.table_styles[tr.get('style')])
482 if tr.get('paraStyle'):
483 paraStyle = self.styles.styles[tr.get('paraStyle')]
486 for td in utils._child_get(tr, self,'td'):
488 st = copy.deepcopy(self.styles.table_styles[td.get('style')])
495 if td.get('paraStyle'):
497 paraStyle = self.styles.styles[td.get('paraStyle')]
501 for n in utils._child_get(td, self):
502 if n.tag == etree.Comment:
505 fl = self._flowable(n, extra_style=paraStyle)
506 if isinstance(fl,list):
512 flow = self._textual(td)
514 if len(data2)>length:
517 while len(ab)<length:
519 while len(data2)<length:
524 if node.get('colWidths'):
525 assert length == len(node.get('colWidths').split(','))
526 colwidths = [utils.unit_get(f.strip()) for f in node.get('colWidths').split(',')]
527 if node.get('rowHeights'):
528 rowheights = [utils.unit_get(f.strip()) for f in node.get('rowHeights').split(',')]
529 if len(rowheights) == 1:
530 rowheights = rowheights[0]
531 table = platypus.LongTable(data = data, colWidths=colwidths, rowHeights=rowheights, **(utils.attr_get(node, ['splitByRow'] ,{'repeatRows':'int','repeatCols':'int'})))
532 if node.get('style'):
533 table.setStyle(self.styles.table_styles[node.get('style')])
538 def _illustration(self, node):
539 class Illustration(platypus.flowables.Flowable):
540 def __init__(self, node, localcontext, styles, self2):
541 self.localcontext = localcontext
544 self.width = utils.unit_get(node.get('width'))
545 self.height = utils.unit_get(node.get('height'))
547 def wrap(self, *args):
548 return (self.width, self.height)
551 drw = _rml_draw(self.localcontext ,self.node,self.styles, images=self.self2.images, path=self.self2.path, title=self.self2.title)
552 drw.render(self.canv, None)
553 return Illustration(node, self.localcontext, self.styles, self)
555 def _textual_image(self, node):
556 return base64.decodestring(node.text)
558 def _flowable(self, node, extra_style=None):
560 style = self.styles.para_style_get(node)
562 style.__dict__.update(extra_style)
564 for i in self._textual(node).split('\n'):
565 result.append(platypus.Paragraph(i, style, **(utils.attr_get(node, [], {'bulletText':'str'}))))
567 elif node.tag=='barCode':
569 from reportlab.graphics.barcode import code128
570 from reportlab.graphics.barcode import code39
571 from reportlab.graphics.barcode import code93
572 from reportlab.graphics.barcode import common
573 from reportlab.graphics.barcode import fourstate
574 from reportlab.graphics.barcode import usps
577 args = utils.attr_get(node, [], {'ratio':'float','xdim':'unit','height':'unit','checksum':'int','quiet':'int','width':'unit','stop':'bool','bearers':'int','barWidth':'float','barHeight':'float'})
579 'codabar': lambda x: common.Codabar(x, **args),
580 'code11': lambda x: common.Code11(x, **args),
581 'code128': lambda x: code128.Code128(x, **args),
582 'standard39': lambda x: code39.Standard39(x, **args),
583 'standard93': lambda x: code93.Standard93(x, **args),
584 'i2of5': lambda x: common.I2of5(x, **args),
585 'extended39': lambda x: code39.Extended39(x, **args),
586 'extended93': lambda x: code93.Extended93(x, **args),
587 'msi': lambda x: common.MSI(x, **args),
588 'fim': lambda x: usps.FIM(x, **args),
589 'postnet': lambda x: usps.POSTNET(x, **args),
593 code = node.get('code').lower()
594 return codes[code](self._textual(node))
595 elif node.tag=='name':
596 self.styles.names[ node.get('id')] = node.get('value')
598 elif node.tag=='xpre':
599 style = self.styles.para_style_get(node)
600 return platypus.XPreformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int','frags':'int'})))
601 elif node.tag=='pre':
602 style = self.styles.para_style_get(node)
603 return platypus.Preformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int'})))
604 elif node.tag=='illustration':
605 return self._illustration(node)
606 elif node.tag=='blockTable':
607 return self._table(node)
608 elif node.tag=='title':
609 styles = reportlab.lib.styles.getSampleStyleSheet()
610 style = styles['Title']
611 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
612 elif re.match('^h([1-9]+[0-9]*)$', (node.text or '')):
613 styles = reportlab.lib.styles.getSampleStyleSheet()
614 style = styles['Heading'+str(node.get[1:])]
615 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
616 elif node.tag=='image':
617 if not node.get('file'):
619 image_data = self.doc.images[node.get('name')].read()
622 if self.localcontext:
623 newtext = utils._process_text(self, node.text or '')
625 image_data = base64.decodestring(node.text)
626 if not image_data: return False
627 image = cStringIO.StringIO(image_data)
628 return platypus.Image(image, mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
630 return platypus.Image(node.get('file'), mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
631 from reportlab.lib.utils import ImageReader
632 name = str(node.get('file'))
633 img = ImageReader(name)
634 (sx,sy) = img.getSize()
636 for tag in ('width','height'):
638 args[tag] = utils.unit_get(node.get(tag))
639 if ('width' in args) and (not 'height' in args):
640 args['height'] = sy * args['width'] / sx
641 elif ('height' in args) and (not 'width' in args):
642 args['width'] = sx * args['height'] / sy
643 elif ('width' in args) and ('height' in args):
644 if (float(args['width'])/args['height'])>(float(sx)>sy):
645 args['width'] = sx * args['height'] / sy
647 args['height'] = sy * args['width'] / sx
648 return platypus.Image(name, mask=(250,255,250,255,250,255), **args)
649 elif node.tag=='spacer':
650 if node.get('width'):
651 width = utils.unit_get(node.get('width'))
653 width = utils.unit_get('1cm')
654 length = utils.unit_get(node.get('length'))
655 return platypus.Spacer(width=width, height=length)
656 elif node.tag=='section':
657 return self.render(node)
658 elif node.tag == 'pageNumberReset':
660 elif node.tag in ('pageBreak', 'nextPage'):
661 return platypus.PageBreak()
662 elif node.tag=='condPageBreak':
663 return platypus.CondPageBreak(**(utils.attr_get(node, ['height'])))
664 elif node.tag=='setNextTemplate':
665 return platypus.NextPageTemplate(str(node.get('name')))
666 elif node.tag=='nextFrame':
667 return platypus.CondPageBreak(1000) # TODO: change the 1000 !
668 elif node.tag == 'setNextFrame':
669 from reportlab.platypus.doctemplate import NextFrameFlowable
670 return NextFrameFlowable(str(node.get('name')))
671 elif node.tag == 'currentFrame':
672 from reportlab.platypus.doctemplate import CurrentFrameFlowable
673 return CurrentFrameFlowable(str(node.get('name')))
674 elif node.tag == 'frameEnd':
675 return EndFrameFlowable()
676 elif node.tag == 'hr':
677 width_hr=node.get('width') or '100%'
678 color_hr=node.get('color') or 'black'
679 thickness_hr=node.get('thickness') or 1
680 lineCap_hr=node.get('lineCap') or 'round'
681 return platypus.flowables.HRFlowable(width=width_hr,color=color.get(color_hr),thickness=float(thickness_hr),lineCap=str(lineCap_hr))
683 sys.stderr.write('Warning: flowable not yet implemented: %s !\n' % (node.tag,))
686 def render(self, node_story):
687 def process_story(node_story):
689 for node in utils._child_get(node_story, self):
690 if node.tag == etree.Comment:
693 flow = self._flowable(node)
695 if isinstance(flow,list):
696 sub_story = sub_story + flow
698 sub_story.append(flow)
700 return process_story(node_story)
703 class EndFrameFlowable(ActionFlowable):
704 def __init__(self,resume=0):
705 ActionFlowable.__init__(self,('frameEnd',resume))
707 class TinyDocTemplate(platypus.BaseDocTemplate):
708 def ___handle_pageBegin(self):
709 self.page = self.page + 1
710 self.pageTemplate.beforeDrawPage(self.canv,self)
711 self.pageTemplate.checkPageSize(self.canv,self)
712 self.pageTemplate.onPage(self.canv,self)
713 for f in self.pageTemplate.frames: f._reset()
715 self._curPageFlowableCount = 0
716 if hasattr(self,'_nextFrameIndex'):
717 del self._nextFrameIndex
718 for f in self.pageTemplate.frames:
722 self.handle_frameBegin()
723 def afterFlowable(self, flowable):
724 if isinstance(flowable, PageReset):
725 self.canv._pageNumber = 0
727 class _rml_template(object):
728 def __init__(self, localcontext, out, node, doc, images={}, path='.', title=None):
729 self.localcontext = localcontext
733 if not node.get('pageSize'):
734 pageSize = (utils.unit_get('21cm'), utils.unit_get('29.7cm'))
736 ps = map(lambda x:x.strip(), node.get('pageSize').replace(')', '').replace('(', '').split(','))
737 pageSize = ( utils.unit_get(ps[0]),utils.unit_get(ps[1]) )
739 self.doc_tmpl = TinyDocTemplate(out, pagesize=pageSize, **utils.attr_get(node, ['leftMargin','rightMargin','topMargin','bottomMargin'], {'allowSplitting':'int','showBoundary':'bool','title':'str','author':'str'}))
740 self.page_templates = []
741 self.styles = doc.styles
743 pts = node.findall('pageTemplate')
746 for frame_el in pt.findall('frame'):
747 frame = platypus.Frame( **(utils.attr_get(frame_el, ['x1','y1', 'width','height', 'leftPadding', 'rightPadding', 'bottomPadding', 'topPadding'], {'id':'str', 'showBoundary':'bool'})) )
748 if utils.attr_get(frame_el, ['last']):
749 frame.lastFrame = True
750 frames.append( frame )
752 gr = pt.findall('pageGraphics') or pt.getchildren()[1].findall('pageGraphics')
756 drw = _rml_draw(self.localcontext,gr[0], self.doc, images=images, path=self.path, title=self.title)
757 self.page_templates.append( platypus.PageTemplate(frames=frames, onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) ))
759 drw = _rml_draw(self.localcontext,node,self.doc,title=self.title)
760 self.page_templates.append( platypus.PageTemplate(frames=frames,onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) ))
761 self.doc_tmpl.addPageTemplates(self.page_templates)
763 def render(self, node_stories):
765 r = _rml_flowable(self.doc,self.localcontext, images=self.images, path=self.path, title=self.title)
767 for node_story in node_stories:
768 fis += r.render(node_story)
769 if self.localcontext:
771 if story_cnt == len(self.localcontext['objects']):
772 fis.append(PageCount())
773 fis.append(platypus.PageBreak())
774 self.doc_tmpl.build(fis)
776 def parseNode(rml, localcontext = {},fout=None, images={}, path='.',title=None):
777 node = etree.XML(rml)
778 r = _rml_doc(node, localcontext, images, path, title=title)
779 fp = cStringIO.StringIO()
783 def parseString(rml, localcontext = {},fout=None, images={}, path='.',title=None):
784 node = etree.XML(rml)
785 r = _rml_doc(node, localcontext, images, path, title=title)
792 fp = cStringIO.StringIO()
797 print 'Usage: trml2pdf input.rml >output.pdf'
798 print 'Render the standard input (RML) and output a PDF file'
801 if __name__=="__main__":
803 if sys.argv[1]=='--help':
805 print parseString(file(sys.argv[1], 'r').read()),
807 print 'Usage: trml2pdf input.rml >output.pdf'
808 print 'Try \'trml2pdf --help\' for more information.'