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
except ImportError:
from StringIO import StringIO
+_logger = logging.getLogger(__name__)
+
encoding = 'utf-8'
+def select_fontname(fontname, default_fontname):
+ 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.warning('Could not locate font %s, substituting default: %s',
+ fontname, default_fontname)
+ fontname = default_fontname
+ return fontname
+
+
def _open_image(filename, path=None):
"""Attempt to open a binary file and return the descriptor
"""
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={}
+ self._saved_page_states = []
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._saved_page_states.append(dict(self.__dict__))
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 = key + 1
+
+ def save(self):
+ """add page info to each page (page x of y)"""
+ for state in self._saved_page_states:
+ self.__dict__.update(state)
+ self.draw_page_number()
+ canvas.Canvas.showPage(self)
+ canvas.Canvas.save(self)
+
+ def draw_page_number(self):
+ page_count = len(self._saved_page_states)
self.setFont("Helvetica", 8)
self.drawRightString((self._pagesize[0]-30), (self._pagesize[1]-40),
- "Page %(this)i of %(total)i" % {
- 'this': self._pageNumber+1,
- 'total': self.pages.get(key,False),
+ " %(this)i / %(total)i" % {
+ 'this': self._pageNumber,
+ 'total': page_count,
}
)
- 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")
+ 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._pageNumber = 0
+ self.canv._doPageReset = True
class _rml_styles(object,):
def __init__(self, nodes, localcontext):
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])
-
+ if self.default_style.has_key(sname):
+ for key, value in self.styles[sname].items():
+ setattr(self.default_style[sname], key, value)
+ else:
+ 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')
for attr in ['textColor', 'backColor', 'bulletColor', 'borderColor']:
if node.get(attr):
data[attr] = color.get(node.get(attr))
- for attr in ['fontName', 'bulletFontName', 'bulletText']:
+ for attr in ['bulletFontName', 'fontName']:
+ if node.get(attr):
+ fontname= select_fontname(node.get(attr), None)
+ if fontname is not None:
+ data['fontName'] = fontname
+ for attr in ['bulletText']:
if node.get(attr):
data[attr] = node.get(attr)
for attr in ['fontSize', 'leftIndent', 'rightIndent', 'spaceBefore', 'spaceAfter',
if sname in self.styles_obj:
style = self.styles_obj[sname]
else:
- sys.stderr.write('Warning: style not found, %s - setting default!\n' % (node.get('style'),) )
+ _logger.debug('Warning: style not found, %s - setting default!', node.get('style'))
if not style:
style = self.default_style['Normal']
para_update = self._para_style_update(node)
return style
class _rml_doc(object):
- def __init__(self, node, localcontext, images={}, path='.', title=None):
+ 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')
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))
+ #by default, we map the fontName to each style (bold, italic, bold and italic), so that
+ #if there isn't any font defined for one of these style (via a font family), the system
+ #will fallback on the normal font.
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
+ #if registerFontFamily is defined, we register the mapping of the fontName to use for each style.
+ for font_family in node.findall('registerFontFamily'):
+ family_name = font_family.get('normal').encode('ascii')
+ if font_family.get('italic'):
+ addMapping(family_name, 0, 1, font_family.get('italic').encode('ascii'))
+ if font_family.get('bold'):
+ addMapping(family_name, 1, 0, font_family.get('bold').encode('ascii'))
+ if font_family.get('boldItalic'):
+ addMapping(family_name, 1, 1, font_family.get('boldItalic').encode('ascii'))
+
def setTTFontMapping(self,face, fontname, filename, mode='all'):
from reportlab.lib.fonts import addMapping
from reportlab.pdfbase import pdfmetrics
if fontname not in pdfmetrics._fonts:
pdfmetrics.registerFont(TTFont(fontname, filename))
- if (mode == 'all'):
+ 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'):
+ elif mode == 'italic':
addMapping(face, 0, 1, fontname) #italic
- elif (mode == 'bold'):
+ elif mode == 'bold':
addMapping(face, 1, 0, fontname) #bold
- elif (mode == 'bolditalic'):
+ elif mode == 'bolditalic':
addMapping(face, 1, 1, fontname) #italic and bold
def _textual_image(self, node):
self.canvas.save()
class _rml_canvas(object):
- def __init__(self, canvas, localcontext, doc_tmpl=None, doc=None, images={}, path='.', title=None):
+ 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.images = images
self.path = path
self.title = title
- self._logger = logging.getLogger('report.rml.canvas')
if self.title:
self.canvas.setTitle(self.title)
if n.tag == 'pageCount':
if x or y:
self.canvas.translate(x,y)
- self.canvas.doForm('pageCount')
+ self.canvas.doForm('pageCount%s' % (self.canvas._storyCount,))
if x or y:
self.canvas.translate(-x,-y)
if n.tag == 'pageNumber':
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)
+ flows = _rml_flowable(self.doc, self.localcontext, images=self.images, path=self.path, title=self.title, canvas=self.canvas).render(node)
infos = utils.attr_get(node, ['x','y','width','height'])
infos['y']+=infos['height']
flow.drawOn(self.canvas,infos['x'],infos['y'])
infos['height']-=h
else:
- raise ValueError, "Not enough space"
+ raise ValueError("Not enough space")
def _line_mode(self, node):
ljoin = {'round':1, 'mitered':0, 'bevelled':2}
if not nfile:
if node.get('name'):
image_data = self.images[node.get('name')]
- self._logger.debug("Image %s used", node.get('name'))
+ _logger.debug("Image %s used", node.get('name'))
s = StringIO(image_data)
else:
newtext = node.text
if image_data:
s = StringIO(image_data)
else:
- self._logger.debug("No image data!")
+ _logger.debug("No image data!")
return False
else:
if nfile in self.images:
if up and up.scheme:
# RFC: do we really want to open external URLs?
# Are we safe from cross-site scripting or attacks?
- self._logger.debug("Retrieve image from %s", nfile)
+ _logger.debug("Retrieve image from %s", nfile)
u = urllib.urlopen(str(nfile))
s = StringIO(u.read())
else:
- self._logger.debug("Open image file %s ", nfile)
+ _logger.debug("Open image file %s ", nfile)
s = _open_image(nfile, path=self.path)
try:
img = ImageReader(s)
(sx,sy) = img.getSize()
- self._logger.debug("Image is %dx%d", sx, sy)
- args = { 'x': 0.0, 'y': 0.0 }
+ _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))
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:
- logging.getLogger('report.fonts').debug('Could not locate font %s, substituting default: %s',
- fontname,
- self.canvas._fontname)
- fontname = self.canvas._fontname
+ fontname = select_fontname(node.get('name'), self.canvas._fontname)
return self.canvas.setFont(fontname, utils.unit_get(node.get('size')))
def render(self, node):
tags[n.tag](n)
class _rml_draw(object):
- def __init__(self, localcontext ,node, styles, images={}, path='.', title=None):
+ 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.height = utils.unit_get(node.get('height'))
self.self2 = self2
def wrap(self, *args):
- return (self.width, self.height)
+ 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)
+# Workaround for issue #15: https://bitbucket.org/rptlab/reportlab/issue/15/infinite-pages-produced-when-splitting
+original_pto_split = platypus.flowables.PTOContainer.split
+def split(self, availWidth, availHeight):
+ res = original_pto_split(self, availWidth, availHeight)
+ if len(res) > 2 and len(self._content) > 0:
+ header = self._content[0]._ptoinfo.header
+ trailer = self._content[0]._ptoinfo.trailer
+ if isinstance(res[-2], platypus.flowables.UseUpSpace) and len(header + trailer) == len(res[:-2]):
+ return []
+ return res
+platypus.flowables.PTOContainer.split = split
+
class _rml_flowable(object):
- def __init__(self, doc, localcontext, images=None, path='.', title=None):
+ def __init__(self, doc, localcontext, images=None, path='.', title=None, canvas=None):
+ if images is None:
+ images = {}
self.localcontext = localcontext
self.doc = doc
self.styles = doc.styles
- self.images = images or {}
+ self.images = images
self.path = path
self.title = title
- self._logger = logging.getLogger('report.rml.flowable')
+ self.canvas = canvas
def _textual(self, node):
rc1 = utils._process_text(self, node.text or '')
for key in txt_n.attrib.keys():
if key in ('rml_except', 'rml_loop', 'rml_tag'):
del txt_n.attrib[key]
- if True or not self._textual(n).isspace():
- if not n.tag == 'bullet':
+ if not n.tag == 'bullet':
+ if n.tag == 'pageNumber':
+ txt_n.text = self.canvas and str(self.canvas.getPageNumber()) or ''
+ else:
txt_n.text = utils.xml2str(self._textual(n))
- txt_n.tail = n.tail and utils._process_text(self, n.tail.replace('\n','')) or ''
- rc1 += etree.tostring(txt_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):
from reportlab.graphics.barcode import createBarcodeDrawing
except ImportError:
- self._logger.warning("Cannot use barcode renderers:", exc_info=True)
+ _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 = {
if not node.get('file'):
if node.get('name'):
if node.get('name') in self.doc.images:
- self._logger.debug("Image %s read ", node.get('name'))
+ _logger.debug("Image %s read ", node.get('name'))
image_data = self.doc.images[node.get('name')].read()
else:
- self._logger.warning("Image %s not defined", node.get('name'))
+ _logger.warning("Image %s not defined", node.get('name'))
return False
else:
import base64
newtext = utils._process_text(self, node.text or '')
image_data = base64.decodestring(newtext)
if not image_data:
- self._logger.debug("No inline image data")
+ _logger.debug("No inline image data")
return False
image = StringIO(image_data)
else:
- self._logger.debug("Image get from file %s", node.get('file'))
+ _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':
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 = self.page + 1
+ self.page += 1
self.pageTemplate.beforeDrawPage(self.canv,self)
self.pageTemplate.checkPageSize(self.canv,self)
self.pageTemplate.onPage(self.canv,self)
self.frame = f
break
self.handle_frameBegin()
- def afterFlowable(self, flowable):
- if isinstance(flowable, PageReset):
- self.canv._pageCount=self.page
- self.page=0
- self.canv._flag=True
+
+ 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={}, path='.', title=None):
+ 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.path = path
self.title = title
- if not node.get('pageSize'):
- pageSize = (utils.unit_get('21cm'), utils.unit_get('29.7cm'))
- else:
+ pagesize_map = {'a4': A4,
+ 'us_letter': letter
+ }
+ pageSize = A4
+ if self.localcontext.get('company'):
+ pageSize = pagesize_map.get(self.localcontext.get('company').paper_format, A4)
+ if node.get('pageSize'):
ps = map(lambda x:x.strip(), node.get('pageSize').replace(')', '').replace('(', '').split(','))
pageSize = ( utils.unit_get(ps[0]),utils.unit_get(ps[1]) )
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)
+ r = _rml_flowable(self.doc,self.localcontext, images=self.images, path=self.path, title=self.title, canvas=None)
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:
- fis.append(PageCount())
- self.doc_tmpl.build(fis)
+ try:
+ 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)
+ except platypus.doctemplate.LayoutError, e:
+ e.name = 'Print Error'
+ e.value = 'The document you are trying to print contains a table row that does not fit on one page. Please try to split it in smaller rows or contact your administrator.'
+ raise
-def parseNode(rml, localcontext=None,fout=None, images=None, path='.',title=None):
+def parseNode(rml, localcontext=None, fout=None, images=None, path='.', title=None):
node = etree.XML(rml)
- if localcontext is None:
- localcontext = {}
- if images is None:
- images = {}
r = _rml_doc(node, localcontext, images, path, title=title)
#try to override some font mappings
try:
# means there is no custom fonts mapping in this system.
pass
except Exception:
- logging.getLogger('report').warning('Cannot set font mapping', exc_info=True)
+ _logger.warning('Cannot set font mapping', exc_info=True)
pass
fp = StringIO()
r.render(fp)
return fp.getvalue()
-def parseString(rml, localcontext = {},fout=None, images={}, path='.',title=None):
+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)
print 'Usage: trml2pdf input.rml >output.pdf'
print 'Try \'trml2pdf --help\' for more information.'
+
+# vim:expandtab:smartindent:tabstop=4:softtabstop=4:shiftwidth=4: