Source

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

# -*- 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
import sys

from docutils import writers
from docutils import nodes
from docutils import languages
from docutils.transforms.parts import Contents
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
from sphinx.util import SEP
from sphinx.util import ustrftime
import rst2pdf.log
import logging
from pprint import pprint


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

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

    def write(self, *ignored):
        
        self.init_document_data()
        
        if self.config.pdf_verbosity > 1:
            rst2pdf.log.log.setLevel(logging.DEBUG)
        elif self.config.pdf_verbosity > 0:
            rst2pdf.log.log.setLevel(logging.INFO)
    
        for entry in self.document_data:
            docname, targetname, title, author = entry[:4]
            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, targetname + self.out_suffix)
            destination = FileOutput(destination_path=tgt_file, encoding='utf-8')
            doctree = self.assemble_doctree(docname,title,author)
            docwriter.write(doctree, destination)
        
    def init_document_data(self):
        preliminary_document_data = map(list, self.config.pdf_documents)
        if not preliminary_document_data:
            self.warn('no "pdf_documents" config value found; no documents '
                      'will be written')
            return
        # assign subdirs to titles
        self.titles = []
        for entry in preliminary_document_data:
            docname = entry[0]
            if docname not in self.env.all_docs:
                self.warn('"pdf_documents" config value references unknown '
                          'document %s' % docname)
                continue
            self.document_data.append(entry)
            if docname.endswith(SEP+'index'):
                docname = docname[:-5]
            self.titles.append((docname, entry[2]))

    def assemble_doctree(self, docname, title, author):
        self.docnames = set([docname])
        self.info(darkgreen(docname) + " ", 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(docname)
        
        tree = process_tree(docname, tree)

        if self.config.language:
            langmod = languages.get_language(self.config.language[:2])
        else:
            langmod = languages.get_language('en')
            
        if self.config.pdf_use_index:
            # 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)

        # Page transitions
        output='.. raw:: pdf\n\n    PageBreak\n\n'
        pb=docutils.core.publish_doctree(output)[0]
        output='.. raw:: pdf\n\n    PageBreak oneColumn\n\n'
        pb_oneColumn=docutils.core.publish_doctree(output)[0]
        output='.. raw:: pdf\n\n    PageBreak cutePage\n\n'
        pb_cutePage=docutils.core.publish_doctree(output)[0]


        # Generate Contents topic manually
        contents=nodes.topic(classes=['contents'])
        contents+=nodes.title('')
        contents[0]+=nodes.Text( langmod.labels['contents'])
        contents['ids']=['Contents']
        pending=nodes.topic()
        contents.append(pending)
        pending.details={}
        tree.insert(0,pb_cutePage)
        tree.insert(0,contents)
        contTrans=PDFContents(tree)
        contTrans.startnode=pending
        contTrans.apply()

        if self.config.pdf_use_coverpage:
            # Generate cover page
            spacer=docutils.core.publish_doctree('.. raw:: pdf\n\n    Spacer 0 3cm\n\n')[0]
            doctitle=nodes.title()
            doctitle.append(nodes.Text(title))
            docsubtitle=nodes.subtitle()
            docsubtitle.append(nodes.Text('%s %s'%(_('version'),self.config.version)))
            authornode=nodes.paragraph()
            authornode.append(nodes.Text(author))
            authornode['classes']=['author']
            date=nodes.paragraph()
            date.append(nodes.Text(ustrftime(self.config.today_fmt or _('%B %d, %Y'))))
            date['classes']=['author']
            tree.insert(0,pb)
            tree.insert(0,date)
            tree.insert(0,spacer)
            tree.insert(0,authornode)
            tree.insert(0,spacer)
            tree.insert(0,docsubtitle)
            tree.insert(0,doctitle)
        
        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):
    indexlabel = _('Index')
    indexunder = '='*len(indexlabel)
    output=['DUMMY','=====','','.. raw:: pdf\n\n    PageBreak twoColumn\n\n.. _genindex:\n\n',indexlabel,indexunder,'']

    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]


class PDFContents(Contents):
    
    def build_contents(self, node, level=0):
        level += 1
        sections=[]
        for sect in node:
            if isinstance(sect,addnodes.start_of_file):
                for sect2 in sect:
                    if isinstance(sect2,nodes.section):
                        sections.append(sect2)
            elif isinstance(sect, nodes.section):
                sections.append(sect)
        #sections = [sect for sect in node if isinstance(sect, nodes.section)]
        entries = []
        autonum = 0
        depth = self.startnode.details.get('depth', sys.maxint)
        for section in sections:
            title = section[0]
            auto = title.get('auto')    # May be set by SectNum.
            entrytext = self.copy_and_filter(title)
            reference = nodes.reference('', '', refid=section['ids'][0],
                                        *entrytext)
            ref_id = self.document.set_id(reference)
            entry = nodes.paragraph('', '', reference)
            item = nodes.list_item('', entry)
            if ( self.backlinks in ('entry', 'top')
                 and title.next_node(nodes.reference) is None):
                if self.backlinks == 'entry':
                    title['refid'] = ref_id
                elif self.backlinks == 'top':
                    title['refid'] = self.toc_id
            if level < depth:
                subsects = self.build_contents(section, level)
                item += subsects
            entries.append(item)
        if entries:
            contents = nodes.bullet_list('', *entries)
            if auto:
                contents['classes'].append('auto-toc')
            return contents
        else:
            return []