예제 #1
0
    def render(self, document, postProcess=None):
        """
        Invoke the rendering process

        This method invokes the rendering process as well as handling
        the setup and shutdown of image processing.

        Required Arguments:
        document -- the document object to render
        postProcess -- a function that will be called with the content of

        """
        config = document.config

        # If there are no keys, print a warning.
        # This is most likely a problem.
        if not list(self.keys()):
            log.warning('There are no keys in the renderer.  ' +
                        'All objects will use the default rendering method.')

        # Mix in required methods and members
        mixin(Node, type(self).renderableClass)
        Node.renderer = self

        # Create a filename generator
        self.newFilename = Filenames(config['files'].get('filename', raw=True),
                                     (config['files']['bad-chars'],
                                      config['files']['bad-chars-sub']),
                                     {'jobname':document.userdata.get('jobname', '')}, self.fileExtension)

        self.cacheFilenames(document)

        # Instantiate appropriate imager
        names = [x for x in config['images']['imager'].split() if x]
        for name in names:
            if name == 'none':
                break
            elif name == 'dvipng':
                from plasTeX.Imagers.dvipng import Imager
            elif name == 'dvi2bitmap':
                from plasTeX.Imagers.dvi2bitmap  import Imager
            elif name == 'pdftoppm':
                from plasTeX.Imagers.pdftoppm  import Imager
            elif name == 'gspdfpng':
                from plasTeX.Imagers.gspdfpng  import Imager
            elif name == 'gsdvipng':
                from plasTeX.Imagers.gsdvipng  import Imager
            elif name == 'OSXCoreGraphics':
                from plasTeX.Imagers.OSXCoreGraphics  import Imager
            else:
                log.warning("Invalid imager '%s'" % name)
                continue

            self.imager = Imager(document, self.imageTypes)

            # Make sure that this imager works on this machine
            if self.imager.verify():
                log.info('Using the imager "%s".' % name)
                break

        # Still no imager? Just use the default.
        if self.imager is None:
            if 'none' not in names:
                log.warning('Could not find a valid imager in the list: %s.  The default imager will be used.' % ', '.join(names))
            from plasTeX.Imagers import Imager
            self.imager = Imager(document, self.imageTypes)

        if self.imageTypes and self.imager.fileExtension not in self.imageTypes:
            self.imager.fileExtension = self.imageTypes[0]
        if self.imageAttrs and not self.imager.imageAttrs:
            self.imager.imageAttrs = self.imageAttrs
        if self.imageUnits and not self.imager.imageUnits:
            self.imager.imageUnits = self.imageUnits

        # Instantiate appropriate vector imager
        names = [x for x in config['images']['vector-imager'].split() if x]
        for name in names:
            if name == 'none':
                break
            elif name == 'dvisvgm':
                from plasTeX.Imagers.dvisvgm import Imager as VectorImager
            elif name == 'pdf2svg':
                from plasTeX.Imagers.pdf2svg import Imager as VectorImager
            else:
                log.warning("Invalid imager '%s'" % name)
                continue

            self.vectorImager = VectorImager(document, self.vectorImageTypes)

            # Make sure that this imager works on this machine
            if self.vectorImager.verify():
                log.info('Using the vector imager "%s".' % name)
                break

            self.vectorImager = None

        # Still no vector imager? Just use the default.
        if self.vectorImager is None:
            if 'none' not in names:
                log.warning('Could not find a valid vector imager in the list: %s.  The default vector imager will be used.' % ', '.join(names))
            from plasTeX.Imagers import VectorImager
            self.vectorImager = VectorImager(document, self.vectorImageTypes)

        if self.vectorImageTypes and \
           self.vectorImager.fileExtension not in self.vectorImageTypes:
            self.vectorImager.fileExtension = self.vectorImageTypes[0]
        if self.imageAttrs and not self.vectorImager.imageAttrs:
            self.vectorImager.imageAttrs = self.imageAttrs
        if self.imageUnits and not self.vectorImager.imageUnits:
            self.vectorImager.imageUnits = self.imageUnits

        # Invoke the rendering process
        if type(self).renderMethod:
            getattr(document, type(self).renderMethod)()
        else:
            str(document)

        # Finish rendering images
        self.imager.close()
        self.vectorImager.close()

        # Run any cleanup activities
        self.cleanup(document, list(self.files.values()), postProcess=postProcess)

        # Write out auxilliary information
        pauxname = os.path.join(document.userdata.get('working-dir','.'),
                                '%s.paux' % document.userdata.get('jobname',''))
        rname = config['general']['renderer']
        document.context.persist(pauxname, rname)

        # Remove mixins
        del Node.renderer
        unmix(Node, type(self).renderableClass)
