summaryrefslogtreecommitdiff
path: root/bin/report/render
diff options
context:
space:
mode:
authornvi-openerp <nicolas.vanhoren@openerp.com>2010-12-08 11:52:49 +0100
committernvi-openerp <nicolas.vanhoren@openerp.com>2010-12-08 11:52:49 +0100
commit38569b7754bb2e1dc1b6e8d58df626ff0fafaedb (patch)
treee0ad1d56381f8f0e4d288c0f50407191ab8ac7a7 /bin/report/render
parent8cc1c1086d2243678e2fef9c1631758ac517d097 (diff)
parent00f2f6b97d71f43fe38c0ade7875864dd7ef564b (diff)
[merge]
bzr revid: nicolas.vanhoren@openerp.com-20101206091222-miwtrvjkfh8hy8ec bzr revid: nicolas.vanhoren@openerp.com-20101206174010-f819dzx7vyw2jntk bzr revid: nicolas.vanhoren@openerp.com-20101207132443-z0wabhbh0ruzwoo2 bzr revid: nicolas.vanhoren@openerp.com-20101208105249-qfjalxkboqpi9xr0
Diffstat (limited to 'bin/report/render')
-rw-r--r--bin/report/render/render.py18
-rw-r--r--bin/report/render/rml2pdf/customfonts.py98
-rw-r--r--bin/report/render/rml2pdf/trml2pdf.py150
3 files changed, 183 insertions, 83 deletions
diff --git a/bin/report/render/render.py b/bin/report/render/render.py
index 5252288d53d..0d00701ee54 100644
--- a/bin/report/render/render.py
+++ b/bin/report/render/render.py
@@ -36,9 +36,23 @@ import threading
# _render
#
class render(object):
- def __init__(self, bin_datas={}, path='.'):
+ """ Represents a report job being rendered.
+
+ @param bin_datas a dictionary of name:<binary content> of images etc.
+ @param path the path in which binary files can be discovered, useful
+ for components (images) of the report. It can be:
+ - a string, relative or absolute path to images
+ - a list, containing strings of paths.
+ If a string is absolute path, it will be opened as such, else
+ it will be passed to tools.file_open() which also considers zip
+ addons.
+ """
+ def __init__(self, bin_datas=None, path='.'):
self.done = False
- self.bin_datas = bin_datas
+ if bin_datas is None:
+ self.bin_datas = {}
+ else:
+ self.bin_datas = bin_datas
self.path = path
def _render(self):
diff --git a/bin/report/render/rml2pdf/customfonts.py b/bin/report/render/rml2pdf/customfonts.py
index e4f15aa8d8b..a60c2d267f5 100644
--- a/bin/report/render/rml2pdf/customfonts.py
+++ b/bin/report/render/rml2pdf/customfonts.py
@@ -3,6 +3,7 @@
#
# OpenERP, Open Source Management Solution
# Copyright (C) 2004-2009 P. Christeas, Tiny SPRL (<http://tiny.be>).
+# Copyright (C) 2010 OpenERP SA. (http://www.openerp.com)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as
@@ -19,15 +20,22 @@
#
##############################################################################
-from reportlab import rl_config
-import os
+import glob
import logging
+import os
+import platform
+from reportlab import rl_config
+
+from tools import config
"""This module allows the mapping of some system-available TTF fonts to
the reportlab engine.
This file could be customized per distro (although most Linux/Unix ones)
should have the same filenames, only need the code below).
+
+Due to an awful configuration that ships with reportlab at many Linux
+and Ubuntu distros, we have to override the search path, too.
"""
CustomTTFonts = [ ('Helvetica',"DejaVu Sans", "DejaVuSans.ttf", 'normal'),
@@ -42,16 +50,41 @@ CustomTTFonts = [ ('Helvetica',"DejaVu Sans", "DejaVuSans.ttf", 'normal'),
('Times-Roman',"Liberation Serif Bold", "LiberationSerif-Bold.ttf", 'bold'),
('Times-Roman',"Liberation Serif Italic", "LiberationSerif-Italic.ttf", 'italic'),
('Times-Roman',"Liberation Serif BoldItalic", "LiberationSerif-BoldItalic.ttf", 'bolditalic'),
- ('ZapfDingbats',"DejaVu Serif", "DejaVuSerif.ttf", 'normal'),
- ('ZapfDingbats',"DejaVu Serif Bold", "DejaVuSerif-Bold.ttf", 'bold'),
- ('ZapfDingbats',"DejaVu Serif Italic", "DejaVuSerif-Italic.ttf", 'italic'),
- ('ZapfDingbats',"DejaVu Serif BoldItalic", "DejaVuSerif-BoldItalic.ttf", 'bolditalic'),
('Courier',"FreeMono", "FreeMono.ttf", 'normal'),
('Courier',"FreeMono Bold", "FreeMonoBold.ttf", 'bold'),
('Courier',"FreeMono Oblique", "FreeMonoOblique.ttf", 'italic'),
('Courier',"FreeMono BoldOblique", "FreeMonoBoldOblique.ttf", 'bolditalic'),]
-__foundFonts = []
+
+TTFSearchPath_Linux = (
+ '/usr/share/fonts/truetype', # SuSE
+ '/usr/share/fonts/dejavu', '/usr/share/fonts/liberation', # Fedora, RHEL
+ '/usr/share/fonts/truetype/*', # Ubuntu,
+ '/usr/share/fonts/TTF/*', # at Mandriva/Mageia
+ )
+
+TTFSearchPath_Windows = (
+ 'c:/winnt/fonts',
+ 'c:/windows/fonts'
+ )
+
+TTFSearchPath_Darwin = (
+ #mac os X - from
+ #http://developer.apple.com/technotes/tn/tn2024.html
+ '~/Library/Fonts',
+ '/Library/Fonts',
+ '/Network/Library/Fonts',
+ '/System/Library/Fonts',
+ )
+
+TTFSearchPathMap = {
+ 'Darwin': TTFSearchPath_Darwin,
+ 'Windows': TTFSearchPath_Windows,
+ 'Linux': TTFSearchPath_Linux,
+}
+
+# ----- The code below is less distro-specific, please avoid editing! -------
+__foundFonts = None
def FindCustomFonts():
"""Fill the __foundFonts list with those filenames, whose fonts
@@ -64,22 +97,37 @@ def FindCustomFonts():
dirpath = []
log = logging.getLogger('report.fonts')
global __foundFonts
- for dirname in rl_config.TTFSearchPath:
- abp = os.path.abspath(dirname)
- if os.path.isdir(abp):
- dirpath.append(abp)
-
- for k, (name, font, fname, mode) in enumerate(CustomTTFonts):
- if fname in __foundFonts:
+ __foundFonts = {}
+ searchpath = []
+
+ if config.get('fonts_search_path'):
+ searchpath += map(str.strip, config.get('fonts_search_path').split(','))
+
+ local_platform = platform.system()
+ if local_platform in TTFSearchPathMap:
+ searchpath += TTFSearchPathMap[local_platform]
+
+ # Append the original search path of reportlab (at the end)
+ searchpath += rl_config.TTFSearchPath
+
+ # Perform the search for font files ourselves, as reportlab's
+ # TTFOpenFile is not very good at it.
+ for dirglob in searchpath:
+ dirglob = os.path.expanduser(dirglob)
+ for dirname in glob.iglob(dirglob):
+ abp = os.path.abspath(dirname)
+ if os.path.isdir(abp):
+ dirpath.append(abp)
+
+ for k, (name, font, filename, mode) in enumerate(CustomTTFonts):
+ if filename in __foundFonts:
continue
for d in dirpath:
- if os.path.exists(os.path.join(d, fname)):
- log.debug("Found font %s in %s as %s", fname, d, name)
- __foundFonts.append(fname)
+ abs_filename = os.path.join(d, filename)
+ if os.path.exists(abs_filename):
+ log.debug("Found font %s at %s", filename, abs_filename)
+ __foundFonts[filename] = abs_filename
break
-
- # print "Found fonts:", __foundFonts
-
def SetCustomFonts(rmldoc):
""" Map some font names to the corresponding TTF fonts
@@ -90,11 +138,13 @@ def SetCustomFonts(rmldoc):
avoid system-wide processing (cache it, instead).
"""
global __foundFonts
- if not len(__foundFonts):
+ if __foundFonts is None:
FindCustomFonts()
- for name, font, fname, mode in CustomTTFonts:
- if os.path.isabs(fname) or fname in __foundFonts:
- rmldoc.setTTFontMapping(name, font, fname, mode)
+ for name, font, filename, mode in CustomTTFonts:
+ if os.path.isabs(filename) and os.path.exists(filename):
+ rmldoc.setTTFontMapping(name, font, filename, mode)
+ elif filename in __foundFonts:
+ rmldoc.setTTFontMapping(name, font, __foundFonts[filename], mode)
return True
#eof
diff --git a/bin/report/render/rml2pdf/trml2pdf.py b/bin/report/render/rml2pdf/trml2pdf.py
index c8b64f4075b..b609528177e 100644
--- a/bin/report/render/rml2pdf/trml2pdf.py
+++ b/bin/report/render/rml2pdf/trml2pdf.py
@@ -35,6 +35,8 @@ import base64
from reportlab.platypus.doctemplate import ActionFlowable
from tools.safe_eval import safe_eval as eval
from reportlab.lib.units import inch,cm,mm
+from tools.misc import file_open
+from reportlab.pdfbase import pdfmetrics
try:
from cStringIO import StringIO
@@ -44,6 +46,26 @@ except ImportError:
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)
@@ -255,23 +277,24 @@ class _rml_doc(object):
def _images(self, el):
result = {}
- for node in el.findall('image'):
+ 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')
+ el = self.etree.findall('.//docinit')
if el:
self.docinit(el)
- el = self.etree.findall('stylesheet')
+ el = self.etree.findall('.//stylesheet')
self.styles = _rml_styles(el,self.localcontext)
- el = self.etree.findall('images')
+ el = self.etree.findall('.//images')
if el:
self.images.update( self._images(el[0]) )
- el = self.etree.findall('template')
+
+ 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')
@@ -295,6 +318,7 @@ class _rml_canvas(object):
self.images = images
self.path = path
self.title = title
+ self._logger = logging.getLogger('report.rml.canvas')
if self.title:
self.canvas.setTitle(self.title)
@@ -412,10 +436,13 @@ class _rml_canvas(object):
def _image(self, node):
import urllib
+ import urlparse
from reportlab.lib.utils import ImageReader
- if not node.get('file') :
+ nfile = node.get('file')
+ if not nfile:
if node.get('name'):
image_data = self.images[node.get('name')]
+ self._logger.debug("Image %s used", node.get('name'))
s = StringIO(image_data)
else:
if self.localcontext:
@@ -429,21 +456,30 @@ class _rml_canvas(object):
if image_data:
s = StringIO(image_data)
else:
+ self._logger.debug("No image data!")
return False
else:
- if node.get('file') in self.images:
- s = StringIO(self.images[node.get('file')])
+ if nfile in self.images:
+ s = StringIO(self.images[nfile])
else:
try:
- u = urllib.urlopen(str(node.get('file')))
- s = StringIO(u.read())
- except Exception:
- u = file(os.path.join(self.path,str(node.get('file'))), 'rb')
+ 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?
+ self._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)
+ s = _open_image(nfile, path=self.path)
img = ImageReader(s)
(sx,sy) = img.getSize()
+ self._logger.debug("Image is %dx%d", sx, sy)
- args = {}
+ args = { 'x': 0.0, 'y': 0.0 }
for tag in ('width','height','x','y'):
if node.get(tag):
args[tag] = utils.unit_get(node.get(tag))
@@ -485,11 +521,18 @@ class _rml_canvas(object):
self.canvas.drawPath(self.path, **utils.attr_get(node, [], {'fill':'bool','stroke':'bool'}))
def setFont(self, node):
- fname = node.get('name')
- try:
- return self.canvas.setFont(fname, utils.unit_get(node.get('size')))
- except KeyError, e:
- raise KeyError('Font "%s" is not registered in the engine' % fname)
+ 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
+ return self.canvas.setFont(fontname, utils.unit_get(node.get('size')))
def render(self, node):
tags = {
@@ -532,14 +575,29 @@ class _rml_draw(object):
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={}, path='.', title=None):
+ def __init__(self, doc, localcontext, images=None, path='.', title=None):
self.localcontext = localcontext
self.doc = doc
self.styles = doc.styles
- self.images = images
+ self.images = images or {}
self.path = path
self.title = title
+ self._logger = logging.getLogger('report.rml.flowable')
def _textual(self, node):
rc1 = utils._process_text(self, node.text or '')
@@ -630,21 +688,7 @@ class _rml_flowable(object):
return table
def _illustration(self, node):
- class 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):
- canvas = self.canv
- 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)
- return Illustration(node, self.localcontext, self.styles, self)
+ return _rml_Illustration(node, self.localcontext, self.styles, self)
def _textual_image(self, node):
return base64.decodestring(node.text)
@@ -690,6 +734,7 @@ class _rml_flowable(object):
from reportlab.graphics.barcode import fourstate
from reportlab.graphics.barcode import usps
except Exception, e:
+ self._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 = {
@@ -731,38 +776,29 @@ class _rml_flowable(object):
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'):
- image_data = self.doc.images[node.get('name')].read()
+ if node.get('name') in self.doc.images:
+ self._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'))
+ return False
else:
import base64
if self.localcontext:
newtext = utils._process_text(self, node.text or '')
node.text = newtext
image_data = base64.decodestring(node.text)
- if not image_data: return False
+ if not image_data:
+ self._logger.debug("No inline image data")
+ return False
image = StringIO(image_data)
- return platypus.Image(image, mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
else:
- return platypus.Image(node.get('file'), mask=(250,255,250,255,250,255), **(utils.attr_get(node, ['width','height'])))
- from reportlab.lib.utils import ImageReader
- name = str(node.get('file'))
- img = ImageReader(name)
- (sx,sy) = img.getSize()
- args = {}
- for tag in ('width','height'):
- 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
- return platypus.Image(name, mask=(250,255,250,255,250,255), **args)
+ self._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'))