123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044 |
- # -*- encoding: utf-8 -*-
- ##############################################################################
- #
- # Copyright (c) 2013 ZestyBeanz Technologies Pvt. Ltd.
- # (http://wwww.zbeanztech.com)
- # contact@zbeanztech.com
- # prajul@zbeanztech.com
- #
- # This program is free software: you can redistribute it and/or modify
- # it under the terms of the GNU General Public License as published by
- # the Free Software Foundation, either version 3 of the License, or
- # (at your option) any later version.
- #
- # This program is distributed in the hope that it will be useful,
- # but WITHOUT ANY WARRANTY; without even the implied warranty of
- # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- # GNU General Public License for more details.
- #
- # You should have received a copy of the GNU General Public License
- # along with this program. If not, see <http://www.gnu.org/licenses/>.
- #
- ##############################################################################
- import sys
- import copy
- import reportlab
- import re
- from reportlab.pdfgen import canvas
- from reportlab import platypus
- from openerp.report.render.rml2pdf import utils
- from openerp.report.render.rml2pdf import color
- import os
- import logging
- from lxml import etree
- import base64
- from reportlab.platypus.doctemplate import ActionFlowable
- from openerp.tools.safe_eval import safe_eval as eval
- from reportlab.lib.units import inch,cm,mm
- from openerp.tools.misc import file_open
- from reportlab.pdfbase import pdfmetrics
- from reportlab.lib.pagesizes import A4, letter
- try:
- from cStringIO import StringIO
- _hush_pyflakes = [ StringIO ]
- except ImportError:
- from StringIO import StringIO
- _logger = logging.getLogger(__name__)
- encoding = 'utf-8'
- def _open_image(filename, path=None):
- """Attempt to open a binary file and return the descriptor
- """
- if os.path.isfile(filename):
- return open(filename, 'rb')
- for p in (path or []):
- if p and os.path.isabs(p):
- fullpath = os.path.join(p, filename)
- if os.path.isfile(fullpath):
- return open(fullpath, 'rb')
- try:
- if p:
- fullpath = os.path.join(p, filename)
- else:
- fullpath = filename
- return file_open(fullpath)
- except IOError:
- pass
- raise IOError("File %s cannot be found in image path" % filename)
- class NumberedCanvas(canvas.Canvas):
- def __init__(self, *args, **kwargs):
- canvas.Canvas.__init__(self, *args, **kwargs)
- self._codes = []
- self._flag=False
- self._pageCount=0
- self._currentPage =0
- self._pageCounter=0
- self.pages={}
- def showPage(self):
- self._currentPage +=1
- if not self._flag:
- self._pageCount += 1
- else:
- self.pages.update({self._currentPage:self._pageCount})
- self._codes.append({'code': self._code, 'stack': self._codeStack})
- self._startPage()
- self._flag=False
- def pageCount(self):
- if self.pages.get(self._pageCounter,False):
- self._pageNumber=0
- self._pageCounter +=1
- key=self._pageCounter
- if not self.pages.get(key,False):
- while not self.pages.get(key,False):
- key += 1
- self.setFont("Helvetica", 8)
- self.drawRightString((self._pagesize[0]-30), (self._pagesize[1]-40),
- " %(this)i / %(total)i" % {
- 'this': self._pageNumber+1,
- 'total': self.pages.get(key,False),
- }
- )
- def save(self):
- """add page info to each page (page x of y)"""
- # reset page counter
- self._pageNumber = 0
- for code in self._codes:
- self._code = code['code']
- self._codeStack = code['stack']
- self.pageCount()
- canvas.Canvas.showPage(self)
- # self.restoreState()
- self._doc.SaveToFile(self._filename, self)
- class PageCount(platypus.Flowable):
- def __init__(self, story_count=0):
- platypus.Flowable.__init__(self)
- self.story_count = story_count
- def draw(self):
- self.canv.beginForm("pageCount%d" % self.story_count)
- self.canv.setFont("Helvetica", utils.unit_get(str(8)))
- self.canv.drawString(0, 0, str(self.canv.getPageNumber()))
- self.canv.endForm()
- class PageReset(platypus.Flowable):
- def draw(self):
- self.canv._doPageReset = True
- class PageReset(platypus.Flowable):
- def draw(self):
- self.canv._doPageReset = True
- class _rml_styles(object,):
- def __init__(self, nodes, localcontext):
- self.localcontext = localcontext
- self.styles = {}
- self.styles_obj = {}
- self.names = {}
- self.table_styles = {}
- self.default_style = reportlab.lib.styles.getSampleStyleSheet()
- for node in nodes:
- for style in node.findall('blockTableStyle'):
- self.table_styles[style.get('id')] = self._table_style_get(style)
- for style in node.findall('paraStyle'):
- sname = style.get('name')
- self.styles[sname] = self._para_style_update(style)
- self.styles_obj[sname] = reportlab.lib.styles.ParagraphStyle(sname, self.default_style["Normal"], **self.styles[sname])
- for variable in node.findall('initialize'):
- for name in variable.findall('name'):
- self.names[ name.get('id')] = name.get('value')
- def _para_style_update(self, node):
- data = {}
- for attr in ['textColor', 'backColor', 'bulletColor', 'borderColor']:
- if node.get(attr):
- data[attr] = color.get(node.get(attr))
- for attr in ['fontName', 'bulletFontName', 'bulletText']:
- if node.get(attr):
- data[attr] = node.get(attr)
- for attr in ['fontSize', 'leftIndent', 'rightIndent', 'spaceBefore', 'spaceAfter',
- 'firstLineIndent', 'bulletIndent', 'bulletFontSize', 'leading',
- 'borderWidth','borderPadding','borderRadius']:
- if node.get(attr):
- data[attr] = utils.unit_get(node.get(attr))
- if node.get('alignment'):
- align = {
- 'right':reportlab.lib.enums.TA_RIGHT,
- 'center':reportlab.lib.enums.TA_CENTER,
- 'justify':reportlab.lib.enums.TA_JUSTIFY
- }
- data['alignment'] = align.get(node.get('alignment').lower(), reportlab.lib.enums.TA_LEFT)
- return data
- def _table_style_get(self, style_node):
- styles = []
- for node in style_node:
- start = utils.tuple_int_get(node, 'start', (0,0) )
- stop = utils.tuple_int_get(node, 'stop', (-1,-1) )
- if node.tag=='blockValign':
- styles.append(('VALIGN', start, stop, str(node.get('value'))))
- elif node.tag=='blockFont':
- styles.append(('FONT', start, stop, str(node.get('name'))))
- elif node.tag=='blockTextColor':
- styles.append(('TEXTCOLOR', start, stop, color.get(str(node.get('colorName')))))
- elif node.tag=='blockLeading':
- styles.append(('LEADING', start, stop, utils.unit_get(node.get('length'))))
- elif node.tag=='blockAlignment':
- styles.append(('ALIGNMENT', start, stop, str(node.get('value'))))
- elif node.tag=='blockSpan':
- styles.append(('SPAN', start, stop))
- elif node.tag=='blockLeftPadding':
- styles.append(('LEFTPADDING', start, stop, utils.unit_get(node.get('length'))))
- elif node.tag=='blockRightPadding':
- styles.append(('RIGHTPADDING', start, stop, utils.unit_get(node.get('length'))))
- elif node.tag=='blockTopPadding':
- styles.append(('TOPPADDING', start, stop, utils.unit_get(node.get('length'))))
- elif node.tag=='blockBottomPadding':
- styles.append(('BOTTOMPADDING', start, stop, utils.unit_get(node.get('length'))))
- elif node.tag=='blockBackground':
- styles.append(('BACKGROUND', start, stop, color.get(node.get('colorName'))))
- if node.get('size'):
- styles.append(('FONTSIZE', start, stop, utils.unit_get(node.get('size'))))
- elif node.tag=='lineStyle':
- kind = node.get('kind')
- kind_list = [ 'GRID', 'BOX', 'OUTLINE', 'INNERGRID', 'LINEBELOW', 'LINEABOVE','LINEBEFORE', 'LINEAFTER' ]
- assert kind in kind_list
- thick = 1
- if node.get('thickness'):
- thick = float(node.get('thickness'))
- styles.append((kind, start, stop, thick, color.get(node.get('colorName'))))
- return platypus.tables.TableStyle(styles)
- def para_style_get(self, node):
- style = False
- sname = node.get('style')
- if sname:
- if sname in self.styles_obj:
- style = self.styles_obj[sname]
- else:
- _logger.warning('Warning: style not found, %s - setting default!\n' % (node.get('style'),) )
- if not style:
- style = self.default_style['Normal']
- para_update = self._para_style_update(node)
- if para_update:
- # update style only is necessary
- style = copy.deepcopy(style)
- style.__dict__.update(para_update)
- return style
- class _rml_doc(object):
- def __init__(self, node, localcontext=None, images=None, path='.', title=None):
- if images is None:
- images = {}
- if localcontext is None:
- localcontext = {}
- self.localcontext = localcontext
- self.etree = node
- self.filename = self.etree.get('filename')
- self.images = images
- self.path = path
- self.title = title
- def docinit(self, els):
- from reportlab.lib.fonts import addMapping
- from reportlab.pdfbase import pdfmetrics
- from reportlab.pdfbase.ttfonts import TTFont
- for node in els:
- for font in node.findall('registerFont'):
- name = font.get('fontName').encode('ascii')
- fname = font.get('fontFile').encode('ascii')
- if name not in pdfmetrics._fonts:
- pdfmetrics.registerFont(TTFont(name, fname))
- addMapping(name, 0, 0, name) #normal
- addMapping(name, 0, 1, name) #italic
- addMapping(name, 1, 0, name) #bold
- addMapping(name, 1, 1, name) #italic and bold
- def setTTFontMapping(self,face, fontname, filename, mode='all'):
- from reportlab.lib.fonts import addMapping
- from reportlab.pdfbase import pdfmetrics
- from reportlab.pdfbase.ttfonts import TTFont
- if fontname not in pdfmetrics._fonts:
- pdfmetrics.registerFont(TTFont(fontname, filename))
- if mode == 'all':
- addMapping(face, 0, 0, fontname) #normal
- addMapping(face, 0, 1, fontname) #italic
- addMapping(face, 1, 0, fontname) #bold
- addMapping(face, 1, 1, fontname) #italic and bold
- elif (mode== 'normal') or (mode == 'regular'):
- addMapping(face, 0, 0, fontname) #normal
- elif mode == 'italic':
- addMapping(face, 0, 1, fontname) #italic
- elif mode == 'bold':
- addMapping(face, 1, 0, fontname) #bold
- elif mode == 'bolditalic':
- addMapping(face, 1, 1, fontname) #italic and bold
- def _textual_image(self, node):
- rc = ''
- for n in node:
- rc +=( etree.tostring(n) or '') + n.tail
- return base64.decodestring(node.tostring())
- def _images(self, el):
- result = {}
- for node in el.findall('.//image'):
- rc =( node.text or '')
- result[node.get('name')] = base64.decodestring(rc)
- return result
- def render(self, out):
- el = self.etree.findall('.//docinit')
- if el:
- self.docinit(el)
- el = self.etree.findall('.//stylesheet')
- self.styles = _rml_styles(el,self.localcontext)
- el = self.etree.findall('.//images')
- if el:
- self.images.update( self._images(el[0]) )
- el = self.etree.findall('.//template')
- if len(el):
- pt_obj = _rml_template(self.localcontext, out, el[0], self, images=self.images, path=self.path, title=self.title)
- el = utils._child_get(self.etree, self, 'story')
- pt_obj.render(el)
- else:
- self.canvas = canvas.Canvas(out)
- pd = self.etree.find('pageDrawing')[0]
- pd_obj = _rml_canvas(self.canvas, self.localcontext, None, self, self.images, path=self.path, title=self.title)
- pd_obj.render(pd)
- self.canvas.showPage()
- self.canvas.save()
- class _rml_canvas(object):
- def __init__(self, canvas, localcontext, doc_tmpl=None, doc=None, images=None, path='.', title=None):
- if images is None:
- images = {}
- self.localcontext = localcontext
- self.canvas = canvas
- self.styles = doc.styles
- self.doc_tmpl = doc_tmpl
- self.doc = doc
- self.images = images
- self.path = path
- self.title = title
- if self.title:
- self.canvas.setTitle(self.title)
- def _textual(self, node, x=0, y=0):
- text = node.text and node.text.encode('utf-8') or ''
- rc = utils._process_text(self, text)
- for n in node:
- if n.tag == 'seq':
- from reportlab.lib.sequencer import getSequencer
- seq = getSequencer()
- rc += str(seq.next(n.get('id')))
- if n.tag == 'pageCount':
- if x or y:
- self.canvas.translate(x,y)
- self.canvas.doForm('pageCount%s' % (self.canvas._storyCount,))
- if x or y:
- self.canvas.translate(-x,-y)
- if n.tag == 'pageNumber':
- rc += str(self.canvas.getPageNumber())
- rc += utils._process_text(self, n.tail)
- return rc.replace('\n','')
- def _drawString(self, node):
- v = utils.attr_get(node, ['x','y'])
- text=self._textual(node, **v)
- text = utils.xml2str(text)
- self.canvas.drawString(text=text, **v)
- def _drawCenteredString(self, node):
- v = utils.attr_get(node, ['x','y'])
- text=self._textual(node, **v)
- text = utils.xml2str(text)
- self.canvas.drawCentredString(text=text, **v)
- def _drawRightString(self, node):
- v = utils.attr_get(node, ['x','y'])
- text=self._textual(node, **v)
- text = utils.xml2str(text)
- self.canvas.drawRightString(text=text, **v)
- def _rect(self, node):
- if node.get('round'):
- self.canvas.roundRect(radius=utils.unit_get(node.get('round')), **utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
- else:
- self.canvas.rect(**utils.attr_get(node, ['x','y','width','height'], {'fill':'bool','stroke':'bool'}))
- def _ellipse(self, node):
- x1 = utils.unit_get(node.get('x'))
- x2 = utils.unit_get(node.get('width'))
- y1 = utils.unit_get(node.get('y'))
- y2 = utils.unit_get(node.get('height'))
- self.canvas.ellipse(x1,y1,x2,y2, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
- def _curves(self, node):
- line_str = node.text.split()
- lines = []
- while len(line_str)>7:
- self.canvas.bezier(*[utils.unit_get(l) for l in line_str[0:8]])
- line_str = line_str[8:]
- def _lines(self, node):
- line_str = node.text.split()
- lines = []
- while len(line_str)>3:
- lines.append([utils.unit_get(l) for l in line_str[0:4]])
- line_str = line_str[4:]
- self.canvas.lines(lines)
- def _grid(self, node):
- xlist = [utils.unit_get(s) for s in node.get('xs').split(',')]
- ylist = [utils.unit_get(s) for s in node.get('ys').split(',')]
- self.canvas.grid(xlist, ylist)
- def _translate(self, node):
- dx = utils.unit_get(node.get('dx')) or 0
- dy = utils.unit_get(node.get('dy')) or 0
- self.canvas.translate(dx,dy)
- def _circle(self, node):
- 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'}))
- def _place(self, node):
- flows = _rml_flowable(self.doc, self.localcontext, images=self.images, path=self.path, title=self.title).render(node)
- infos = utils.attr_get(node, ['x','y','width','height'])
- infos['y']+=infos['height']
- for flow in flows:
- w,h = flow.wrap(infos['width'], infos['height'])
- if w<=infos['width'] and h<=infos['height']:
- infos['y']-=h
- flow.drawOn(self.canvas,infos['x'],infos['y'])
- infos['height']-=h
- else:
- raise ValueError("Not enough space")
- def _line_mode(self, node):
- ljoin = {'round':1, 'mitered':0, 'bevelled':2}
- lcap = {'default':0, 'round':1, 'square':2}
- if node.get('width'):
- self.canvas.setLineWidth(utils.unit_get(node.get('width')))
- if node.get('join'):
- self.canvas.setLineJoin(ljoin[node.get('join')])
- if node.get('cap'):
- self.canvas.setLineCap(lcap[node.get('cap')])
- if node.get('miterLimit'):
- self.canvas.setDash(utils.unit_get(node.get('miterLimit')))
- if node.get('dash'):
- dashes = node.get('dash').split(',')
- for x in range(len(dashes)):
- dashes[x]=utils.unit_get(dashes[x])
- self.canvas.setDash(node.get('dash').split(','))
- def _image(self, node):
- import urllib
- import urlparse
- from reportlab.lib.utils import ImageReader
- nfile = node.get('file')
- if not nfile:
- if node.get('name'):
- image_data = self.images[node.get('name')]
- _logger.debug("Image %s used", node.get('name'))
- s = StringIO(image_data)
- else:
- newtext = node.text
- if self.localcontext:
- res = utils._regex.findall(newtext)
- for key in res:
- newtext = eval(key, {}, self.localcontext) or ''
- image_data = None
- if newtext:
- image_data = base64.decodestring(newtext)
- if image_data:
- s = StringIO(image_data)
- else:
- _logger.debug("No image data!")
- return False
- else:
- if nfile in self.images:
- s = StringIO(self.images[nfile])
- else:
- try:
- up = urlparse.urlparse(str(nfile))
- except ValueError:
- up = False
- if up and up.scheme:
- # RFC: do we really want to open external URLs?
- # Are we safe from cross-site scripting or attacks?
- _logger.debug("Retrieve image from %s", nfile)
- u = urllib.urlopen(str(nfile))
- s = StringIO(u.read())
- else:
- _logger.debug("Open image file %s ", nfile)
- s = _open_image(nfile, path=self.path)
- try:
- img = ImageReader(s)
- (sx,sy) = img.getSize()
- _logger.debug("Image is %dx%d", sx, sy)
- args = { 'x': 0.0, 'y': 0.0, 'mask': 'auto'}
- for tag in ('width','height','x','y'):
- if node.get(tag):
- args[tag] = utils.unit_get(node.get(tag))
- if ('width' in args) and (not 'height' in args):
- args['height'] = sy * args['width'] / sx
- elif ('height' in args) and (not 'width' in args):
- args['width'] = sx * args['height'] / sy
- elif ('width' in args) and ('height' in args):
- if (float(args['width'])/args['height'])>(float(sx)>sy):
- args['width'] = sx * args['height'] / sy
- else:
- args['height'] = sy * args['width'] / sx
- self.canvas.drawImage(img, **args)
- finally:
- s.close()
- # self.canvas._doc.SaveToFile(self.canvas._filename, self.canvas)
- def _path(self, node):
- self.path = self.canvas.beginPath()
- self.path.moveTo(**utils.attr_get(node, ['x','y']))
- for n in utils._child_get(node, self):
- if not n.text :
- if n.tag=='moveto':
- vals = utils.text_get(n).split()
- self.path.moveTo(utils.unit_get(vals[0]), utils.unit_get(vals[1]))
- elif n.tag=='curvesto':
- vals = utils.text_get(n).split()
- while len(vals)>5:
- pos=[]
- while len(pos)<6:
- pos.append(utils.unit_get(vals.pop(0)))
- self.path.curveTo(*pos)
- elif n.text:
- data = n.text.split() # Not sure if I must merge all TEXT_NODE ?
- while len(data)>1:
- x = utils.unit_get(data.pop(0))
- y = utils.unit_get(data.pop(0))
- self.path.lineTo(x,y)
- if (not node.get('close')) or utils.bool_get(node.get('close')):
- self.path.close()
- self.canvas.drawPath(self.path, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
- def setFont(self, node):
- fontname = node.get('name')
- if fontname not in pdfmetrics.getRegisteredFontNames()\
- or fontname not in pdfmetrics.standardFonts:
- # let reportlab attempt to find it
- try:
- pdfmetrics.getFont(fontname)
- except Exception:
- _logger.debug('Could not locate font %s, substituting default: %s',
- fontname,
- self.canvas._fontname)
- fontname = self.canvas._fontname
- return self.canvas.setFont(fontname, utils.unit_get(node.get('size')))
- def render(self, node):
- tags = {
- 'drawCentredString': self._drawCenteredString,
- 'drawRightString': self._drawRightString,
- 'drawString': self._drawString,
- 'rect': self._rect,
- 'ellipse': self._ellipse,
- 'lines': self._lines,
- 'grid': self._grid,
- 'curves': self._curves,
- 'fill': lambda node: self.canvas.setFillColor(color.get(node.get('color'))),
- 'stroke': lambda node: self.canvas.setStrokeColor(color.get(node.get('color'))),
- 'setFont': self.setFont ,
- 'place': self._place,
- 'circle': self._circle,
- 'lineMode': self._line_mode,
- 'path': self._path,
- 'rotate': lambda node: self.canvas.rotate(float(node.get('degrees'))),
- 'translate': self._translate,
- 'image': self._image
- }
- for n in utils._child_get(node, self):
- if n.tag in tags:
- tags[n.tag](n)
- class _rml_draw(object):
- def __init__(self, localcontext, node, styles, images=None, path='.', title=None):
- if images is None:
- images = {}
- self.localcontext = localcontext
- self.node = node
- self.styles = styles
- self.canvas = None
- self.images = images
- self.path = path
- self.canvas_title = title
- def render(self, canvas, doc):
- canvas.saveState()
- cnv = _rml_canvas(canvas, self.localcontext, doc, self.styles, images=self.images, path=self.path, title=self.canvas_title)
- cnv.render(self.node)
- canvas.restoreState()
- class _rml_Illustration(platypus.flowables.Flowable):
- def __init__(self, node, localcontext, styles, self2):
- self.localcontext = (localcontext or {}).copy()
- self.node = node
- self.styles = styles
- self.width = utils.unit_get(node.get('width'))
- self.height = utils.unit_get(node.get('height'))
- self.self2 = self2
- def wrap(self, *args):
- return self.width, self.height
- def draw(self):
- drw = _rml_draw(self.localcontext ,self.node,self.styles, images=self.self2.images, path=self.self2.path, title=self.self2.title)
- drw.render(self.canv, None)
- class _rml_flowable(object):
- def __init__(self, doc, localcontext, images=None, path='.', title=None):
- if images is None:
- images = {}
- self.localcontext = localcontext
- self.doc = doc
- self.styles = doc.styles
- self.images = images
- self.path = path
- self.title = title
- def _textual(self, node):
- rc1 = utils._process_text(self, node.text or '')
- for n in utils._child_get(node,self):
- txt_n = copy.deepcopy(n)
- for key in txt_n.attrib.keys():
- if key in ('rml_except', 'rml_loop', 'rml_tag'):
- del txt_n.attrib[key]
- if not n.tag == 'bullet':
- txt_n.text = utils.xml2str(self._textual(n))
- txt_n.tail = n.tail and utils.xml2str(utils._process_text(self, n.tail.replace('\n',''))) or ''
- rc1 += etree.tostring(txt_n)
- return rc1
- def _table(self, node):
- children = utils._child_get(node,self,'tr')
- if not children:
- return None
- length = 0
- colwidths = None
- rowheights = None
- data = []
- styles = []
- posy = 0
- for tr in children:
- paraStyle = None
- if tr.get('style'):
- st = copy.deepcopy(self.styles.table_styles[tr.get('style')])
- for si in range(len(st._cmds)):
- s = list(st._cmds[si])
- s[1] = (s[1][0],posy)
- s[2] = (s[2][0],posy)
- st._cmds[si] = tuple(s)
- styles.append(st)
- if tr.get('paraStyle'):
- paraStyle = self.styles.styles[tr.get('paraStyle')]
- data2 = []
- posx = 0
- for td in utils._child_get(tr, self,'td'):
- if td.get('style'):
- st = copy.deepcopy(self.styles.table_styles[td.get('style')])
- for s in st._cmds:
- s[1][1] = posy
- s[2][1] = posy
- s[1][0] = posx
- s[2][0] = posx
- styles.append(st)
- if td.get('paraStyle'):
- # TODO: merge styles
- paraStyle = self.styles.styles[td.get('paraStyle')]
- posx += 1
- flow = []
- for n in utils._child_get(td, self):
- if n.tag == etree.Comment:
- n.text = ''
- continue
- fl = self._flowable(n, extra_style=paraStyle)
- if isinstance(fl,list):
- flow += fl
- else:
- flow.append( fl )
- if not len(flow):
- flow = self._textual(td)
- data2.append( flow )
- if len(data2)>length:
- length=len(data2)
- for ab in data:
- while len(ab)<length:
- ab.append('')
- while len(data2)<length:
- data2.append('')
- data.append( data2 )
- posy += 1
- if node.get('colWidths'):
- assert length == len(node.get('colWidths').split(','))
- colwidths = [utils.unit_get(f.strip()) for f in node.get('colWidths').split(',')]
- if node.get('rowHeights'):
- rowheights = [utils.unit_get(f.strip()) for f in node.get('rowHeights').split(',')]
- if len(rowheights) == 1:
- rowheights = rowheights[0]
- table = platypus.LongTable(data = data, colWidths=colwidths, rowHeights=rowheights, **(utils.attr_get(node, ['splitByRow'] ,{'repeatRows':'int','repeatCols':'int'})))
- if node.get('style'):
- table.setStyle(self.styles.table_styles[node.get('style')])
- for s in styles:
- table.setStyle(s)
- return table
- def _illustration(self, node):
- return _rml_Illustration(node, self.localcontext, self.styles, self)
- def _textual_image(self, node):
- return base64.decodestring(node.text)
- def _pto(self, node):
- sub_story = []
- pto_header = None
- pto_trailer = None
- for node in utils._child_get(node, self):
- if node.tag == etree.Comment:
- node.text = ''
- continue
- elif node.tag=='pto_header':
- pto_header = self.render(node)
- elif node.tag=='pto_trailer':
- pto_trailer = self.render(node)
- else:
- flow = self._flowable(node)
- if flow:
- if isinstance(flow,list):
- sub_story = sub_story + flow
- else:
- sub_story.append(flow)
- return platypus.flowables.PTOContainer(sub_story, trailer=pto_trailer, header=pto_header)
- def _flowable(self, node, extra_style=None):
- if node.tag=='pto':
- return self._pto(node)
- if node.tag=='para':
- style = self.styles.para_style_get(node)
- if extra_style:
- style.__dict__.update(extra_style)
- result = []
- for i in self._textual(node).split('\n'):
- result.append(platypus.Paragraph(i, style, **(utils.attr_get(node, [], {'bulletText':'str'}))))
- return result
- elif node.tag=='barCode':
- try:
- from reportlab.graphics.barcode import code128
- from reportlab.graphics.barcode import code39
- from reportlab.graphics.barcode import code93
- from reportlab.graphics.barcode import common
- from reportlab.graphics.barcode import fourstate
- from reportlab.graphics.barcode import usps
- from reportlab.graphics.barcode import createBarcodeDrawing
- except ImportError:
- _logger.warning("Cannot use barcode renderers:", exc_info=True)
- return None
- 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'})
- codes = {
- 'codabar': lambda x: common.Codabar(x, **args),
- 'code11': lambda x: common.Code11(x, **args),
- 'code128': lambda x: code128.Code128(str(x), **args),
- 'standard39': lambda x: code39.Standard39(str(x), **args),
- 'standard93': lambda x: code93.Standard93(str(x), **args),
- 'i2of5': lambda x: common.I2of5(x, **args),
- 'extended39': lambda x: code39.Extended39(str(x), **args),
- 'extended93': lambda x: code93.Extended93(str(x), **args),
- 'msi': lambda x: common.MSI(x, **args),
- 'fim': lambda x: usps.FIM(x, **args),
- 'postnet': lambda x: usps.POSTNET(x, **args),
- 'ean13': lambda x: createBarcodeDrawing('EAN13', value=str(x), **args),
- 'qrcode': lambda x: createBarcodeDrawing('QR', value=x, **args),
- }
- code = 'code128'
- if node.get('code'):
- code = node.get('code').lower()
- return codes[code](self._textual(node))
- elif node.tag=='name':
- self.styles.names[ node.get('id')] = node.get('value')
- return None
- elif node.tag=='xpre':
- style = self.styles.para_style_get(node)
- return platypus.XPreformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int','frags':'int'})))
- elif node.tag=='pre':
- style = self.styles.para_style_get(node)
- return platypus.Preformatted(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str','dedent':'int'})))
- elif node.tag=='illustration':
- return self._illustration(node)
- elif node.tag=='blockTable':
- return self._table(node)
- elif node.tag=='title':
- styles = reportlab.lib.styles.getSampleStyleSheet()
- style = styles['Title']
- return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
- elif re.match('^h([1-9]+[0-9]*)$', (node.tag or '')):
- styles = reportlab.lib.styles.getSampleStyleSheet()
- style = styles['Heading'+str(node.tag[1:])]
- return platypus.Paragraph(self._textual(node), style, **(utils.attr_get(node, [], {'bulletText':'str'})))
- elif node.tag=='image':
- image_data = False
- if not node.get('file'):
- if node.get('name'):
- if node.get('name') in self.doc.images:
- _logger.debug("Image %s read ", node.get('name'))
- image_data = self.doc.images[node.get('name')].read()
- else:
- _logger.warning("Image %s not defined", node.get('name'))
- return False
- else:
- import base64
- newtext = node.text
- if self.localcontext:
- newtext = utils._process_text(self, node.text or '')
- image_data = base64.decodestring(newtext)
- if not image_data:
- _logger.debug("No inline image data")
- return False
- image = StringIO(image_data)
- else:
- _logger.debug("Image get from file %s", node.get('file'))
- image = _open_image(node.get('file'), path=self.doc.path)
- return platypus.Image(image, mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
- elif node.tag=='spacer':
- if node.get('width'):
- width = utils.unit_get(node.get('width'))
- else:
- width = utils.unit_get('1cm')
- length = utils.unit_get(node.get('length'))
- return platypus.Spacer(width=width, height=length)
- elif node.tag=='section':
- return self.render(node)
- elif node.tag == 'pageNumberReset':
- return PageReset()
- elif node.tag in ('pageBreak', 'nextPage'):
- return platypus.PageBreak()
- elif node.tag=='condPageBreak':
- return platypus.CondPageBreak(**(utils.attr_get(node, ['height'])))
- elif node.tag=='setNextTemplate':
- return platypus.NextPageTemplate(str(node.get('name')))
- elif node.tag=='nextFrame':
- return platypus.CondPageBreak(1000) # TODO: change the 1000 !
- elif node.tag == 'setNextFrame':
- from reportlab.platypus.doctemplate import NextFrameFlowable
- return NextFrameFlowable(str(node.get('name')))
- elif node.tag == 'currentFrame':
- from reportlab.platypus.doctemplate import CurrentFrameFlowable
- return CurrentFrameFlowable(str(node.get('name')))
- elif node.tag == 'frameEnd':
- return EndFrameFlowable()
- elif node.tag == 'hr':
- width_hr=node.get('width') or '100%'
- color_hr=node.get('color') or 'black'
- thickness_hr=node.get('thickness') or 1
- lineCap_hr=node.get('lineCap') or 'round'
- return platypus.flowables.HRFlowable(width=width_hr,color=color.get(color_hr),thickness=float(thickness_hr),lineCap=str(lineCap_hr))
- else:
- sys.stderr.write('Warning: flowable not yet implemented: %s !\n' % (node.tag,))
- return None
- def render(self, node_story):
- def process_story(node_story):
- sub_story = []
- for node in utils._child_get(node_story, self):
- if node.tag == etree.Comment:
- node.text = ''
- continue
- flow = self._flowable(node)
- if flow:
- if isinstance(flow,list):
- sub_story = sub_story + flow
- else:
- sub_story.append(flow)
- return sub_story
- return process_story(node_story)
- class EndFrameFlowable(ActionFlowable):
- def __init__(self,resume=0):
- ActionFlowable.__init__(self,('frameEnd',resume))
- class TinyDocTemplate(platypus.BaseDocTemplate):
- def beforeDocument(self):
- # Store some useful value directly inside canvas, so it's available
- # on flowable drawing (needed for proper PageCount handling)
- self.canv._doPageReset = False
- self.canv._storyCount = 0
- def ___handle_pageBegin(self):
- self.page += 1
- self.pageTemplate.beforeDrawPage(self.canv,self)
- self.pageTemplate.checkPageSize(self.canv,self)
- self.pageTemplate.onPage(self.canv,self)
- for f in self.pageTemplate.frames: f._reset()
- self.beforePage()
- self._curPageFlowableCount = 0
- if hasattr(self,'_nextFrameIndex'):
- del self._nextFrameIndex
- for f in self.pageTemplate.frames:
- if f.id == 'first':
- self.frame = f
- break
- self.handle_frameBegin()
- def afterPage(self):
- if self.canv._doPageReset:
- # Following a <pageReset/> tag:
- # - we reset page number to 0
- # - we add an new PageCount flowable (relative to the current
- # story number), but not for NumeredCanvas at is handle page
- # count itself)
- # NOTE: _rml_template render() method add a PageReset flowable at end
- # of each story, so we're sure to pass here at least once per story.
- if not isinstance(self.canv, NumberedCanvas):
- self.handle_flowable([ PageCount(story_count=self.canv._storyCount) ])
- self.canv._pageCount = self.page
- self.page = 0
- self.canv._flag = True
- self.canv._pageNumber = 0
- self.canv._doPageReset = False
- self.canv._storyCount += 1
- class _rml_template(object):
- def __init__(self, localcontext, out, node, doc, images=None, path='.', title=None):
- if images is None:
- images = {}
- if not localcontext:
- localcontext={'internal_header':True}
- self.localcontext = localcontext
- self.images= images
- self.path = path
- self.title = title
- pagesize_map = {'a4': A4,
- 'us_letter': letter
- }
- pageSize = (841.8897637795275, 595.275590551181)
- 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'}))
- self.page_templates = []
- self.styles = doc.styles
- self.doc = doc
- self.image=[]
- pts = node.findall('pageTemplate')
- for pt in pts:
- frames = []
- for frame_el in pt.findall('frame'):
- frame = platypus.Frame( **(utils.attr_get(frame_el, ['x1','y1', 'width','height', 'leftPadding', 'rightPadding', 'bottomPadding', 'topPadding'], {'id':'str', 'showBoundary':'bool'})) )
- if utils.attr_get(frame_el, ['last']):
- frame.lastFrame = True
- frames.append( frame )
- try :
- gr = pt.findall('pageGraphics')\
- or pt[1].findall('pageGraphics')
- except Exception: # FIXME: be even more specific, perhaps?
- gr=''
- if len(gr):
- # self.image=[ n for n in utils._child_get(gr[0], self) if n.tag=='image' or not self.localcontext]
- drw = _rml_draw(self.localcontext,gr[0], self.doc, images=images, path=self.path, title=self.title)
- self.page_templates.append( platypus.PageTemplate(frames=frames, onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) ))
- else:
- drw = _rml_draw(self.localcontext,node,self.doc,title=self.title)
- self.page_templates.append( platypus.PageTemplate(frames=frames,onPage=drw.render, **utils.attr_get(pt, [], {'id':'str'}) ))
- self.doc_tmpl.addPageTemplates(self.page_templates)
- def render(self, node_stories):
- if self.localcontext and not self.localcontext.get('internal_header',False):
- del self.localcontext['internal_header']
- fis = []
- r = _rml_flowable(self.doc,self.localcontext, images=self.images, path=self.path, title=self.title)
- story_cnt = 0
- for node_story in node_stories:
- if story_cnt > 0:
- fis.append(platypus.PageBreak())
- fis += r.render(node_story)
- # Reset Page Number with new story tag
- fis.append(PageReset())
- story_cnt += 1
- if self.localcontext and self.localcontext.get('internal_header',False):
- self.doc_tmpl.afterFlowable(fis)
- self.doc_tmpl.build(fis,canvasmaker=NumberedCanvas)
- else:
- self.doc_tmpl.build(fis)
- def parseNode(rml, localcontext=None, fout=None, images=None, path='.', title=None):
- node = etree.XML(rml)
- r = _rml_doc(node, localcontext, images, path, title=title)
- #try to override some font mappings
- try:
- from customfonts import SetCustomFonts
- SetCustomFonts(r)
- except ImportError:
- # means there is no custom fonts mapping in this system.
- pass
- except Exception:
- _logger.warning('Cannot set font mapping', exc_info=True)
- pass
- fp = StringIO()
- r.render(fp)
- return fp.getvalue()
- def parseString(rml, localcontext=None, fout=None, images=None, path='.', title=None):
- node = etree.XML(rml)
- r = _rml_doc(node, localcontext, images, path, title=title)
- #try to override some font mappings
- try:
- from customfonts import SetCustomFonts
- SetCustomFonts(r)
- except Exception:
- pass
- if fout:
- fp = file(fout,'wb')
- r.render(fp)
- fp.close()
- return fout
- else:
- fp = StringIO()
- r.render(fp)
- return fp.getvalue()
- def trml2pdf_help():
- print 'Usage: trml2pdf input.rml >output.pdf'
- print 'Render the standard input (RML) and output a PDF file'
- sys.exit(0)
- if __name__=="__main__":
- if len(sys.argv)>1:
- if sys.argv[1]=='--help':
- trml2pdf_help()
- print parseString(file(sys.argv[1], 'r').read()),
- else:
- print 'Usage: trml2pdf input.rml >output.pdf'
- print 'Try \'trml2pdf --help\' for more information.'
- # vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4:
|