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 openerp.tools.safe_eval import safe_eval as eval
37 from reportlab.lib.units import inch,cm,mm
38 from openerp.tools.misc import file_open
39 from reportlab.pdfbase import pdfmetrics
40 from reportlab.lib.pagesizes import A4, letter
43 from cStringIO import StringIO
44 _hush_pyflakes = [ StringIO ]
46 from StringIO import StringIO
48 _logger = logging.getLogger(__name__)
52 def _open_image(filename, path=None):
53 """Attempt to open a binary file and return the descriptor
55 if os.path.isfile(filename):
56 return open(filename, 'rb')
57 for p in (path or []):
58 if p and os.path.isabs(p):
59 fullpath = os.path.join(p, filename)
60 if os.path.isfile(fullpath):
61 return open(fullpath, 'rb')
64 fullpath = os.path.join(p, filename)
67 return file_open(fullpath)
70 raise IOError("File %s cannot be found in image path" % filename)
72 class NumberedCanvas(canvas.Canvas):
73 def __init__(self, *args, **kwargs):
74 canvas.Canvas.__init__(self, *args, **kwargs)
87 self.pages.update({self._currentPage:self._pageCount})
88 self._codes.append({'code': self._code, 'stack': self._codeStack})
93 if self.pages.get(self._pageCounter,False):
97 if not self.pages.get(key,False):
98 while not self.pages.get(key,False):
100 self.setFont("Helvetica", 8)
101 self.drawRightString((self._pagesize[0]-30), (self._pagesize[1]-40),
102 " %(this)i / %(total)i" % {
103 'this': self._pageNumber+1,
104 'total': self.pages.get(key,False),
109 """add page info to each page (page x of y)"""
112 for code in self._codes:
113 self._code = code['code']
114 self._codeStack = code['stack']
116 canvas.Canvas.showPage(self)
117 # self.restoreState()
118 self._doc.SaveToFile(self._filename, self)
120 class PageCount(platypus.Flowable):
122 self.canv.beginForm("pageCount")
123 self.canv.setFont("Helvetica", utils.unit_get(str(8)))
124 self.canv.drawString(0, 0, str(self.canv.getPageNumber()))
127 class PageReset(platypus.Flowable):
129 self.canv._pageNumber = 0
131 class _rml_styles(object,):
132 def __init__(self, nodes, localcontext):
133 self.localcontext = localcontext
137 self.table_styles = {}
138 self.default_style = reportlab.lib.styles.getSampleStyleSheet()
141 for style in node.findall('blockTableStyle'):
142 self.table_styles[style.get('id')] = self._table_style_get(style)
143 for style in node.findall('paraStyle'):
144 sname = style.get('name')
145 self.styles[sname] = self._para_style_update(style)
147 self.styles_obj[sname] = reportlab.lib.styles.ParagraphStyle(sname, self.default_style["Normal"], **self.styles[sname])
149 for variable in node.findall('initialize'):
150 for name in variable.findall('name'):
151 self.names[ name.get('id')] = name.get('value')
153 def _para_style_update(self, node):
155 for attr in ['textColor', 'backColor', 'bulletColor', 'borderColor']:
157 data[attr] = color.get(node.get(attr))
158 for attr in ['fontName', 'bulletFontName', 'bulletText']:
160 data[attr] = node.get(attr)
161 for attr in ['fontSize', 'leftIndent', 'rightIndent', 'spaceBefore', 'spaceAfter',
162 'firstLineIndent', 'bulletIndent', 'bulletFontSize', 'leading',
163 'borderWidth','borderPadding','borderRadius']:
165 data[attr] = utils.unit_get(node.get(attr))
166 if node.get('alignment'):
168 'right':reportlab.lib.enums.TA_RIGHT,
169 'center':reportlab.lib.enums.TA_CENTER,
170 'justify':reportlab.lib.enums.TA_JUSTIFY
172 data['alignment'] = align.get(node.get('alignment').lower(), reportlab.lib.enums.TA_LEFT)
175 def _table_style_get(self, style_node):
177 for node in style_node:
178 start = utils.tuple_int_get(node, 'start', (0,0) )
179 stop = utils.tuple_int_get(node, 'stop', (-1,-1) )
180 if node.tag=='blockValign':
181 styles.append(('VALIGN', start, stop, str(node.get('value'))))
182 elif node.tag=='blockFont':
183 styles.append(('FONT', start, stop, str(node.get('name'))))
184 elif node.tag=='blockTextColor':
185 styles.append(('TEXTCOLOR', start, stop, color.get(str(node.get('colorName')))))
186 elif node.tag=='blockLeading':
187 styles.append(('LEADING', start, stop, utils.unit_get(node.get('length'))))
188 elif node.tag=='blockAlignment':
189 styles.append(('ALIGNMENT', start, stop, str(node.get('value'))))
190 elif node.tag=='blockSpan':
191 styles.append(('SPAN', start, stop))
192 elif node.tag=='blockLeftPadding':
193 styles.append(('LEFTPADDING', start, stop, utils.unit_get(node.get('length'))))
194 elif node.tag=='blockRightPadding':
195 styles.append(('RIGHTPADDING', start, stop, utils.unit_get(node.get('length'))))
196 elif node.tag=='blockTopPadding':
197 styles.append(('TOPPADDING', start, stop, utils.unit_get(node.get('length'))))
198 elif node.tag=='blockBottomPadding':
199 styles.append(('BOTTOMPADDING', start, stop, utils.unit_get(node.get('length'))))
200 elif node.tag=='blockBackground':
201 styles.append(('BACKGROUND', start, stop, color.get(node.get('colorName'))))
203 styles.append(('FONTSIZE', start, stop, utils.unit_get(node.get('size'))))
204 elif node.tag=='lineStyle':
205 kind = node.get('kind')
206 kind_list = [ 'GRID', 'BOX', 'OUTLINE', 'INNERGRID', 'LINEBELOW', 'LINEABOVE','LINEBEFORE', 'LINEAFTER' ]
207 assert kind in kind_list
209 if node.get('thickness'):
210 thick = float(node.get('thickness'))
211 styles.append((kind, start, stop, thick, color.get(node.get('colorName'))))
212 return platypus.tables.TableStyle(styles)
214 def para_style_get(self, node):
216 sname = node.get('style')
218 if sname in self.styles_obj:
219 style = self.styles_obj[sname]
221 _logger.warning('Warning: style not found, %s - setting default!\n' % (node.get('style'),) )
223 style = self.default_style['Normal']
224 para_update = self._para_style_update(node)
226 # update style only is necessary
227 style = copy.deepcopy(style)
228 style.__dict__.update(para_update)
231 class _rml_doc(object):
232 def __init__(self, node, localcontext=None, images=None, path='.', title=None):
235 if localcontext is None:
237 self.localcontext = localcontext
239 self.filename = self.etree.get('filename')
244 def docinit(self, els):
245 from reportlab.lib.fonts import addMapping
246 from reportlab.pdfbase import pdfmetrics
247 from reportlab.pdfbase.ttfonts import TTFont
250 for font in node.findall('registerFont'):
251 name = font.get('fontName').encode('ascii')
252 fname = font.get('fontFile').encode('ascii')
253 if name not in pdfmetrics._fonts:
254 pdfmetrics.registerFont(TTFont(name, fname))
255 addMapping(name, 0, 0, name) #normal
256 addMapping(name, 0, 1, name) #italic
257 addMapping(name, 1, 0, name) #bold
258 addMapping(name, 1, 1, name) #italic and bold
260 def setTTFontMapping(self,face, fontname, filename, mode='all'):
261 from reportlab.lib.fonts import addMapping
262 from reportlab.pdfbase import pdfmetrics
263 from reportlab.pdfbase.ttfonts import TTFont
265 if fontname not in pdfmetrics._fonts:
266 pdfmetrics.registerFont(TTFont(fontname, filename))
268 addMapping(face, 0, 0, fontname) #normal
269 addMapping(face, 0, 1, fontname) #italic
270 addMapping(face, 1, 0, fontname) #bold
271 addMapping(face, 1, 1, fontname) #italic and bold
272 elif (mode== 'normal') or (mode == 'regular'):
273 addMapping(face, 0, 0, fontname) #normal
274 elif (mode == 'italic'):
275 addMapping(face, 0, 1, fontname) #italic
276 elif (mode == 'bold'):
277 addMapping(face, 1, 0, fontname) #bold
278 elif (mode == 'bolditalic'):
279 addMapping(face, 1, 1, fontname) #italic and bold
281 def _textual_image(self, node):
284 rc +=( etree.tostring(n) or '') + n.tail
285 return base64.decodestring(node.tostring())
287 def _images(self, el):
289 for node in el.findall('.//image'):
290 rc =( node.text or '')
291 result[node.get('name')] = base64.decodestring(rc)
294 def render(self, out):
295 el = self.etree.findall('.//docinit')
299 el = self.etree.findall('.//stylesheet')
300 self.styles = _rml_styles(el,self.localcontext)
302 el = self.etree.findall('.//images')
304 self.images.update( self._images(el[0]) )
306 el = self.etree.findall('.//template')
308 pt_obj = _rml_template(self.localcontext, out, el[0], self, images=self.images, path=self.path, title=self.title)
309 el = utils._child_get(self.etree, self, 'story')
312 self.canvas = canvas.Canvas(out)
313 pd = self.etree.find('pageDrawing')[0]
314 pd_obj = _rml_canvas(self.canvas, self.localcontext, None, self, self.images, path=self.path, title=self.title)
317 self.canvas.showPage()
320 class _rml_canvas(object):
321 def __init__(self, canvas, localcontext, doc_tmpl=None, doc=None, images=None, path='.', title=None):
324 self.localcontext = localcontext
326 self.styles = doc.styles
327 self.doc_tmpl = doc_tmpl
333 self.canvas.setTitle(self.title)
335 def _textual(self, node, x=0, y=0):
336 text = node.text and node.text.encode('utf-8') or ''
337 rc = utils._process_text(self, text)
340 from reportlab.lib.sequencer import getSequencer
342 rc += str(seq.next(n.get('id')))
343 if n.tag == 'pageCount':
345 self.canvas.translate(x,y)
346 self.canvas.doForm('pageCount')
348 self.canvas.translate(-x,-y)
349 if n.tag == 'pageNumber':
350 rc += str(self.canvas.getPageNumber())
351 rc += utils._process_text(self, n.tail)
352 return rc.replace('\n','')
354 def _drawString(self, node):
355 v = utils.attr_get(node, ['x','y'])
356 text=self._textual(node, **v)
357 text = utils.xml2str(text)
358 self.canvas.drawString(text=text, **v)
360 def _drawCenteredString(self, node):
361 v = utils.attr_get(node, ['x','y'])
362 text=self._textual(node, **v)
363 text = utils.xml2str(text)
364 self.canvas.drawCentredString(text=text, **v)
366 def _drawRightString(self, node):
367 v = utils.attr_get(node, ['x','y'])
368 text=self._textual(node, **v)
369 text = utils.xml2str(text)
370 self.canvas.drawRightString(text=text, **v)
372 def _rect(self, node):
373 if node.get('round'):
374 self.canvas.roundRect(radius=utils.unit_get(node.get('round')), **utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
376 self.canvas.rect(**utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
378 def _ellipse(self, node):
379 x1 = utils.unit_get(node.get('x'))
380 x2 = utils.unit_get(node.get('width'))
381 y1 = utils.unit_get(node.get('y'))
382 y2 = utils.unit_get(node.get('height'))
384 self.canvas.ellipse(x1,y1,x2,y2, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
386 def _curves(self, node):
387 line_str = node.text.split()
389 while len(line_str)>7:
390 self.canvas.bezier(*[utils.unit_get(l) for l in line_str[0:8]])
391 line_str = line_str[8:]
393 def _lines(self, node):
394 line_str = node.text.split()
396 while len(line_str)>3:
397 lines.append([utils.unit_get(l) for l in line_str[0:4]])
398 line_str = line_str[4:]
399 self.canvas.lines(lines)
401 def _grid(self, node):
402 xlist = [utils.unit_get(s) for s in node.get('xs').split(',')]
403 ylist = [utils.unit_get(s) for s in node.get('ys').split(',')]
405 self.canvas.grid(xlist, ylist)
407 def _translate(self, node):
408 dx = utils.unit_get(node.get('dx')) or 0
409 dy = utils.unit_get(node.get('dy')) or 0
410 self.canvas.translate(dx,dy)
412 def _circle(self, node):
413 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'}))
415 def _place(self, node):
416 flows = _rml_flowable(self.doc, self.localcontext, images=self.images, path=self.path, title=self.title).render(node)
417 infos = utils.attr_get(node, ['x','y','width','height'])
419 infos['y']+=infos['height']
421 w,h = flow.wrap(infos['width'], infos['height'])
422 if w<=infos['width'] and h<=infos['height']:
424 flow.drawOn(self.canvas,infos['x'],infos['y'])
427 raise ValueError, "Not enough space"
429 def _line_mode(self, node):
430 ljoin = {'round':1, 'mitered':0, 'bevelled':2}
431 lcap = {'default':0, 'round':1, 'square':2}
433 if node.get('width'):
434 self.canvas.setLineWidth(utils.unit_get(node.get('width')))
436 self.canvas.setLineJoin(ljoin[node.get('join')])
438 self.canvas.setLineCap(lcap[node.get('cap')])
439 if node.get('miterLimit'):
440 self.canvas.setDash(utils.unit_get(node.get('miterLimit')))
442 dashes = node.get('dash').split(',')
443 for x in range(len(dashes)):
444 dashes[x]=utils.unit_get(dashes[x])
445 self.canvas.setDash(node.get('dash').split(','))
447 def _image(self, node):
450 from reportlab.lib.utils import ImageReader
451 nfile = node.get('file')
454 image_data = self.images[node.get('name')]
455 _logger.debug("Image %s used", node.get('name'))
456 s = StringIO(image_data)
459 if self.localcontext:
460 res = utils._regex.findall(newtext)
462 newtext = eval(key, {}, self.localcontext) or ''
465 image_data = base64.decodestring(newtext)
467 s = StringIO(image_data)
469 _logger.debug("No image data!")
472 if nfile in self.images:
473 s = StringIO(self.images[nfile])
476 up = urlparse.urlparse(str(nfile))
480 # RFC: do we really want to open external URLs?
481 # Are we safe from cross-site scripting or attacks?
482 _logger.debug("Retrieve image from %s", nfile)
483 u = urllib.urlopen(str(nfile))
484 s = StringIO(u.read())
486 _logger.debug("Open image file %s ", nfile)
487 s = _open_image(nfile, path=self.path)
490 (sx,sy) = img.getSize()
491 _logger.debug("Image is %dx%d", sx, sy)
492 args = { 'x': 0.0, 'y': 0.0 }
493 for tag in ('width','height','x','y'):
495 args[tag] = utils.unit_get(node.get(tag))
496 if ('width' in args) and (not 'height' in args):
497 args['height'] = sy * args['width'] / sx
498 elif ('height' in args) and (not 'width' in args):
499 args['width'] = sx * args['height'] / sy
500 elif ('width' in args) and ('height' in args):
501 if (float(args['width'])/args['height'])>(float(sx)>sy):
502 args['width'] = sx * args['height'] / sy
504 args['height'] = sy * args['width'] / sx
505 self.canvas.drawImage(img, **args)
508 # self.canvas._doc.SaveToFile(self.canvas._filename, self.canvas)
510 def _path(self, node):
511 self.path = self.canvas.beginPath()
512 self.path.moveTo(**utils.attr_get(node, ['x','y']))
513 for n in utils._child_get(node, self):
516 vals = utils.text_get(n).split()
517 self.path.moveTo(utils.unit_get(vals[0]), utils.unit_get(vals[1]))
518 elif n.tag=='curvesto':
519 vals = utils.text_get(n).split()
523 pos.append(utils.unit_get(vals.pop(0)))
524 self.path.curveTo(*pos)
526 data = n.text.split() # Not sure if I must merge all TEXT_NODE ?
528 x = utils.unit_get(data.pop(0))
529 y = utils.unit_get(data.pop(0))
530 self.path.lineTo(x,y)
531 if (not node.get('close')) or utils.bool_get(node.get('close')):
533 self.canvas.drawPath(self.path, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
535 def setFont(self, node):
536 fontname = node.get('name')
537 if fontname not in pdfmetrics.getRegisteredFontNames()\
538 or fontname not in pdfmetrics.standardFonts:
539 # let reportlab attempt to find it
541 pdfmetrics.getFont(fontname)
543 _logger.debug('Could not locate font %s, substituting default: %s',
545 self.canvas._fontname)
546 fontname = self.canvas._fontname
547 return self.canvas.setFont(fontname, utils.unit_get(node.get('size')))
549 def render(self, node):
551 'drawCentredString': self._drawCenteredString,
552 'drawRightString': self._drawRightString,
553 'drawString': self._drawString,
555 'ellipse': self._ellipse,
556 'lines': self._lines,
558 'curves': self._curves,
559 'fill': lambda node: self.canvas.setFillColor(color.get(node.get('color'))),
560 'stroke': lambda node: self.canvas.setStrokeColor(color.get(node.get('color'))),
561 'setFont': self.setFont ,
562 'place': self._place,
563 'circle': self._circle,
564 'lineMode': self._line_mode,
566 'rotate': lambda node: self.canvas.rotate(float(node.get('degrees'))),
567 'translate': self._translate,
570 for n in utils._child_get(node, self):
574 class _rml_draw(object):
575 def __init__(self, localcontext, node, styles, images=None, path='.', title=None):
578 self.localcontext = localcontext
584 self.canvas_title = title
586 def render(self, canvas, doc):
588 cnv = _rml_canvas(canvas, self.localcontext, doc, self.styles, images=self.images, path=self.path, title=self.canvas_title)
589 cnv.render(self.node)
590 canvas.restoreState()
592 class _rml_Illustration(platypus.flowables.Flowable):
593 def __init__(self, node, localcontext, styles, self2):
594 self.localcontext = (localcontext or {}).copy()
597 self.width = utils.unit_get(node.get('width'))
598 self.height = utils.unit_get(node.get('height'))
600 def wrap(self, *args):
601 return (self.width, self.height)
603 drw = _rml_draw(self.localcontext ,self.node,self.styles, images=self.self2.images, path=self.self2.path, title=self.self2.title)
604 drw.render(self.canv, None)
606 class _rml_flowable(object):
607 def __init__(self, doc, localcontext, images=None, path='.', title=None):
610 self.localcontext = localcontext
612 self.styles = doc.styles
617 def _textual(self, node):
618 rc1 = utils._process_text(self, node.text or '')
619 for n in utils._child_get(node,self):
620 txt_n = copy.deepcopy(n)
621 for key in txt_n.attrib.keys():
622 if key in ('rml_except', 'rml_loop', 'rml_tag'):
623 del txt_n.attrib[key]
624 if not n.tag == 'bullet':
625 txt_n.text = utils.xml2str(self._textual(n))
626 txt_n.tail = n.tail and utils.xml2str(utils._process_text(self, n.tail.replace('\n',''))) or ''
627 rc1 += etree.tostring(txt_n)
630 def _table(self, node):
631 children = utils._child_get(node,self,'tr')
643 st = copy.deepcopy(self.styles.table_styles[tr.get('style')])
644 for si in range(len(st._cmds)):
645 s = list(st._cmds[si])
646 s[1] = (s[1][0],posy)
647 s[2] = (s[2][0],posy)
648 st._cmds[si] = tuple(s)
650 if tr.get('paraStyle'):
651 paraStyle = self.styles.styles[tr.get('paraStyle')]
654 for td in utils._child_get(tr, self,'td'):
656 st = copy.deepcopy(self.styles.table_styles[td.get('style')])
663 if td.get('paraStyle'):
665 paraStyle = self.styles.styles[td.get('paraStyle')]
669 for n in utils._child_get(td, self):
670 if n.tag == etree.Comment:
673 fl = self._flowable(n, extra_style=paraStyle)
674 if isinstance(fl,list):
680 flow = self._textual(td)
682 if len(data2)>length:
685 while len(ab)<length:
687 while len(data2)<length:
692 if node.get('colWidths'):
693 assert length == len(node.get('colWidths').split(','))
694 colwidths = [utils.unit_get(f.strip()) for f in node.get('colWidths').split(',')]
695 if node.get('rowHeights'):
696 rowheights = [utils.unit_get(f.strip()) for f in node.get('rowHeights').split(',')]
697 if len(rowheights) == 1:
698 rowheights = rowheights[0]
699 table = platypus.LongTable(data = data, colWidths=colwidths, rowHeights=rowheights, **(utils.attr_get(node, ['splitByRow'] ,{'repeatRows':'int','repeatCols':'int'})))
700 if node.get('style'):
701 table.setStyle(self.styles.table_styles[node.get('style')])
706 def _illustration(self, node):
707 return _rml_Illustration(node, self.localcontext, self.styles, self)
709 def _textual_image(self, node):
710 return base64.decodestring(node.text)
712 def _pto(self, node):
716 for node in utils._child_get(node, self):
717 if node.tag == etree.Comment:
720 elif node.tag=='pto_header':
721 pto_header = self.render(node)
722 elif node.tag=='pto_trailer':
723 pto_trailer = self.render(node)
725 flow = self._flowable(node)
727 if isinstance(flow,list):
728 sub_story = sub_story + flow
730 sub_story.append(flow)
731 return platypus.flowables.PTOContainer(sub_story, trailer=pto_trailer, header=pto_header)
733 def _flowable(self, node, extra_style=None):
735 return self._pto(node)
737 style = self.styles.para_style_get(node)
739 style.__dict__.update(extra_style)
741 for i in self._textual(node).split('\n'):
742 result.append(platypus.Paragraph(i, style, **(utils.attr_get(node, [], {'bulletText':'str'}))))
744 elif node.tag=='barCode':
746 from reportlab.graphics.barcode import code128
747 from reportlab.graphics.barcode import code39
748 from reportlab.graphics.barcode import code93
749 from reportlab.graphics.barcode import common
750 from reportlab.graphics.barcode import fourstate
751 from reportlab.graphics.barcode import usps
752 from reportlab.graphics.barcode import createBarcodeDrawing
755 _logger.warning("Cannot use barcode renderers:", exc_info=True)
757 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'})
759 'codabar': lambda x: common.Codabar(x, **args),
760 'code11': lambda x: common.Code11(x, **args),
761 'code128': lambda x: code128.Code128(str(x), **args),
762 'standard39': lambda x: code39.Standard39(str(x), **args),
763 'standard93': lambda x: code93.Standard93(str(x), **args),
764 'i2of5': lambda x: common.I2of5(x, **args),
765 'extended39': lambda x: code39.Extended39(str(x), **args),
766 'extended93': lambda x: code93.Extended93(str(x), **args),
767 'msi': lambda x: common.MSI(x, **args),
768 'fim': lambda x: usps.FIM(x, **args),
769 'postnet': lambda x: usps.POSTNET(x, **args),
770 'ean13': lambda x: createBarcodeDrawing('EAN13', value=str(x), **args),
771 'qrcode': lambda x: createBarcodeDrawing('QR', value=x, **args),
775 code = node.get('code').lower()
776 return codes[code](self._textual(node))
777 elif node.tag=='name':
778 self.styles.names[ node.get('id')] = node.get('value')
780 elif node.tag=='xpre':
781 style = self.styles.para_style_get(node)
782 return platypus.XPreformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int','frags':'int'})))
783 elif node.tag=='pre':
784 style = self.styles.para_style_get(node)
785 return platypus.Preformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int'})))
786 elif node.tag=='illustration':
787 return self._illustration(node)
788 elif node.tag=='blockTable':
789 return self._table(node)
790 elif node.tag=='title':
791 styles = reportlab.lib.styles.getSampleStyleSheet()
792 style = styles['Title']
793 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
794 elif re.match('^h([1-9]+[0-9]*)$', (node.tag or '')):
795 styles = reportlab.lib.styles.getSampleStyleSheet()
796 style = styles['Heading'+str(node.tag[1:])]
797 return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
798 elif node.tag=='image':
800 if not node.get('file'):
802 if node.get('name') in self.doc.images:
803 _logger.debug("Image %s read ", node.get('name'))
804 image_data = self.doc.images[node.get('name')].read()
806 _logger.warning("Image %s not defined", node.get('name'))
811 if self.localcontext:
812 newtext = utils._process_text(self, node.text or '')
813 image_data = base64.decodestring(newtext)
815 _logger.debug("No inline image data")
817 image = StringIO(image_data)
819 _logger.debug("Image get from file %s", node.get('file'))
820 image = _open_image(node.get('file'), path=self.doc.path)
821 return platypus.Image(image, mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
822 elif node.tag=='spacer':
823 if node.get('width'):
824 width = utils.unit_get(node.get('width'))
826 width = utils.unit_get('1cm')
827 length = utils.unit_get(node.get('length'))
828 return platypus.Spacer(width=width, height=length)
829 elif node.tag=='section':
830 return self.render(node)
831 elif node.tag == 'pageNumberReset':
833 elif node.tag in ('pageBreak', 'nextPage'):
834 return platypus.PageBreak()
835 elif node.tag=='condPageBreak':
836 return platypus.CondPageBreak(**(utils.attr_get(node, ['height'])))
837 elif node.tag=='setNextTemplate':
838 return platypus.NextPageTemplate(str(node.get('name')))
839 elif node.tag=='nextFrame':
840 return platypus.CondPageBreak(1000) # TODO: change the 1000 !
841 elif node.tag == 'setNextFrame':
842 from reportlab.platypus.doctemplate import NextFrameFlowable
843 return NextFrameFlowable(str(node.get('name')))
844 elif node.tag == 'currentFrame':
845 from reportlab.platypus.doctemplate import CurrentFrameFlowable
846 return CurrentFrameFlowable(str(node.get('name')))
847 elif node.tag == 'frameEnd':
848 return EndFrameFlowable()
849 elif node.tag == 'hr':
850 width_hr=node.get('width') or '100%'
851 color_hr=node.get('color') or 'black'
852 thickness_hr=node.get('thickness') or 1
853 lineCap_hr=node.get('lineCap') or 'round'
854 return platypus.flowables.HRFlowable(width=width_hr,color=color.get(color_hr),thickness=float(thickness_hr),lineCap=str(lineCap_hr))
856 sys.stderr.write('Warning: flowable not yet implemented: %s !\n' % (node.tag,))
859 def render(self, node_story):
860 def process_story(node_story):
862 for node in utils._child_get(node_story, self):
863 if node.tag == etree.Comment:
866 flow = self._flowable(node)
868 if isinstance(flow,list):
869 sub_story = sub_story + flow
871 sub_story.append(flow)
873 return process_story(node_story)
876 class EndFrameFlowable(ActionFlowable):
877 def __init__(self,resume=0):
878 ActionFlowable.__init__(self,('frameEnd',resume))
880 class TinyDocTemplate(platypus.BaseDocTemplate):
881 def ___handle_pageBegin(self):
882 self.page = self.page + 1
883 self.pageTemplate.beforeDrawPage(self.canv,self)
884 self.pageTemplate.checkPageSize(self.canv,self)
885 self.pageTemplate.onPage(self.canv,self)
886 for f in self.pageTemplate.frames: f._reset()
888 self._curPageFlowableCount = 0
889 if hasattr(self,'_nextFrameIndex'):
890 del self._nextFrameIndex
891 for f in self.pageTemplate.frames:
895 self.handle_frameBegin()
896 def afterFlowable(self, flowable):
897 if isinstance(flowable, PageReset):
898 self.canv._pageCount=self.page
901 self.canv._pageNumber = 0
903 class _rml_template(object):
904 def __init__(self, localcontext, out, node, doc, images=None, path='.', title=None):
908 localcontext={'internal_header':True}
909 self.localcontext = localcontext
914 pagesize_map = {'a4': A4,
918 if self.localcontext.get('company'):
919 pageSize = pagesize_map.get(self.localcontext.get('company').paper_format, A4)
920 if node.get('pageSize'):
921 ps = map(lambda x:x.strip(), node.get('pageSize').replace(')', '').replace('(', '').split(','))
922 pageSize = ( utils.unit_get(ps[0]),utils.unit_get(ps[1]) )
924 self.doc_tmpl = TinyDocTemplate(out, pagesize=pageSize, **utils.attr_get(node, ['leftMargin','rightMargin','topMargin','bottomMargin'], {'allowSplitting':'int','showBoundary':'bool','rotation':'int','title':'str','author':'str'}))
925 self.page_templates = []
926 self.styles = doc.styles
929 pts = node.findall('pageTemplate')
932 for frame_el in pt.findall('frame'):
933 frame = platypus.Frame( **(utils.attr_get(frame_el, ['x1','y1', 'width','height', 'leftPadding', 'rightPadding', 'bottomPadding', 'topPadding'], {'id':'str', 'showBoundary':'bool'})) )
934 if utils.attr_get(frame_el, ['last']):
935 frame.lastFrame = True
936 frames.append( frame )
938 gr = pt.findall('pageGraphics')\
939 or pt[1].findall('pageGraphics')
940 except Exception: # FIXME: be even more specific, perhaps?
943 # self.image=[ n for n in utils._child_get(gr[0], self) if n.tag=='image' or not self.localcontext]
944 drw = _rml_draw(self.localcontext,gr[0], self.doc, images=images, path=self.path, title=self.title)
945 self.page_templates.append( platypus.PageTemplate(frames=frames, onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) ))
947 drw = _rml_draw(self.localcontext,node,self.doc,title=self.title)
948 self.page_templates.append( platypus.PageTemplate(frames=frames,onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) ))
949 self.doc_tmpl.addPageTemplates(self.page_templates)
951 def render(self, node_stories):
952 if self.localcontext and not self.localcontext.get('internal_header',False):
953 del self.localcontext['internal_header']
955 r = _rml_flowable(self.doc,self.localcontext, images=self.images, path=self.path, title=self.title)
957 for node_story in node_stories:
959 fis.append(platypus.PageBreak())
960 fis += r.render(node_story)
961 # Reset Page Number with new story tag
962 fis.append(PageReset())
964 if self.localcontext and self.localcontext.get('internal_header',False):
965 self.doc_tmpl.afterFlowable(fis)
966 self.doc_tmpl.build(fis,canvasmaker=NumberedCanvas)
968 fis.append(PageCount())
969 self.doc_tmpl.build(fis)
971 def parseNode(rml, localcontext=None, fout=None, images=None, path='.', title=None):
972 node = etree.XML(rml)
973 r = _rml_doc(node, localcontext, images, path, title=title)
974 #try to override some font mappings
976 from customfonts import SetCustomFonts
979 # means there is no custom fonts mapping in this system.
982 _logger.warning('Cannot set font mapping', exc_info=True)
988 def parseString(rml, localcontext=None, fout=None, images=None, path='.', title=None):
989 node = etree.XML(rml)
990 r = _rml_doc(node, localcontext, images, path, title=title)
992 #try to override some font mappings
994 from customfonts import SetCustomFonts
1000 fp = file(fout,'wb')
1007 return fp.getvalue()
1009 def trml2pdf_help():
1010 print 'Usage: trml2pdf input.rml >output.pdf'
1011 print 'Render the standard input (RML) and output a PDF file'
1014 if __name__=="__main__":
1016 if sys.argv[1]=='--help':
1018 print parseString(file(sys.argv[1], 'r').read()),
1020 print 'Usage: trml2pdf input.rml >output.pdf'
1021 print 'Try \'trml2pdf --help\' for more information.'
1024 # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: