diff options
| author | nvi-openerp <nicolas.vanhoren@openerp.com> | 2010-12-08 11:52:49 +0100 |
|---|---|---|
| committer | nvi-openerp <nicolas.vanhoren@openerp.com> | 2010-12-08 11:52:49 +0100 |
| commit | 38569b7754bb2e1dc1b6e8d58df626ff0fafaedb (patch) | |
| tree | e0ad1d56381f8f0e4d288c0f50407191ab8ac7a7 /bin/report/render | |
| parent | 8cc1c1086d2243678e2fef9c1631758ac517d097 (diff) | |
| parent | 00f2f6b97d71f43fe38c0ade7875864dd7ef564b (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.py | 18 | ||||
| -rw-r--r-- | bin/report/render/rml2pdf/customfonts.py | 98 | ||||
| -rw-r--r-- | bin/report/render/rml2pdf/trml2pdf.py | 150 |
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')) |
