Blame lib/dbtexmf/core/imagedata.py

Packit 0f19cf
import sys
Packit 0f19cf
import os
Packit 0f19cf
import re
Packit 0f19cf
import shutil
Packit 0f19cf
import logging
Packit 0f19cf
from dbtexmf.core.error import signal_error
Packit Service f3de8e
from dbtexmf.core.commander import CommandRunner
Packit Service f3de8e
Packit Service f3de8e
try:
Packit Service f3de8e
    from urllib import url2pathname
Packit Service f3de8e
except ImportError:
Packit Service f3de8e
    from urllib.request import url2pathname
Packit 0f19cf
Packit 0f19cf
class ObjectFilter:
Packit 0f19cf
    """
Packit 0f19cf
    Its purpose is to select some objects from a list according to specified
Packit 0f19cf
    criterions. It assumes that '*' applied to a criterion means 'any'.
Packit 0f19cf
    """
Packit 0f19cf
    def __init__(self):
Packit 0f19cf
        pass
Packit 0f19cf
Packit 0f19cf
    def _re_multi_or_star(self, searched):
Packit 0f19cf
        if not(searched):
Packit 0f19cf
            searched = r"\w*"
Packit 0f19cf
        else:
Packit 0f19cf
            s = searched.split()
Packit 0f19cf
            #searched = "|".join(["(?<=[/ ])%s" % p for p in s])
Packit 0f19cf
            searched = "|".join(["%s" % p for p in s])
Packit 0f19cf
        searched += r"|\*"
Packit 0f19cf
        return "("+searched+")"
Packit 0f19cf
Packit 0f19cf
    def select(self, object_list, **filter_criterions):
Packit 0f19cf
        for criterion, value in filter_criterions.items():
Packit 0f19cf
            filter_criterions[criterion] = self._re_multi_or_star(value)
Packit 0f19cf
Packit 0f19cf
        founds = []
Packit 0f19cf
        for obj in object_list:
Packit 0f19cf
            object_criterions = obj.criterions()
Packit 0f19cf
            for criterion, re_expr in filter_criterions.items():
Packit 0f19cf
                data = object_criterions.get(criterion, "")
Packit 0f19cf
                m = re.search(re_expr, data)
Packit 0f19cf
                #print "Lookup2:", criterion, re_expr, data, not(m is None)
Packit 0f19cf
                if not(m): break
Packit 0f19cf
Packit 0f19cf
            if m: founds.append(obj)
Packit 0f19cf
            #print "Lookup2: found %d" % len(founds)
Packit 0f19cf
        return founds
Packit 0f19cf
Packit 0f19cf
Packit 0f19cf
class PoolManager:
Packit 0f19cf
    def __init__(self): 
Packit 0f19cf
        self._used_pool = None
Packit 0f19cf
        self._pending_pools = []
Packit 0f19cf
    
Packit 0f19cf
    def set_pool(self, pool):
Packit 0f19cf
        self._used_pool = pool
Packit 0f19cf
        for p in self._pending_pools:
Packit 0f19cf
            pool.preprend(p)
Packit 0f19cf
        self._pending_pools = []
Packit 0f19cf
    
Packit 0f19cf
    def prepend_pool(self, pool):
Packit 0f19cf
        if self._used_pool:
Packit 0f19cf
            self._used_pool.prepend(pool)
Packit 0f19cf
        else:
Packit 0f19cf
            self._pending_pools.append(pool)
Packit 0f19cf
Packit 0f19cf
class ImageSetup:
Packit 0f19cf
    """
Packit 0f19cf
    Central imagedata setup, filled by default object configurations and
Packit 0f19cf
    by the XML configuration
Packit 0f19cf
    """
Packit 0f19cf
    def __init__(self):
Packit 0f19cf
        self.converter_pool = PoolManager()
Packit 0f19cf
        self.format_pool = PoolManager()
Packit 0f19cf
Packit 0f19cf
_image_setup = ImageSetup()
Packit 0f19cf
    
Packit 0f19cf
def image_setup():
Packit 0f19cf
    global _image_setup
Packit 0f19cf
    return _image_setup
Packit 0f19cf
Packit 0f19cf
Packit 0f19cf
#
Packit 0f19cf
# Objects to convert an image format to another. Actually use the underlying
Packit 0f19cf
# tools.
Packit 0f19cf
#
Packit 0f19cf
class ImageConverter:
Packit 0f19cf
    _log = logging.getLogger("dblatex")