예제 #2
0
class Renderer(dict):
    """
    Base class for all renderers

    All renderers must act like a dictionary.  Each macro that is encountered
    in a document must have a corresponding key in the renderer.  This
    key points to a callable object which is called with the object to
    be rendered.

    In addition to callable renderers, the renderer also handles image
    generation.  Images are generated when the output document type can
    not support the rendering of a macro.  One example of this is equations
    in HTML.

    """

    renderableClass = Renderable
    renderMethod = None
    textDefault = str
    default = str
    outputType = str
    imageTypes = []
    vectorImageTypes = []
    fileExtension = ''
    imageAttrs = '&${filename}-${attr};'
    imageUnits = '&${units};'
    encodingErrors = 'replace'

    def __init__(self, data=None):
        dict.__init__(self, data or {})

        # Names of generated files
        self.files = {}

        # Instantiated at render time
        self.imager = None
        self.vectorImager = None

        # Filename generator
        self.newFilename = None

    def cacheFilenames(self, node):
        """
        Generate filenames in order

        Since filenames are generated on demand, in order to make the
        nodes have a filename that corresponds to its position in the document,
        the filenames must be generated before rendering the document.

        Required Arguments:
        node -- the top-level node in the document

        """
        # Using the side-effect of the filename property
        _ = node.filename
        for child in node.childNodes:
            self.cacheFilenames(child)

    def render(self, document, postProcess=None):
        """
        Invoke the rendering process

        This method invokes the rendering process as well as handling
        the setup and shutdown of image processing.

        Required Arguments:
        document -- the document object to render
        postProcess -- a function that will be called with the content of

        """
        config = document.config

        # If there are no keys, print a warning.
        # This is most likely a problem.
        if not list(self.keys()):
            log.warning('There are no keys in the renderer.  ' +
                        'All objects will use the default rendering method.')

        # Mix in required methods and members
        mixin(Node, type(self).renderableClass)
        Node.renderer = self

        # Create a filename generator
        self.newFilename = Filenames(config['files'].get('filename', raw=True),
                                     (config['files']['bad-chars'],
                                      config['files']['bad-chars-sub']),
                                     {'jobname':document.userdata.get('jobname', '')}, self.fileExtension)

        self.cacheFilenames(document)

        # Instantiate appropriate imager
        names = [x for x in config['images']['imager'].split() if x]
        for name in names:
            if name == 'none':
                break
            elif name == 'dvipng':
                from plasTeX.Imagers.dvipng import Imager
            elif name == 'dvi2bitmap':
                from plasTeX.Imagers.dvi2bitmap  import Imager
            elif name == 'pdftoppm':
                from plasTeX.Imagers.pdftoppm  import Imager
            elif name == 'gspdfpng':
                from plasTeX.Imagers.gspdfpng  import Imager
            elif name == 'gsdvipng':
                from plasTeX.Imagers.gsdvipng  import Imager
            elif name == 'OSXCoreGraphics':
                from plasTeX.Imagers.OSXCoreGraphics  import Imager
            else:
                log.warning("Invalid imager '%s'" % name)
                continue

            self.imager = Imager(document, self.imageTypes)

            # Make sure that this imager works on this machine
            if self.imager.verify():
                log.info('Using the imager "%s".' % name)
                break

        # Still no imager? Just use the default.
        if self.imager is None:
            if 'none' not in names:
                log.warning('Could not find a valid imager in the list: %s.  The default imager will be used.' % ', '.join(names))
            from plasTeX.Imagers import Imager
            self.imager = Imager(document, self.imageTypes)

        if self.imageTypes and self.imager.fileExtension not in self.imageTypes:
            self.imager.fileExtension = self.imageTypes[0]
        if self.imageAttrs and not self.imager.imageAttrs:
            self.imager.imageAttrs = self.imageAttrs
        if self.imageUnits and not self.imager.imageUnits:
            self.imager.imageUnits = self.imageUnits

        # Instantiate appropriate vector imager
        names = [x for x in config['images']['vector-imager'].split() if x]
        for name in names:
            if name == 'none':
                break
            elif name == 'dvisvgm':
                from plasTeX.Imagers.dvisvgm import Imager as VectorImager
            elif name == 'pdf2svg':
                from plasTeX.Imagers.pdf2svg import Imager as VectorImager
            else:
                log.warning("Invalid imager '%s'" % name)
                continue

            self.vectorImager = VectorImager(document, self.vectorImageTypes)

            # Make sure that this imager works on this machine
            if self.vectorImager.verify():
                log.info('Using the vector imager "%s".' % name)
                break

            self.vectorImager = None

        # Still no vector imager? Just use the default.
        if self.vectorImager is None:
            if 'none' not in names:
                log.warning('Could not find a valid vector imager in the list: %s.  The default vector imager will be used.' % ', '.join(names))
            from plasTeX.Imagers import VectorImager
            self.vectorImager = VectorImager(document, self.vectorImageTypes)

        if self.vectorImageTypes and \
           self.vectorImager.fileExtension not in self.vectorImageTypes:
            self.vectorImager.fileExtension = self.vectorImageTypes[0]
        if self.imageAttrs and not self.vectorImager.imageAttrs:
            self.vectorImager.imageAttrs = self.imageAttrs
        if self.imageUnits and not self.vectorImager.imageUnits:
            self.vectorImager.imageUnits = self.imageUnits

        # Invoke the rendering process
        if type(self).renderMethod:
            getattr(document, type(self).renderMethod)()
        else:
            str(document)

        # Finish rendering images
        self.imager.close()
        self.vectorImager.close()

        # Run any cleanup activities
        self.cleanup(document, list(self.files.values()), postProcess=postProcess)

        # Write out auxilliary information
        pauxname = os.path.join(document.userdata.get('working-dir','.'),
                                '%s.paux' % document.userdata.get('jobname',''))
        rname = config['general']['renderer']
        document.context.persist(pauxname, rname)

        # Remove mixins
        del Node.renderer
        unmix(Node, type(self).renderableClass)

    def processFileContent(self, document, s):
        return s

    def cleanup(self, document, files, postProcess=None):
        """
        Cleanup method called at the end of rendering

        This method allows you to do arbitrary post-processing after
        all files have been rendered.

        Note: While I greatly dislike post-processing, sometimes it's
              just easier...

        Required Arguments:
        document -- the document being rendered
        files -- the list of filenames that were generated

        Optional Arguments:
        postProcess -- a function that will be called on the content of
            each file.  It is called with the document object and a
            string object with the content of each file.
            It must return a string object.

        """
        if self.processFileContent is Renderer.processFileContent:
            return

        encoding = document.config['files']['output-encoding']
        errs = self.encodingErrors
        for f in files:
            try:
                with open(f, 'r', encoding=encoding, errors=errs) as fd:
                    s = fd.read()

            except IOError as msg:
                log.error(msg)
                continue

            s = self.processFileContent(document, s)
            if isinstance(postProcess, collections.abc.Callable):
                s = postProcess(document, s)

            with open(f, 'w', encoding=encoding) as fd:
                fd.write(''.join(s))

    def find(self, keys, default=None):
        """
        Locate a renderer given a list of possibilities

        Required Arguments:
        keys -- a list of strings containing the requested name of
            a renderer.  This list is traversed in order.  The first
            renderer that is found is returned.

        Keyword Arguments:
        default -- the renderer to return if none of the keys exists

        Returns:
        the requested renderer

        """
        for key in keys:
            if key in self:
                return self[key]

        # Other nodes supplied default
        log.warning('Using default renderer for %s' % ', '.join(keys))
        for key in keys:
            self[key] = default
        return default