Source

sphinx-rst2pdf-builder / sphinx / builders / pdf.py

Full commit
# -*- coding: utf-8 -*-
"""
    sphinx.builders.pdf
    ~~~~~~~~~~~~~~~~~~~

    A rst2pdf builder

    :copyright: Copyright 2007-2009 by the Sphinx team, see AUTHORS.
    :license: BSD, see LICENSE for details.
"""


from os import path

from docutils import writers
from docutils import nodes
from docutils.io import FileOutput
import docutils.core

from sphinx import addnodes
from sphinx.builders import Builder
from sphinx.writers.pdf import PDFWriter
from sphinx.util.console import darkgreen
import rst2pdf.log
import logging
from pprint import pprint


class collected_footnote(nodes.footnote):
    """Footnotes that are collected are assigned this class."""
    
class PDFTranslator(nodes.SparseNodeVisitor):
    def __init__(self, document, builder):
        nodes.NodeVisitor.__init__(self, document)
        self.builder = builder
        self.footnotestack = []
	self.top_sectionlevel = 1

    def visit_document(self, node):
        self.footnotestack.append(self.collect_footnotes(node))
	print 'VD:',self.footnotestack
        #self.curfilestack.append(node.get('docname', ''))
        #if self.first_document == 1:
            ## the first document is all the regular content ...
            #self.body.append(BEGIN_DOC % self.elements)
            #self.first_document = 0
        #elif self.first_document == 0:
            ## ... and all others are the appendices
            #self.body.append('\n\\appendix\n')
            #self.first_document = -1
        #if 'docname' in node:
            #self.body.append('\\hypertarget{--doc-%s}{}' % node['docname'])
        # "- 1" because the level is increased before the title is visited
        self.sectionlevel = self.top_sectionlevel - 1
    def visit_start_of_file(self, node):
        # This marks the begin of a new file; therefore the current module and
        # class must be reset
        #self.body.append('\n\\resetcurrentobjects\n')
        # and also, new footnotes
        self.footnotestack.append(self.collect_footnotes(node))
        # also add a document target
        #self.body.append('\\hypertarget{--doc-%s}{}' % node['docname'])
        #self.curfilestack.append(node['docname'])

    def visit_footnote(self, node):
        raise nodes.SkipNode

    def visit_footnote_reference(self, node):
        num = node.astext().strip()
        try:
            footnode, used = self.footnotestack[-1][num]
        except (KeyError, IndexError):
            raise nodes.SkipNode
        # if a footnote has been inserted once, it shouldn't be repeated
        # by the next reference
        if used:
	    pass
            #self.body.append('\\footnotemark[%s]' % num)
        else:
            footnode.walkabout(self)
            self.footnotestack[-1][num][1] = True
        raise nodes.SkipChildren
    
    def depart_footnote_reference(self, node):
        pass

    def depart_start_of_file(self, node):
        self.footnotestack.pop()

    def visit_collected_footnote(self, node):
        print 'CF:',node
    def depart_collected_footnote(self, node):
	pass
	    
    def collect_footnotes(self, node):
        fnotes = {}
        def footnotes_under(n):
            if isinstance(n, nodes.footnote):
                yield n
            else:
                for c in n.children:
                    if isinstance(c, addnodes.start_of_file):
                        continue
                    for k in footnotes_under(c):
                        yield k
        for fn in footnotes_under(node):
            num = fn.children[0].astext().strip()
            fnotes[num] = [collected_footnote(*fn.children), False]
        return fnotes