Packit 0f19cf
Packit 0f19cf
    def __init__(self, imgsrc, imgdst="", docformat="", backend=""):
Packit 0f19cf
        self.imgsrc = imgsrc
Packit 0f19cf
        self.imgdst = imgdst or "*"
Packit 0f19cf
        self.docformat = docformat or "*"
Packit 0f19cf
        self.backend = backend or "*"
Packit 0f19cf
        self.command = CommandRunner(log=self._log)
Packit 0f19cf
Packit 0f19cf
    def criterions(self):
Packit 0f19cf
        return { "imgsrc": self.imgsrc,
Packit 0f19cf
                 "imgdst": self.imgdst,
Packit 0f19cf
                 "docformat": self.docformat,
Packit 0f19cf
                 "backend": self.backend }
Packit 0f19cf
Packit 0f19cf
    def add_command(self, *args, **kwargs):
Packit 0f19cf
        self.command.add_command(*args, **kwargs)
Packit 0f19cf
Packit 0f19cf
    def convert(self, input, output, format, doexec=1):
Packit 0f19cf
        rc = self.command.run(kw={"input": input, "output": output,
Packit 0f19cf
                                  "dst": format})
Packit 0f19cf
        if rc != 0: signal_error(self, "")
Packit 0f19cf
Packit 0f19cf
class ImageConverterPool:
Packit 0f19cf
    def __init__(self):
Packit 0f19cf
        self.converters = []
Packit 0f19cf
        self._filter = ObjectFilter()
Packit 0f19cf
Packit 0f19cf
    def add_converter(self, converter):
Packit 0f19cf
        self.converters.append(converter)
Packit 0f19cf
Packit 0f19cf
    def extend(self, other):
Packit 0f19cf
        self.converters.extend(other.converters)
Packit 0f19cf
Packit 0f19cf
    def prepend(self, other):
Packit 0f19cf
        self.converters = other.converters + self.converters
Packit 0f19cf
Packit 0f19cf
    def get_converters(self, imgsrc="", imgdst="", docformat="", backend=""):
Packit 0f19cf
        founds = self._filter.select(self.converters,
Packit 0f19cf
                                     imgsrc=imgsrc,
Packit 0f19cf
                                     imgdst=imgdst,
Packit 0f19cf
                                     docformat=docformat,
Packit 0f19cf
                                     backend=backend)
Packit 0f19cf
        return founds
Packit 0f19cf
Packit 0f19cf
Packit 0f19cf
class ImageConverters(ImageConverterPool):
Packit 0f19cf
    def __init__(self):
Packit 0f19cf
        ImageConverterPool.__init__(self)
Packit 0f19cf
        # Default setup
Packit 0f19cf
        self.add_converter(GifConverter("gif"))
Packit 0f19cf
        self.add_converter(EpsConverter("eps", "pdf"))
Packit 0f19cf
        self.add_converter(EpsConverter("eps", "png"))
Packit 0f19cf
        self.add_converter(FigConverter("fig", "pdf"))
Packit 0f19cf
        self.add_converter(FigConverter("fig", "png"))
Packit 0f19cf
        self.add_converter(SvgConverter("svg"))
Packit 0f19cf
Packit 0f19cf
        # Register as main pool
Packit 0f19cf
        image_setup().converter_pool.set_pool(self)
Packit 0f19cf
Packit 0f19cf
Packit 0f19cf
class GifConverter(ImageConverter):
Packit 0f19cf
    def __init__(self, imgsrc, imgdst="", docformat="", backend=""):
Packit 0f19cf
        ImageConverter.__init__(self, imgsrc="gif bmp", imgdst="*")
Packit 0f19cf
        self.add_command(["convert", "%(input)s", "%(output)s"])
Packit 0f19cf
Packit 0f19cf
class EpsConverter(ImageConverter):
Packit 0f19cf
    def __init__(self, imgsrc, imgdst="", docformat="", backend=""):
Packit 0f19cf
        ImageConverter.__init__(self, imgsrc="eps", imgdst=imgdst)
Packit 0f19cf
        if imgdst == "pdf":
