diff --git a/src/calibre/ebooks/pdf/from_any.py b/src/calibre/ebooks/pdf/from_any.py new file mode 100644 index 0000000000..e4fb937cdb --- /dev/null +++ b/src/calibre/ebooks/pdf/from_any.py @@ -0,0 +1,69 @@ +''' +Convert any ebook format to PDF. +''' + +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net ' \ + 'and Marshall T. Vandegrift ' \ + 'and John Schember ' +__docformat__ = 'restructuredtext en' + +import sys, os, glob, logging + +from calibre.ebooks.epub.from_any import any2epub, formats, USAGE +from calibre.ebooks.epub import config as common_config +from calibre.ptempfile import TemporaryDirectory +from calibre.ebooks.pdf.writer import oeb2pdf, config as pdf_config + +def config(defaults=None): + c = common_config(defaults=defaults, name='pdf') + c.remove_opt('profile') + pdfc = pdf_config(defaults=defaults) + c.update(pdfc) + return c + +def option_parser(usage=USAGE): + usage = usage % ('PDF', formats()) + parser = config().option_parser(usage=usage) + return parser + +def any2pdf(opts, path, notification=None): + ext = os.path.splitext(path)[1] + if not ext: + raise ValueError('Unknown file type: '+path) + ext = ext.lower()[1:] + + if opts.output is None: + opts.output = os.path.splitext(os.path.basename(path))[0]+'.pdf' + + opts.output = os.path.abspath(opts.output) + orig_output = opts.output + + with TemporaryDirectory('_any2pdf') as tdir: + oebdir = os.path.join(tdir, 'oeb') + os.mkdir(oebdir) + opts.output = os.path.join(tdir, 'dummy.epub') + opts.profile = 'None' + opts.dont_split_on_page_breaks = True + orig_bfs = opts.base_font_size2 + opts.base_font_size2 = 0 + any2epub(opts, path, create_epub=False, oeb_cover=True, extract_to=oebdir) + opts.base_font_size2 = orig_bfs + opf = glob.glob(os.path.join(oebdir, '*.opf'))[0] + opts.output = orig_output + logging.getLogger('html2epub').info(_('Creating PDF file from EPUB...')) + oeb2pdf(opts, opf) + +def main(args=sys.argv): + parser = option_parser() + opts, args = parser.parse_args(args) + if len(args) < 2: + parser.print_help() + print 'No input file specified.' + return 1 + any2pdf(opts, args[1]) + +if __name__ == '__main__': + sys.exit(main()) diff --git a/src/calibre/ebooks/pdf/writer.py b/src/calibre/ebooks/pdf/writer.py new file mode 100644 index 0000000000..d46d1fc0ed --- /dev/null +++ b/src/calibre/ebooks/pdf/writer.py @@ -0,0 +1,153 @@ +''' +Write content to PDF. +''' +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2009, John Schember ' + +import os, logging, shutil, sys +from calibre.ebooks.oeb.base import Logger, OEBBook +from calibre.ebooks.oeb.profile import Context +from calibre.ebooks.epub.iterator import SpineItem +from calibre.ebooks.metadata.opf2 import OPF +from calibre.ptempfile import PersistentTemporaryDirectory +from calibre.customize.ui import run_plugins_on_postprocess +from calibre.utils.config import Config, StringConfig + +from PyQt4 import QtCore +from PyQt4.Qt import QUrl, QEventLoop, SIGNAL, QObject, QApplication, QPrinter, \ + QMetaObject +from PyQt4.Qt import * +from PyQt4.QtWebKit import QWebView + +from pyPdf import PdfFileWriter, PdfFileReader + +class PDFWriter(QObject): + def __init__(self): + if QApplication.instance() is None: + QApplication([]) + QObject.__init__(self) + + self.loop = QEventLoop() + self.view = QWebView() + self.connect(self.view, SIGNAL('loadFinished(bool)'), self._render_html) + self.render_queue = [] + self.combine_queue = [] + self.tmp_path = PersistentTemporaryDirectory('_any2pdf_parts') + + def dump(self, oeb, oebpath, path): + self._reset() + + opf = OPF(oebpath, os.path.dirname(oebpath)) + self.render_queue = [SpineItem(i.path) for i in opf.spine] + + self.path = path + + QMetaObject.invokeMethod(self, "_render_book", Qt.QueuedConnection) + self.loop.exec_() + + @QtCore.pyqtSignature('_render_book()') + def _render_book(self): + if len(self.render_queue) == 0: + self._write() + else: + self._render_next() + + def _render_next(self): + item = str(self.render_queue.pop(0)) + self.combine_queue.append(os.path.join(self.tmp_path, '%s.pdf' % os.path.basename(item))) + + self.view.load(QUrl(item)) + + def _render_html(self, ok): + if ok: + printer = QPrinter(QPrinter.HighResolution) + printer.setPageMargins(1, 1, 1, 1, QPrinter.Inch) + printer.setOutputFormat(QPrinter.PdfFormat) + printer.setOutputFileName(os.path.join(self.tmp_path, '%s.pdf' % os.path.basename(str(self.view.url().toLocalFile())))) + self.view.print_(printer) + self._render_book() + + def _reset(self): + self.render_queue = [] + self.combine_queue = [] + self.path = '' + if os.path.exists(self.tmp_path): + shutil.rmtree(self.tmp_path, True) + self.tmp_path = PersistentTemporaryDirectory('_any2pdf_parts') + + def _write(self): + print self.path + try: + outPDF = PdfFileWriter() + for item in self.combine_queue: + inputPDF = PdfFileReader(file(item, 'rb')) + for page in inputPDF.pages: + outPDF.addPage(page) + outputStream = file(self.path, 'wb') + outPDF.write(outputStream) + outputStream.close() + finally: + self._reset() + self.loop.exit(0) + + +def config(defaults=None): + desc = _('Options to control the conversion to PDF') + if defaults is None: + c = Config('pdf', desc) + else: + c = StringConfig(defaults, desc) + + pdf = c.add_group('PDF', _('PDF options.')) + + return c + + +def option_parser(): + c = config() + parser = c.option_parser(usage='%prog '+_('[options]')+' file.opf') + parser.add_option( + '-o', '--output', default=None, + help=_('Output file. Default is derived from input filename.')) + parser.add_option( + '-v', '--verbose', default=0, action='count', + help=_('Useful for debugging.')) + return parser + +def oeb2pdf(opts, inpath): + logger = Logger(logging.getLogger('oeb2pdf')) + logger.setup_cli_handler(opts.verbose) + outpath = opts.output + if outpath is None: + outpath = os.path.basename(inpath) + outpath = os.path.splitext(outpath)[0] + '.pdf' +# source = opts.source_profile +# if source not in Context.PROFILES: +# logger.error(_('Unknown source profile %r') % source) +# return 1 +# dest = opts.dest_profile +# if dest not in Context.PROFILES: +# logger.error(_('Unknown destination profile %r') % dest) +# return 1 + + oeb = OEBBook(inpath, logger=logger, encoding=opts.encoding) + writer = PDFWriter() + writer.dump(oeb, inpath, outpath) + run_plugins_on_postprocess(outpath, 'pdf') + logger.info(_('Output written to ') + outpath) + +def main(argv=sys.argv): + parser = option_parser() + opts, args = parser.parse_args(argv[1:]) + if len(args) != 1: + parser.print_help() + return 1 + inpath = args[0] + retval = oeb2pdf(opts, inpath) + return retval + +if __name__ == '__main__': + sys.exit(main()) + diff --git a/src/calibre/linux.py b/src/calibre/linux.py index edcfa99342..81b8424199 100644 --- a/src/calibre/linux.py +++ b/src/calibre/linux.py @@ -51,6 +51,7 @@ entry_points = { 'any2epub = calibre.ebooks.epub.from_any:main', 'any2lit = calibre.ebooks.lit.from_any:main', 'any2mobi = calibre.ebooks.mobi.from_any:main', + 'any2pdf = calibre.ebooks.pdf.from_any:main', 'lrf2lrs = calibre.ebooks.lrf.lrfparser:main', 'lrs2lrf = calibre.ebooks.lrf.lrs.convert_from:main', 'pdfreflow = calibre.ebooks.lrf.pdf.reflow:main', diff --git a/src/calibre/parallel.py b/src/calibre/parallel.py index f9b4513c78..7bdb606642 100644 --- a/src/calibre/parallel.py +++ b/src/calibre/parallel.py @@ -71,6 +71,9 @@ PARALLEL_FUNCS = { 'any2mobi' : ('calibre.ebooks.mobi.from_any', 'any2mobi', {}, None), + 'any2pdf' : + ('calibre.ebooks.pdf.from_any', 'any2pdf', {}, None), + 'feeds2mobi' : ('calibre.ebooks.mobi.from_feeds', 'main', {}, 'notification'),