class PDFBuilder(Builder):
    name = 'pdf'
    out_suffix = '.pdf'

    def init(self):
        self.docnames = []
        self.document_data = self.config.master_doc

    def write(self, *ignored):
        if self.config.pdf_verbosity > 1:
            rst2pdf.log.log.setLevel(logging.DEBUG)
        elif self.config.pdf_verbosity > 0:
            rst2pdf.log.log.setLevel(logging.INFO)

        docwriter = PDFWriter(self,
                            stylesheets=self.config.pdf_stylesheets,
                            language=self.config.pdf_language,
                            breaklevel=self.config.pdf_break_level,
                            fontpath=self.config.pdf_font_path,
                            fitmode=self.config.pdf_fit_mode,
                            compressed=self.config.pdf_compressed,
                            inline_footnotes=self.config.pdf_inline_footnotes,
                            )
        tgt_file = path.join(self.outdir, self.config.master_doc + self.out_suffix)
        destination = FileOutput(destination_path=tgt_file, encoding='utf-8')
        doctree = self.assemble_doctree(self.document_data)
        docwriter.write(doctree, destination)

    def assemble_doctree(self, indexfile):
        self.docnames = set([indexfile])
        self.info(darkgreen(indexfile) + " ", nonl=1)
        def process_tree(docname, tree):
            tree = tree.deepcopy()
            for toctreenode in tree.traverse(addnodes.toctree):
                newnodes = []
                includefiles = map(str, toctreenode['includefiles'])
                for includefile in includefiles:
                    try:
                        self.info(darkgreen(includefile) + " ", nonl=1)
                        subtree = process_tree(includefile,
                        self.env.get_doctree(includefile))
                        self.docnames.add(includefile)
                    except Exception:
                        self.warn('%s: toctree contains ref to nonexisting file %r'\
                                                     % (docname, includefile))
                    else:
                        sof = addnodes.start_of_file(docname=includefile)
                        sof.children = subtree.children
                        newnodes.append(sof)
                toctreenode.parent.replace(toctreenode, newnodes)
            return tree

        tree = self.env.get_doctree(indexfile)
        tree = process_tree(indexfile, tree)

        # Add index at the end of the document

        genindex = self.env.create_index(self)
        pb,index_nodes=genindex_nodes(genindex)
        tree.append(pb)
        tree.append(index_nodes)


        for pendingnode in tree.traverse(addnodes.pending_xref):
            # This needs work, need to keep track of all targets
            # so I don't replace and create hanging refs, which
            # crash
            if pendingnode['reftarget'] in ['genindex']:
                pendingnode.replace_self(nodes.reference(text=pendingnode.astext(),
                    refuri=pendingnode['reftarget']))
            else:
                pass
        return tree
    

    def get_target_uri(self, docname, typ=None):
        return ''

    def get_outdated_docs(self):
        for docname in self.env.found_docs:
            if docname not in self.env.all_docs:
                yield docname
                continue
            targetname = self.env.doc2path(docname, self.outdir, self.out_suffix)
            try:
                targetmtime = path.getmtime(targetname)
            except Exception:
                targetmtime = 0
            try:
                srcmtime = path.getmtime(self.env.doc2path(docname))
                if srcmtime > targetmtime:
                    yield docname
            except EnvironmentError:
                # source doesn't exist anymore
                pass

def genindex_nodes(genindexentries):
    output=['DUMMY','=====','','.. raw:: pdf\n\n    PageBreak twoColumn\n\n.. _genindex:\n\n','INDEX','=====','']

    for key, entries in genindexentries:
        output.append('.. cssclass:: heading4\n\n%s\n\n'%key) # initial
        for entryname, (links, subitems) in entries:
            if links:
                output.append('`%s <%s>`_'%(entryname,links[0]))
                for i,link in enumerate(links[1:]):
                    output[-1]+=(' `[%s] <%s>`_ '%(i+1,link))
                output.append('')
            else:
                output.append(entryname)
            if subitems:
                for subentryname, subentrylinks in subitems:
                    output.append('    `%s <%s>`_'%(subentryname,subentrylinks[0]))
                    for i,link in enumerate(subentrylinks[1:]):
                        output[-1]+=(' `[%s] <%s>`_ '%(i+1,link))
                    output.append('')

    doctree = docutils.core.publish_doctree('\n'.join(output))
    return doctree[0][1],doctree[1]