Packit 0f19cf
            self.add_command(["epstopdf", "--outfile=%(output)s", "%(input)s"],
Packit 0f19cf
                             shell=True)
Packit 0f19cf
        elif imgdst == "png":
Packit 0f19cf
            self.add_command(["convert", "%(input)s", "%(output)s"])
Packit 0f19cf
Packit 0f19cf
class FigConverter(ImageConverter):
Packit 0f19cf
    def __init__(self, imgsrc, imgdst="", docformat="", backend=""):
Packit 0f19cf
        ImageConverter.__init__(self, imgsrc="fig", imgdst=imgdst)
Packit 0f19cf
        self.add_command(["fig2dev", "-L", "eps", "%(input)s"],
Packit 0f19cf
                         stdout="%(output)s")
Packit 0f19cf
        if imgdst != "eps":
Packit 0f19cf
            self.conv_next = EpsConverter("eps", imgdst=imgdst)
Packit 0f19cf
        else:
Packit 0f19cf
            self.conv_next = None
Packit 0f19cf
Packit 0f19cf
    def convert(self, input, output, format):
Packit 0f19cf
        if self.conv_next:
Packit 0f19cf
            epsfile = "tmp_fig.eps"
Packit 0f19cf
        else:
Packit 0f19cf
            epsfile = output
Packit 0f19cf
        ImageConverter.convert(self, input, epsfile, "eps")
Packit 0f19cf
        if self.conv_next:
Packit 0f19cf
            self.conv_next.convert(epsfile, output, format)
Packit 0f19cf
Packit 0f19cf
class SvgConverter(ImageConverter):
Packit 0f19cf
    def __init__(self, imgsrc, imgdst="", docformat="", backend=""):
Packit 0f19cf
        ImageConverter.__init__(self, imgsrc="svg", imgdst=imgdst)
Packit 0f19cf
        self.add_command(["inkscape", "-z", "-D", "--export-%(dst)s=%(output)s",
Packit 0f19cf
                          "%(input)s"])
Packit 0f19cf
Packit 0f19cf
Packit 0f19cf
class FormatRule:
Packit 0f19cf
    def __init__(self, imgsrc="", imgdst="", docformat="", backend=""):
Packit 0f19cf
        self.imgsrc = imgsrc or "*"
Packit 0f19cf
        self.imgdst = imgdst or "*"
Packit 0f19cf
        self.docformat = docformat or "*"
Packit 0f19cf
        self.backend = backend or "*"
Packit 0f19cf
Packit 0f19cf
    def criterions(self):
Packit 0f19cf
        return { "imgsrc": self.imgsrc,
Packit 0f19cf
                 "imgdst": self.imgdst,
Packit 0f19cf
                 "docformat": self.docformat,
Packit 0f19cf
                 "backend": self.backend }
Packit 0f19cf
Packit 0f19cf
class ImageFormatPool:
Packit 0f19cf
    def __init__(self):
Packit 0f19cf
        self.rules = []
Packit 0f19cf
        self._filter = ObjectFilter()
Packit 0f19cf
Packit 0f19cf
    def add_rule(self, rule):
Packit 0f19cf
        self.rules.append(rule)
Packit 0f19cf
Packit 0f19cf
    def prepend(self, other):
Packit 0f19cf
        self.rules = other.rules + self.rules
Packit 0f19cf
Packit 0f19cf
    def output_format(self, imgsrc="", docformat="", backend=""):
Packit 0f19cf
        founds = self._filter.select(self.rules,
Packit 0f19cf
                                     imgsrc=imgsrc,
Packit 0f19cf
                                     docformat=docformat,
Packit 0f19cf
                                     backend=backend)
Packit 0f19cf
        if founds:
Packit 0f19cf
            return founds[0].imgdst
Packit 0f19cf
        else:
Packit 0f19cf
            return ""
Packit 0f19cf
Packit 0f19cf
class ImageFormatRuleset(ImageFormatPool):
Packit 0f19cf
    def __init__(self):
Packit 0f19cf
        ImageFormatPool.__init__(self)
Packit 0f19cf
        # There can be a mismatch between PDF-1.4 images and PDF-1.3
Packit 0f19cf
        # document produced by XeTeX
Packit 0f19cf
        self.add_rule(FormatRule(docformat="pdf", backend="xetex", 
Packit 0f19cf
                                 imgdst="png"))
