from __future__ import with_statement import tempfile, os, re import magic, mimetypes import subprocess from . import formats from .dependencies import Dependencies class ConversionFailedException(formats.FormatException): pass class Intermed(object): def __init__(self,metadata={}): self.md = metadata self.path = None self.extension = None self.data = None self.deps = Dependencies() def isPath(self): return self.path is not None def asPath(self): if self.path is None: assert self.extension.startswith('.'),self.extension fdesc,self.path = tempfile.mkstemp(suffix=self.extension) os.write(fdesc,self.data) os.close(fdesc) return self.path def asData(self): if self.data is None: with open(self.path) as f: self.data = f.read() self.nix() return self.data def getExtension(self): return self.extension def nix(self): if self.path is not None: os.remove(self.path) self.path = None def metadata(self): return self.md def addPropvalDep(self, elm, propname): self.deps.addPropvalDep(elm, propname) def addDeps(self,deps): self.deps.update(deps) def getDeps(self): return self.deps def setData(self,data,extension): self.nix() self.extension = unicode(extension) self.data = data self.path = None def setPath(self,path): self.nix() self.path = path self.extension = u'.'+path.rsplit('.',1)[-1] self.data = None def parse_dimension(arg): if arg.startswith('!'): arg = arg[1:] force=True else: force=False if 'x' in arg: w,h = (int(b) if b != '?' else None for b in arg.split('x')) else: w = h = int(arg) return w,h,force def rsvg_convert(im,format='.png'): args = [] force = False if 'd' in im.metadata()['filters']: w,h,force = parse_dimension(im.metadata()['filters']['d']) del im.metadata()['filters']['d'] # This takes width and height in pt, where 1pt=1.25px, and as integers. # But only when converting to .svg, to be confusing. if format == '.svg': mw = int(w/1.25) mh = int(h/1.25) else: mw = w mh = h args.append('-w') args.append(str(mw)) args.append('-h') args.append(str(mh)) if not force: args.append('-a') args.append('-f') args.append(format[1:]) p=subprocess.Popen(['rsvg-convert']+args+[im.asPath()], stdout=subprocess.PIPE,stderr=subprocess.PIPE) out,err = p.communicate() if p.returncode != 0: # Error messages get to stdout, which is dumb. raise ConversionFailedException( "rsvg-convert failed with status %s: %s" % (p.returncode, out+err)) im.setData(out,format) if force: im.metadata()['width'] = w im.metadata()['height'] = h FORMAT_TO_PSTOEDIT = {'.svg' : 'plot-svg', '.eps' : 'ps', '.epdf': 'gs:pdfwrite', } def pstoedit_convert(im, format): assert format in FORMAT_TO_PSTOEDIT cmd = ['pstoedit', '-f', FORMAT_TO_PSTOEDIT[format], '-pti', im.asPath()] p=subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = p.communicate() if p.returncode != 0: raise OSError(p.returncode, err) im.setData(out, format) # TRANSLATORS maps dest to a map that maps source to function. # So, if TRANSLATORS['.pdf']['.tex'] is func, then func takes # an Intermed that refers to '.tex' formatted data and sets # that Intermed with '.pdf' formatted data or a appropriate path. # # Formats can start with a '.', in which case they represent complete # files, or without a '.', in which case they represent incomplete # fragments. # # A translator should either call asData or asPath, whichever is more # convenient, but only once. # # Anything that calls asPath but doesn't call setData or setPath # should call nix when it's done with the file at that path. TRANSLATORS = {'.txt': {'txt':lambda im: im}, '.png': {'.svg':lambda im:rsvg_convert(im,'.png')}, '.epdf': {'.svg':lambda im:rsvg_convert(im,'.epdf')}, '.eps': {'.svg':lambda im:rsvg_convert(im,'.eps')}, } # Need a helper function b/c python doesn't freeze the environment of a lambda def _trans(func, format): def translator(im): return func(im, format) return translator for to_f in FORMAT_TO_PSTOEDIT: for from_f in formats.PSTOEDIT_READABLE_FORMATS: if to_f != from_f: if from_f not in TRANSLATORS.setdefault(to_f, {}): TRANSLATORS[to_f][from_f] = _trans(pstoedit_convert, to_f) # Overrides for mimetypes.guess_extension MIME_MAP = {"image/svg+xml": ".svg", "text/plain": ".txt", "application/x-empty": ".txt", "text/x-c": ".txt", "text/html": ".html", "image/jpeg": ".jpg"} # Overrides for mimetypes.guess_type EXT_MAP = {'.svg': 'image/svg+xml', '.tex': 'text/plain'} _magic = None _mime_pattern = re.compile(r'^[^;\s]+') def extension(data): global _magic if _magic is None: mag = magic.open(magic.MAGIC_MIME) mag.load() _magic = mag mime = _mime_pattern.match(_magic.buffer(str(data))).group() return guess_extension(mime) def guess_extension(mime): if mime in MIME_MAP: return MIME_MAP[mime] else: ext = mimetypes.guess_extension(mime) #assert ext is not None,mime if ext is None: return ".dat" return ext def guess_type(filename): ext = '.' + filename.rsplit('.', 1)[-1] if ext in EXT_MAP: return EXT_MAP[ext], None return mimetypes.guess_type(filename, strict=False)