1 # -*- coding: utf-8 -*-
2 ##############################################################################
4 # OpenERP, Open Source Management Solution
5 # Copyright (C) 2004-2009 Tiny SPRL (<http://tiny.be>).
7 # This program is free software: you can redistribute it and/or modify
8 # it under the terms of the GNU Affero General Public License as
9 # published by the Free Software Foundation, either version 3 of the
10 # License, or (at your option) any later version.
12 # This program is distributed in the hope that it will be useful,
13 # but WITHOUT ANY WARRANTY; without even the implied warranty of
14 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15 # GNU Affero General Public License for more details.
17 # You should have received a copy of the GNU Affero General Public License
18 # along with this program. If not, see <http://www.gnu.org/licenses/>.
20 ##############################################################################
27 from reportlab.pdfgen import canvas
28 from reportlab import platypus
33 from lxml import etree
35 from reportlab.platypus.doctemplate import ActionFlowable
36 from tools.safe_eval import safe_eval as eval
37 from reportlab.lib.units import inch,cm,mm
38 from tools.misc import file_open
39 from reportlab.pdfbase import pdfmetrics
42 from cStringIO import StringIO
43 _hush_pyflakes = [ StringIO ]
45 from StringIO import StringIO
49 def _open_image(filename, path=None):
50 """Attempt to open a binary file and return the descriptor
52 if os.path.isfile(filename):
53 return open(filename, 'rb')
54 for p in (path or []):
55 if p and os.path.isabs(p):
56 fullpath = os.path.join(p, filename)
57 if os.path.isfile(fullpath):
58 return open(fullpath, 'rb')
61 fullpath = os.path.join(p, filename)
64 return file_open(fullpath)
67 raise IOError("File %s cannot be found in image path" % filename)
69 class NumberedCanvas(canvas.Canvas):
70 def __init__(self, *args, **kwargs):
71 canvas.Canvas.__init__(self, *args, **kwargs)
84 self.pages.update({self._currentPage:self._pageCount})
85 self._codes.append({'code': self._code, 'stack': self._codeStack})
90 if self.pages.get(self._pageCounter,False):
94 if not self.pages.get(key,False):
95 while not self.pages.get(key,False):
97 self.setFont("Helvetica", 8)
98 self.drawRightString((self._pagesize[0]-30), (self._pagesize[1]-40),
99 "Page %(this)i of %(total)i" % {
100 'this': self._pageNumber+1,
101 'total': self.pages.get(key,False),
106 """add page info to each page (page x of y)"""
109 for code in self._codes:
110 self._code = code['code']
111 self._codeStack = code['stack']
113 canvas.Canvas.showPage(self)
114 # self.restoreState()
115 self._doc.SaveToFile(self._filename, self)
117 class PageCount(platypus.Flowable):
119 self.canv.beginForm("pageCount")
120 self.canv.setFont("Helvetica", utils.unit_get(str(8)))
121 self.canv.drawString(0, 0, str(self.canv.getPageNumber()))
124 class PageReset(platypus.Flowable):
126 self.canv._pageNumber = 0
128 class _rml_styles(object,):
129 def __init__(self, nodes, localcontext):
130 self.localcontext = localcontext
134 self.table_styles = {}
135 self.default_style = reportlab.lib.styles.getSampleStyleSheet()
138 for style in node.findall('blockTableStyle'):
139 self.table_styles[style.get('id')] = self._table_style_get(style)
140 for style in node.findall('paraStyle'):
141 sname = style.get('name')
142 self.styles[sname] = self._para_style_update(style)
144 self.styles_obj[sname] = reportlab.lib.styles.ParagraphStyle(sname, self.default_style["Normal"], **self.styles[sname])
146 for variable in node.findall('initialize'):
147 for name in variable.findall('name'):
148 self.names[ name.get('id')] = name.get('value')
150 def _para_style_update(self, node):
152 for attr in ['textColor', 'backColor', 'bulletColor', 'borderColor']:
154 data[attr] = color.get(node.get(attr))
155 for attr in ['fontName', 'bulletFontName', 'bulletText']:
157 data[attr] = node.get(attr)
158 for attr in ['fontSize', 'leftIndent', 'rightIndent', 'spaceBefore', 'spaceAfter',
159 'firstLineIndent', 'bulletIndent', 'bulletFontSize', 'leading',
160 'borderWidth','borderPadding','borderRadius']:
162 data[attr] = utils.unit_get(node.get(attr))
163 if node.get('alignment'):
165 'right':reportlab.lib.enums.TA_RIGHT,
166 'center':reportlab.lib.enums.TA_CENTER,
167 'justify':reportlab.lib.enums.TA_JUSTIFY
169 data['alignment'] = align.get(node.get('alignment').lower(), reportlab.lib.enums.TA_LEFT)
172 def _table_style_get(self, style_node):
174 for node in style_node:
175 start = utils.tuple_int_get(node, 'start', (0,0) )
176 stop = utils.tuple_int_get(node, 'stop', (-1,-1) )
177 if node.tag=='blockValign':
178 styles.append(('VALIGN', start, stop, str(node.get('value'))))
179 elif node.tag=='blockFont':
180 styles.append(('FONT', start, stop, str(node.get('name'))))
181 elif node.tag=='blockTextColor':
182 styles.append(('TEXTCOLOR', start, stop, color.get(str(node.get('colorName')))))
183 elif node.tag=='blockLeading':
184 styles.append(('LEADING', start, stop, utils.unit_get(node.get('length'))))
185 elif node.tag=='blockAlignment':
186 styles.append(('ALIGNMENT', start, stop, str(node.get('value'))))
187 elif node.tag=='blockSpan':
188 styles.append(('SPAN', start, stop))
189 elif node.tag=='blockLeftPadding':
190 styles.append(('LEFTPADDING', start, stop, utils.unit_get(node.get('length'))))
191 elif node.tag=='blockRightPadding':
192 styles.append(('RIGHTPADDING', start, stop, utils.unit_get(node.get('length'))))
193 elif node.tag=='blockTopPadding':
194 styles.append(('TOPPADDING', start, stop, utils.unit_get(node.get('length'))))
195 elif node.tag=='blockBottomPadding':
196 styles.append(('BOTTOMPADDING', start, stop, utils.unit_get(node.get('length'))))
197 elif node.tag=='blockBackground':
198 styles.append(('BACKGROUND', start, stop, color.get(node.get('colorName'))))
200 styles.append(('FONTSIZE', start, stop, utils.unit_get(node.get('size'))))
201 elif node.tag=='lineStyle':
202 kind = node.get('kind')
203 kind_list = [ 'GRID', 'BOX', 'OUTLINE', 'INNERGRID', 'LINEBELOW', 'LINEABOVE','LINEBEFORE', 'LINEAFTER' ]
204 assert kind in kind_list
206 if node.get('thickness'):
207 thick = float(node.get('thickness'))
208 styles.append((kind, start, stop, thick, color.get(node.get('colorName'))))
209 return platypus.tables.TableStyle(styles)
211 def para_style_get(self, node):
213 sname = node.get('style')
215 if sname in self.styles_obj:
216 style = self.styles_obj[sname]
218 sys.stderr.write('Warning: style not found, %s - setting default!\n' % (node.get('style'),) )
220 style = self.default_style['Normal']
221 para_update = self._para_style_update(node)
223 # update style only is necessary
224 style = copy.deepcopy(style)
225 style.__dict__.update(para_update)
228 class _rml_doc(object):
229 def __init__(self, node, localcontext, images={}, path='.', title=None):
230 self.localcontext = localcontext
232 self.filename = self.etree.get('filename')
237 def docinit(self, els):
238 from reportlab.lib.fonts import addMapping
239 from reportlab.pdfbase import pdfmetrics
240 from reportlab.pdfbase.ttfonts import TTFont
243 for font in node.findall('registerFont'):
244 name = font.get('fontName').encode('ascii')
245 fname = font.get('fontFile').encode('ascii')
246 if name not in pdfmetrics._fonts:
247 pdfmetrics.registerFont(TTFont(name, fname ))
248 addMapping(name, 0, 0, name) #normal
249 addMapping(name, 0, 1, name) #italic
250 addMapping(name, 1, 0, name) #bold
251 addMapping(name, 1, 1, name) #italic and bold
253 def setTTFontMapping(self,face, fontname, filename, mode='all'):
254 from reportlab.lib.fonts import addMapping
255 from reportlab.pdfbase import pdfmetrics
256 from reportlab.pdfbase.ttfonts import TTFont
258 if fontname not in pdfmetrics._fonts:
259 pdfmetrics.registerFont(TTFont(fontname, filename ))
261 addMapping(face, 0, 0, fontname) #normal
262 addMapping(face, 0, 1, fontname) #italic
263 addMapping(face, 1, 0, fontname) #bold
264 addMapping(face, 1, 1, fontname) #italic and bold
265 elif (mode== 'normal') or (mode == 'regular'):
266 addMapping(face, 0, 0, fontname) #normal
267 elif (mode == 'italic'):
268 addMapping(face, 0, 1, fontname) #italic
269 elif (mode == 'bold'):
270 addMapping(face, 1, 0, fontname) #bold
271 elif (mode == 'bolditalic'):
272 addMapping(face, 1, 1, fontname) #italic and bold
274 def _textual_image(self, node):
277 rc +=( etree.tostring(n) or '') + n.tail
278 return base64.decodestring(node.tostring())
280 def _images(self, el):
282 for node in el.findall('.//image'):
283 rc =( node.text or '')
284 result[node.get('name')] = base64.decodestring(rc)
287 def render(self, out):
288 el = self.etree.findall('.//docinit')
292 el = self.etree.findall('.//stylesheet')
293 self.styles = _rml_styles(el,self.localcontext)
295 el = self.etree.findall('.//images')
297 self.images.update( self._images(el[0]) )
299 el = self.etree.findall('.//template')
301 pt_obj = _rml_template(self.localcontext, out, el[0], self, images=self.images, path=self.path, title=self.title)
302 el = utils._child_get(self.etree, self, 'story')
305 self.canvas = canvas.Canvas(out)
306 pd = self.etree.find('pageDrawing')[0]
307 pd_obj = _rml_canvas(self.canvas, self.localcontext, None, self, self.images, path=self.path, title=self.title)
310 self.canvas.showPage()
313 class _rml_canvas(object):
314 def __init__(self, canvas, localcontext, doc_tmpl=None, doc=None, images={}, path='.', title=None):
315 self.localcontext = localcontext
317 self.styles = doc.styles
318 self.doc_tmpl = doc_tmpl
323 self._logger = logging.getLogger('report.rml.canvas')
325 self.canvas.setTitle(self.title)
327 def _textual(self, node, x=0, y=0):
328 text = node.text and node.text.encode('utf-8') or ''
329 rc = utils._process_text(self, text)
332 from reportlab.lib.sequencer import getSequencer
334 rc += str(seq.next(n.get('id')))
335 if n.tag == 'pageCount':
337 self.canvas.translate(x,y)
338 self.canvas.doForm('pageCount')
340 self.canvas.translate(-x,-y)
341 if n.tag == 'pageNumber':
342 rc += str(self.canvas.getPageNumber())
343 rc += utils._process_text(self, n.tail)
344 return rc.replace('\n','')
346 def _drawString(self, node):
347 v = utils.attr_get(node, ['x','y'])
348 text=self._textual(node, **v)
349 text = utils.xml2str(text)
350 self.canvas.drawString(text=text, **v)
352 def _drawCenteredString(self, node):
353 v = utils.attr_get(node, ['x','y'])
354 text=self._textual(node, **v)
355 text = utils.xml2str(text)
356 self.canvas.drawCentredString(text=text, **v)
358 def _drawRightString(self, node):
359 v = utils.attr_get(node, ['x','y'])
360 text=self._textual(node, **v)
361 text = utils.xml2str(text)
362 self.canvas.drawRightString(text=text, **v)
364 def _rect(self, node):
365 if node.get('round'):
366 self.canvas.roundRect(radius=utils.unit_get(node.get('round')), **utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
368 self.canvas.rect(**utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
370 def _ellipse(self, node):
371 x1 = utils.unit_get(node.get('x'))
372 x2 = utils.unit_get(node.get('width'))
373 y1 = utils.unit_get(node.get('y'))
374 y2 = utils.unit_get(node.get('height'))
376 self.canvas.ellipse(x1,y1,x2,y2, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
378 def _curves(self, node):
379 line_str = node.text.split()
381 while len(line_str)>7:
382 self.canvas.bezier(*[utils.unit_get(l) for l in line_str[0:8]])
383 line_str = line_str[8:]
385 def _lines(self, node):
386 line_str = node.text.split()
388 while len(line_str)>3:
389 lines.append([utils.unit_get(l) for l in line_str[0:4]])
390 line_str = line_str[4:]
391 self.canvas.lines(lines)
393 def _grid(self, node):
394 xlist = [utils.unit_get(s) for s in node.get('xs').split(',')]
395 ylist = [utils.unit_get(s) for s in node.get('ys').split(',')]
397 self.canvas.grid(xlist, ylist)
399 def _translate(self, node):
400 dx = utils.unit_get(node.get('dx')) or 0
401 dy = utils.unit_get(node.get('dy')) or 0
402 self.canvas.translate(dx,dy)
404 def _circle(self, node):
405 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'}))
407 def _place(self, node):
408 flows = _rml_flowable(self.doc, self.localcontext, images=self.images, path=self.path, title=self.title).render(node)
409 infos = utils.attr_get(node, ['x','y','width','height'])
411 infos['y']+=infos['height']
413 w,h = flow.wrap(infos['width'], infos['height'])
414 if w<=infos['width'] and h<=infos['height']:
416 flow.drawOn(self.canvas,infos['x'],infos['y'])
419 raise ValueError, "Not enough space"
421 def _line_mode(self, node):
422 ljoin = {'round':1, 'mitered':0, 'bevelled':2}
423 lcap = {'default':0, 'round':1, 'square':2}
425 if node.get('width'):
426 self.canvas.setLineWidth(utils.unit_get(node.get('width')))
428 self.canvas.setLineJoin(ljoin[node.get('join')])
430 self.canvas.setLineCap(lcap[node.get('cap')])
431 if node.get('miterLimit'):
432 self.canvas.setDash(utils.unit_get(node.get('miterLimit')))
434 dashes = node.get('dash').split(',')
435 for x in range(len(dashes)):
436 dashes[x]=utils.unit_get(dashes[x])
437 self.canvas.setDash(node.get('dash').split(','))
439 def _image(self, node):
442 from reportlab.lib.utils import ImageReader
443 nfile = node.get('file')
446 image_data = self.images[node.get('name')]
447 self._logger.debug("Image %s used", node.get('name'))
448 s = StringIO(image_data)
450 if self.localcontext:
451 res = utils._regex.findall(node.text)
453 newtext = eval(key, {}, self.localcontext)
457 image_data = base64.decodestring(node.text)
459 s = StringIO(image_data)
461 self._logger.debug("No image data!")
464 if nfile in self.images:
465 s = StringIO(self.images[nfile])
468 up = urlparse.urlparse(str(nfile))
472 # RFC: do we really want to open external URLs?
473 # Are we safe from cross-site scripting or attacks?
474 self._logger.debug("Retrieve image from %s", nfile)
475 u = urllib.urlopen(str(nfile))
476 s = StringIO(u.read())
478 self._logger.debug("Open image file %s ", nfile)
479 s = _open_image(nfile, path=self.path)
481 (sx,sy) = img.getSize()
482 self._logger.debug("Image is %dx%d", sx, sy)
484 args = { 'x': 0.0, 'y': 0.0 }
485 for tag in ('width','height','x','y'):
487 args[tag] = utils.unit_get(node.get(tag))
488 if ('width' in args) and (not 'height' in args):
489 args['height'] = sy * args['width'] / sx
490 elif ('height' in args) and (not 'width' in args):
491 args['width'] = sx * args['height'] / sy
492 elif ('width' in args) and ('height' in args):
493 if (float(args['width'])/args['height'])>(float(sx)>sy):
494 args['width'] = sx * args['height'] / sy
496 args['height'] = sy * args['width'] / sx
497 self.canvas.drawImage(img, **args)
498 # self.canvas._doc.SaveToFile(self.canvas._filename, self.canvas)
500 def _path(self, node):
501 self.path = self.canvas.beginPath()
502 self.path.moveTo(**utils.attr_get(node, ['x','y']))
503 for n in utils._child_get(node, self):
506 vals = utils.text_get(n).split()
507 self.path.moveTo(utils.unit_get(vals[0]), utils.unit_get(vals[1]))
508 elif n.tag=='curvesto':
509 vals = utils.text_get(n).split()
513 pos.append(utils.unit_get(vals.pop(0)))
514 self.path.curveTo(*pos)
516 data = n.text.split() # Not sure if I must merge all TEXT_NODE ?
518 x = utils.unit_get(data.pop(0))
519 y = utils.unit_get(data.pop(0))
520 self.path.lineTo(x,y)
521 if (not node.get('close')) or utils.bool_get(node.get('close')):
523 self.canvas.drawPath(self.path, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
525 def setFont(self, node):
526 fontname = node.get('name')
527 if fontname not in pdfmetrics.getRegisteredFontNames()\
528 or fontname not in pdfmetrics.standardFonts:
529 # let reportlab attempt to find it
531 pdfmetrics.getFont(fontname)
533 logging.getLogger('report.fonts').debug('Could not locate font %s, substituting default: %s',
535 self.canvas._fontname)
536 fontname = self.canvas._fontname
537 return self.canvas.setFont(fontname, utils.unit_get(node.get('size')))
539 def render(self, node):
541 'drawCentredString': self._drawCenteredString,
542 'drawRightString': self._drawRightString,
543 'drawString': self._drawString,
545 'ellipse': self._ellipse,
546 'lines': self._lines,
548 'curves': self._curves,
549 'fill': lambda node: self.canvas.setFillColor(color.get(node.get('color'))),
550 'stroke': lambda node: self.canvas.setStrokeColor(color.get(node.get('color'))),
551 'setFont': self.setFont ,
552 'place': self._place,
553 'circle': self._circle,
554 'lineMode': self._line_mode,
556 'rotate': lambda node: self.canvas.rotate(float(node.get('degrees'))),
557 'translate': self._translate,
560 for n in utils._child_get(node, self):
564 class _rml_draw(object):
565 def __init__(self, localcontext ,node, styles, images={}, path='.', title=None):
566 self.localcontext = localcontext
572 self.canvas_title = title
574 def render(self, canvas, doc):
576 cnv = _rml_canvas(canvas, self.localcontext, doc, self.styles, images=self.images, path=self.path, title=self.canvas_title)
577 cnv.render(self.node)
578 canvas.restoreState()
580 class _rml_Illustration(platypus.flowables.Flowable):
581 def __init__(self, node, localcontext, styles, self2):
582 self.localcontext = (localcontext or {}).copy()
585 self.width = utils.unit_get(node.get('width'))
586 self.height = utils.unit_get(node.get('height'))
588 def wrap(self, *args):
589 return (self.width, self.height)
591 drw = _rml_draw(self.localcontext ,self.node,self.styles, images=self.self2.images, path=self.self2.path, title=self.self2.title)
592 drw.render(self.canv, None)
594 class _rml_flowable(object):
595 def __init__(self, doc, localcontext, images=None, path='.', title=None):
596 self.localcontext = localcontext
598 self.styles = doc.styles
599 self.images = images or {}
602 self._logger = logging.getLogger('report.rml.flowable')
604 def _textual(self, node):
605 rc1 = utils._process_text(self, node.text or '')
606 for n in utils._child_get(node,self):
607 txt_n = copy.deepcopy(n)
608 for key in txt_n.attrib.keys():
609 if key in ('rml_except', 'rml_loop', 'rml_tag'):
610 del txt_n.attrib[key]
611 if True or not self._textual(n).isspace():
612 if not n.tag == 'bullet':
613 txt_n.text = utils.xml2str(self._textual(n))
614 txt_n.tail = n.tail and utils._process_text(self, n.tail.replace('\n','')) or ''
615 rc1 += etree.tostring(txt_n)
618 def _table(self, node):
619 children = utils._child_get(node,self,'tr')
631 st = copy.deepcopy(self.styles.table_styles[tr.get('style')])
636 if tr.get('paraStyle'):
637 paraStyle = self.styles.styles[tr.get('paraStyle')]
640 for td in utils._child_get(tr, self,'td'):
642 st = copy.deepcopy(self.styles.table_styles[td.get('style')])
649 if td.get('paraStyle'):
651 paraStyle = self.styles.styles[td.get('paraStyle')]
655 for n in utils._child_get(td, self):
656 if n.tag == etree.Comment:
659 fl = self._flowable(n, extra_style=paraStyle)
660 if isinstance(fl,list):
666 flow = self._textual(td)
668 if len(data2)>length:
671 while len(ab)<length:
673 while len(data2)<length:
678 if node.get('colWidths'):
679 assert length == len(node.get('colWidths').split(','))
680 colwidths = [utils.unit_get(f.strip()) for f in node.get('colWidths').split(',')]
681 if node.get('rowHeights'):
682 rowheights = [utils.unit_get(f.strip()) for f in node.get('rowHeights').split(',')]
683 if len(rowheights) == 1:
684 rowheights = rowheights[0]
685 table = platypus.LongTable(data = data, colWidths=colwidths, rowHeights=rowheights, **(utils.attr_get(node, ['splitByRow'] ,{'repeatRows':'int','repeatCols':'int'})))
686 if node.get('style'):
687 table.setStyle(self.styles.table_styles[node.get('style')])
692 def _illustration(self, node):
693 return _rml_Illustration(node, self.localcontext, self.styles, self)
695 def _textual_image(self, node):
696 return base64.decodestring(node.text)
698 def _pto(self, node):
702 for node in utils._child_get(node, self):
703 if node.tag == etree.Comment:
706 elif node.tag=='pto_header':
707 pto_header = self.render(node)
708 elif node.tag=='pto_trailer':
709 pto_trailer = self.render(node)
711 flow = self._flowable(node)
713 if isinstance(flow,list):
714 sub_story = sub_story + flow
716 sub_story.append(flow)
717 return platypus.flowables.PTOContainer(sub_story, trailer=pto_trailer, header=pto_header)
719 def _flowable(self, node, extra_style=None):
721 return self._pto(node)
723 style = self.styles.para_style_get(node)
725 style.__dict__.update(extra_style)
727 for i in self._textual(node).split('\n'):
728 result.append(platypus.Paragraph(i, style, **(utils.attr_get(node, [], {'bulletText':'str'}))))
730 elif node.tag=='barCode':
732 from reportlab.graphics.barcode import code128
733 from reportlab.graphics.barcode import code39
734 from reportlab.graphics.barcode import code93
735 from reportlab.graphics.barcode import common
736 from reportlab.graphics.barcode import fourstate
737 from reportlab.graphics.barcode import usps
739 self._logger.warning("Cannot use barcode renderers:", exc_info=True)
741 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'})
743 'codabar': lambda x: common.Codabar(x, **args),
744 'code11': lambda x: common.Code11(x, **args),
745 'code128': lambda x: code128.Code128(x, **args),
746 'standard39': lambda x: code39.Standard39(x, **args),
747 'standard93': lambda x: code93.Standard93(x, **args),
748 'i2of5': lambda x: common.I2of5(x, **args),
749 'extended39': lambda x: code39.Extended39(x, **args),
750 'extended93': lambda x: code93.Extended93(x, **args),
751 'msi': lambda x: common.MSI(x, **args),
752 'fim': lambda x: usps.FIM(x, **args),
753 'postnet': lambda x: usps.POSTNET(x, **args),
757 code = node.get('code').lower()
758 return codes[code](self._textual(node))
759 elif node.tag=='name':
760 self.styles.names[ node.get('id')] = node.get('value')
762 elif node.tag=='xpre':
763 style = self.styles.para_style_get(node)
764 return platypus.XPreformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int','frags':'int'})))
765 elif node.tag=='pre':
766 style = self.styles.para_style_get(node)
767 return platypus.Preformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int'})))
768 elif node.tag=='illustration':
769 return self._illustration(node)
770 elif node.tag=='blockTable':
771 return self._table(node)
772 elif node.tag=='title':
773 styles = reportlab.lib.styles.getSampleStyleSheet()
774 style = styles['Title']
775 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
776 elif re.match('^h([1-9]+[0-9]*)$', (node.tag or '')):
777 styles = reportlab.lib.styles.getSampleStyleSheet()
778 style = styles['Heading'+str(node.tag[1:])]
779 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
780 elif node.tag=='image':
782 if not node.get('file'):
784 if node.get('name') in self.doc.images:
785 self._logger.debug("Image %s read ", node.get('name'))
786 image_data = self.doc.images[node.get('name')].read()
788 self._logger.warning("Image %s not defined", node.get('name'))
792 if self.localcontext:
793 newtext = utils._process_text(self, node.text or '')
795 image_data = base64.decodestring(node.text)
797 self._logger.debug("No inline image data")
799 image = StringIO(image_data)
801 self._logger.debug("Image get from file %s", node.get('file'))
802 image = _open_image(node.get('file'), path=self.doc.path)
803 return platypus.Image(image, mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
804 elif node.tag=='spacer':
805 if node.get('width'):
806 width = utils.unit_get(node.get('width'))
808 width = utils.unit_get('1cm')
809 length = utils.unit_get(node.get('length'))
810 return platypus.Spacer(width=width, height=length)
811 elif node.tag=='section':
812 return self.render(node)
813 elif node.tag == 'pageNumberReset':
815 elif node.tag in ('pageBreak', 'nextPage'):
816 return platypus.PageBreak()
817 elif node.tag=='condPageBreak':
818 return platypus.CondPageBreak(**(utils.attr_get(node, ['height'])))
819 elif node.tag=='setNextTemplate':
820 return platypus.NextPageTemplate(str(node.get('name')))
821 elif node.tag=='nextFrame':
822 return platypus.CondPageBreak(1000) # TODO: change the 1000 !
823 elif node.tag == 'setNextFrame':
824 from reportlab.platypus.doctemplate import NextFrameFlowable
825 return NextFrameFlowable(str(node.get('name')))
826 elif node.tag == 'currentFrame':
827 from reportlab.platypus.doctemplate import CurrentFrameFlowable
828 return CurrentFrameFlowable(str(node.get('name')))
829 elif node.tag == 'frameEnd':
830 return EndFrameFlowable()
831 elif node.tag == 'hr':
832 width_hr=node.get('width') or '100%'
833 color_hr=node.get('color') or 'black'
834 thickness_hr=node.get('thickness') or 1
835 lineCap_hr=node.get('lineCap') or 'round'
836 return platypus.flowables.HRFlowable(width=width_hr,color=color.get(color_hr),thickness=float(thickness_hr),lineCap=str(lineCap_hr))
838 sys.stderr.write('Warning: flowable not yet implemented: %s !\n' % (node.tag,))
841 def render(self, node_story):
842 def process_story(node_story):
844 for node in utils._child_get(node_story, self):
845 if node.tag == etree.Comment:
848 flow = self._flowable(node)
850 if isinstance(flow,list):
851 sub_story = sub_story + flow
853 sub_story.append(flow)
855 return process_story(node_story)
858 class EndFrameFlowable(ActionFlowable):
859 def __init__(self,resume=0):
860 ActionFlowable.__init__(self,('frameEnd',resume))
862 class TinyDocTemplate(platypus.BaseDocTemplate):
863 def ___handle_pageBegin(self):
864 self.page = self.page + 1
865 self.pageTemplate.beforeDrawPage(self.canv,self)
866 self.pageTemplate.checkPageSize(self.canv,self)
867 self.pageTemplate.onPage(self.canv,self)
868 for f in self.pageTemplate.frames: f._reset()
870 self._curPageFlowableCount = 0
871 if hasattr(self,'_nextFrameIndex'):
872 del self._nextFrameIndex
873 for f in self.pageTemplate.frames:
877 self.handle_frameBegin()
878 def afterFlowable(self, flowable):
879 if isinstance(flowable, PageReset):
880 self.canv._pageCount=self.page
883 self.canv._pageNumber = 0
885 class _rml_template(object):
886 def __init__(self, localcontext, out, node, doc, images={}, path='.', title=None):
888 localcontext={'internal_header':True}
889 self.localcontext = localcontext
894 if not node.get('pageSize'):
895 pageSize = (utils.unit_get('21cm'), utils.unit_get('29.7cm'))
897 ps = map(lambda x:x.strip(), node.get('pageSize').replace(')', '').replace('(', '').split(','))
898 pageSize = ( utils.unit_get(ps[0]),utils.unit_get(ps[1]) )
900 self.doc_tmpl = TinyDocTemplate(out, pagesize=pageSize, **utils.attr_get(node, ['leftMargin','rightMargin','topMargin','bottomMargin'], {'allowSplitting':'int','showBoundary':'bool','title':'str','author':'str'}))
901 self.page_templates = []
902 self.styles = doc.styles
905 pts = node.findall('pageTemplate')
908 for frame_el in pt.findall('frame'):
909 frame = platypus.Frame( **(utils.attr_get(frame_el, ['x1','y1', 'width','height', 'leftPadding', 'rightPadding', 'bottomPadding', 'topPadding'], {'id':'str', 'showBoundary':'bool'})) )
910 if utils.attr_get(frame_el, ['last']):
911 frame.lastFrame = True
912 frames.append( frame )
914 gr = pt.findall('pageGraphics')\
915 or pt[1].findall('pageGraphics')
916 except Exception: # FIXME: be even more specific, perhaps?
919 # self.image=[ n for n in utils._child_get(gr[0], self) if n.tag=='image' or not self.localcontext]
920 drw = _rml_draw(self.localcontext,gr[0], self.doc, images=images, path=self.path, title=self.title)
921 self.page_templates.append( platypus.PageTemplate(frames=frames, onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) ))
923 drw = _rml_draw(self.localcontext,node,self.doc,title=self.title)
924 self.page_templates.append( platypus.PageTemplate(frames=frames,onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) ))
925 self.doc_tmpl.addPageTemplates(self.page_templates)
927 def render(self, node_stories):
928 if self.localcontext and not self.localcontext.get('internal_header',False):
929 del self.localcontext['internal_header']
931 r = _rml_flowable(self.doc,self.localcontext, images=self.images, path=self.path, title=self.title)
933 for node_story in node_stories:
935 fis.append(platypus.PageBreak())
936 fis += r.render(node_story)
937 # Reset Page Number with new story tag
938 fis.append(PageReset())
940 if self.localcontext and self.localcontext.get('internal_header',False):
941 self.doc_tmpl.afterFlowable(fis)
942 self.doc_tmpl.build(fis,canvasmaker=NumberedCanvas)
944 fis.append(PageCount())
945 self.doc_tmpl.build(fis)
947 def parseNode(rml, localcontext=None,fout=None, images=None, path='.',title=None):
948 node = etree.XML(rml)
949 if localcontext is None:
953 r = _rml_doc(node, localcontext, images, path, title=title)
954 #try to override some font mappings
956 from customfonts import SetCustomFonts
959 # means there is no custom fonts mapping in this system.
962 logging.getLogger('report').warning('Cannot set font mapping', exc_info=True)
968 def parseString(rml, localcontext = {},fout=None, images={}, path='.',title=None):
969 node = etree.XML(rml)
970 r = _rml_doc(node, localcontext, images, path, title=title)
972 #try to override some font mappings
974 from customfonts import SetCustomFonts
990 print 'Usage: trml2pdf input.rml >output.pdf'
991 print 'Render the standard input (RML) and output a PDF file'
994 if __name__=="__main__":
996 if sys.argv[1]=='--help':
998 print parseString(file(sys.argv[1], 'r').read()),
1000 print 'Usage: trml2pdf input.rml >output.pdf'
1001 print 'Try \'trml2pdf --help\' for more information.'