Packit 0f19cf
        self.add_rule(FormatRule(docformat="pdf", imgdst="pdf"))
Packit 0f19cf
        self.add_rule(FormatRule(docformat="dvi", imgdst="eps"))
Packit 0f19cf
        self.add_rule(FormatRule(docformat="ps", imgdst="eps"))
Packit 0f19cf
Packit 0f19cf
        # Register as main pool
Packit 0f19cf
        image_setup().format_pool.set_pool(self)
Packit 0f19cf
Packit 0f19cf
#
Packit 0f19cf
# The Imagedata class handles all the image transformation
Packit 0f19cf
# process, from the discovery of the actual image involved to
Packit 0f19cf
# the conversion process.
Packit 0f19cf
#
Packit 0f19cf
class Imagedata:
Packit 0f19cf
    def __init__(self):
Packit 0f19cf
        self.paths = []
Packit 0f19cf
        self.input_format = "png"
Packit 0f19cf
        self.output_format = "pdf"
Packit 0f19cf
        self.docformat = "pdf"
Packit 0f19cf
        self.backend = ""
Packit 0f19cf
        self.rules = ImageFormatRuleset()
Packit 0f19cf
        self.converters = ImageConverters()
Packit 0f19cf
        self.converted = {}
Packit 0f19cf
        self.log = logging.getLogger("dblatex")
Packit 0f19cf
        self.output_encoding = ""
Packit 0f19cf
Packit 0f19cf
    def set_encoding(self, output_encoding):
Packit 0f19cf
        self.output_encoding = output_encoding
Packit 0f19cf
Packit 0f19cf
    def set_format(self, docformat, backend):
Packit 0f19cf
        self.docformat = docformat
Packit 0f19cf
        self.backend = backend
Packit 0f19cf
        self.output_format = self.rules.output_format(docformat=docformat,
Packit 0f19cf
                                                      backend=backend)
Packit 0f19cf
Packit 0f19cf
    def convert(self, fig):
Packit Service f3de8e
        fig = fig.decode("utf-8")
Packit Service f3de8e
Packit 0f19cf
        # Translate the URL to an actual local path
Packit Service f3de8e
        fig = url2pathname(fig)
Packit 0f19cf
Packit 0f19cf
        # Always use '/' in path: work even on windows and is required by tex
Packit 0f19cf
        if os.path.sep != '/': fig = fig.replace(os.path.sep, '/')
Packit 0f19cf
Packit 0f19cf
        # First, scan the available formats
Packit 0f19cf
        (realfig, ext) = self.scanformat(fig)
Packit 0f19cf
Packit 0f19cf
        # No real file found, give up
Packit 0f19cf
        if not(realfig):
Packit 0f19cf
            self.log.warning("Image '%s' not found" % fig)
Packit 0f19cf
            return fig
Packit 0f19cf
Packit 0f19cf
        # Check if this image has been already converted
Packit Service f3de8e
        if realfig in self.converted:
Packit 0f19cf
            self.log.info("Image '%s' already converted as %s" % \
Packit 0f19cf
                  (fig, self.converted[realfig]))
Packit 0f19cf
            return self.converted[realfig]
Packit 0f19cf
Packit 0f19cf
        # No format found, take the default one
Packit 0f19cf
        if not(ext):
Packit 0f19cf
            ext = self.input_format
Packit 0f19cf
Packit 0f19cf
        # Natively supported format?
Packit 0f19cf
        if (ext == self.output_format):
Packit 0f19cf
            return self._safe_file(fig, realfig, ext)
Packit 0f19cf
Packit 0f19cf
        # Try to convert
Packit 0f19cf
        count = len(self.converted)
Packit 0f19cf
        newfig = "fig%d.%s" % (count, self.output_format)
Packit 0f19cf
Packit 0f19cf
        conv = self.converters.get_converters(imgsrc=ext,
Packit 0f19cf
                                              imgdst=self.output_format,
Packit 0f19cf
                                              backend=self.backend)
Packit 0f19cf
        if not(conv):
Packit 0f19cf
            self.log.debug("Cannot convert '%s' to %s" % (fig,
Packit 0f19cf
                             self.output_format))
Packit 0f19cf
            # Unknown conversion to do, or nothing to do
Packit 0f19cf
            return self._safe_file(fig, realfig, ext)
Packit 0f19cf
        else:
Packit 0f19cf
            # Take the first converter that does the trick
Packit 0f19cf
            conv = conv[0]
Packit 0f19cf
Packit 0f19cf
        # Convert the image and put it in the cache
Packit 0f19cf
        conv.log = self.log
Packit 0f19cf
        conv.convert(realfig, newfig, self.output_format)
Packit 0f19cf
        self.converted[realfig] = newfig
Packit Service f3de8e
        return self._path_encode(newfig)
Packit 0f19cf
Packit 0f19cf
    def _safe_file(self, fig, realfig, ext):
Packit 0f19cf
        """
Packit 0f19cf
        Copy the file in the working directory if its path contains characters
Packit 0f19cf
        unsupported by latex, like spaces.
Packit 0f19cf
        """
Packit 0f19cf
        # Encode to expected output format. If encoding is OK and 
Packit 0f19cf
        # supported by tex, just return the encoded path
Packit 0f19cf
        newfig = self._path_encode(fig)
Packit Service f3de8e
        if newfig and newfig.find(b" ") == -1:
Packit 0f19cf
            return newfig
Packit 0f19cf
Packit 0f19cf
        # Added to the converted list
Packit 0f19cf
        count = len(self.converted)
Packit 0f19cf
        newfig = "figcopy%d.%s" % (count, ext)
Packit 0f19cf
        self.converted[realfig] = newfig
Packit 0f19cf
Packit 0f19cf
        # Do the copy
Packit 0f19cf
        shutil.copyfile(realfig, newfig)
Packit Service f3de8e
        return self._path_encode(newfig)
Packit 0f19cf
Packit 0f19cf
    def _path_encode(self, fig):
Packit 0f19cf
        # Actually, only ASCII characters are sure to match filesystem encoding
Packit 0f19cf
        # so let's be conservative
Packit Service f3de8e
        if self.output_encoding == "utf-8":
Packit Service f3de8e
            return fig.encode("utf-8")
Packit 0f19cf
        try:
Packit Service f3de8e
            newfig = fig.encode("ascii")
Packit 0f19cf
        except:
Packit Service f3de8e
            newfig = b""
Packit 0f19cf
        return newfig
Packit 0f19cf
Packit 0f19cf
    def scanformat(self, fig):
Packit 0f19cf
        (root, ext) = os.path.splitext(fig)
Packit 0f19cf
Packit 0f19cf
        if (ext):
Packit 0f19cf
            realfig = self.find(fig)
Packit 0f19cf
            return (realfig, ext[1:])
Packit 0f19cf
        
Packit 0f19cf
        # Lookup for the best suited available figure
Packit 0f19cf
        if (self.output_format == "pdf"):
Packit 0f19cf
            formats = ("png", "pdf", "jpg", "eps", "gif", "fig", "svg")
Packit 0f19cf
        else:
Packit 0f19cf
            formats = ("eps", "fig", "pdf", "png", "svg")
Packit 0f19cf
Packit 0f19cf
        for format in formats:
Packit 0f19cf
            realfig = self.find("%s.%s" % (fig, format))
Packit 0f19cf
            if realfig:
Packit 0f19cf
                self.log.info("Found %s for '%s'" % (format, fig))
Packit 0f19cf
                break
Packit 0f19cf
Packit 0f19cf
        # Maybe a figure with no extension
Packit 0f19cf
        if not(realfig):
Packit 0f19cf
            realfig = self.find(fig)
Packit 0f19cf
            format = ""
Packit 0f19cf
Packit 0f19cf
        return (realfig, format)
Packit 0f19cf
        
Packit 0f19cf
    def find(self, fig):
Packit 0f19cf
        # First, the obvious absolute path case
Packit 0f19cf
        if os.path.isabs(fig):
Packit 0f19cf
            if os.path.isfile(fig):
Packit 0f19cf
                return fig
Packit 0f19cf
            else:
Packit 0f19cf
                return None
Packit 0f19cf
Packit 0f19cf
        # Then, look for the file in known paths
Packit 0f19cf
        for path in self.paths:
Packit 0f19cf
            realfig = os.path.join(path, fig)
Packit 0f19cf
            if os.path.isfile(realfig):
Packit 0f19cf
                return realfig
Packit 0f19cf
Packit 0f19cf
        return None
Packit 0f19cf