diff --git a/src/calibre/ebooks/pdf/output.py b/src/calibre/ebooks/pdf/output.py index fef20ae55d..2a19b09dd9 100644 --- a/src/calibre/ebooks/pdf/output.py +++ b/src/calibre/ebooks/pdf/output.py @@ -15,42 +15,14 @@ from calibre.customize.conversion import OutputFormatPlugin, \ OptionRecommendation from calibre.ebooks.metadata.opf2 import OPF from calibre.ptempfile import TemporaryDirectory -from calibre.ebooks.pdf.writer import PDFWriter, ImagePDFWriter, PDFMetadata, \ - get_pdf_page_size +from calibre.ebooks.pdf.writer import PDFWriter, ImagePDFWriter, PDFMetadata from calibre.ebooks.pdf.pageoptions import UNITS, PAPER_SIZES, \ ORIENTATIONS -from calibre.ebooks.epub.output import CoverManager -class CoverManagerPDF(CoverManager): - - def setup_cover(self, opts): - width, height = get_pdf_page_size(opts) - factor = opts.output_profile.dpi - self.NONSVG_TITLEPAGE_COVER = '''\ - - - - - Cover - - - -
- cover -
- - - '''%(int(width*factor), int(height*factor)-5) - - -class PDFOutput(OutputFormatPlugin, CoverManagerPDF): +class PDFOutput(OutputFormatPlugin): name = 'PDF Output' - author = 'John Schember' + author = 'John Schember and Kovid Goyal' file_type = 'pdf' options = set([ @@ -72,6 +44,12 @@ class PDFOutput(OutputFormatPlugin, CoverManagerPDF): level=OptionRecommendation.LOW, choices=ORIENTATIONS.keys(), help=_('The orientation of the page. Default is portrait. Choices ' 'are %s') % ORIENTATIONS.keys()), + OptionRecommendation(name='preserve_cover_aspect_ratio', + recommended_value=False, + help=_('Preserve the aspect ratio of the cover, instead' + ' of stretching it to fill the ull first page of the' + ' generated pdf.') + ), ]) def convert(self, oeb_book, output_path, input_plugin, opts, log): @@ -79,6 +57,7 @@ class PDFOutput(OutputFormatPlugin, CoverManagerPDF): self.input_plugin, self.opts, self.log = input_plugin, opts, log self.output_path = output_path self.metadata = oeb_book.metadata + self.cover_data = None if input_plugin.is_image_collection: log.debug('Converting input as an image collection...') @@ -90,13 +69,20 @@ class PDFOutput(OutputFormatPlugin, CoverManagerPDF): def convert_images(self, images): self.write(ImagePDFWriter, images) + def get_cover_data(self): + g, m = self.oeb.guide, self.oeb.manifest + if 'titlepage' not in g: + if 'cover' in g: + href = g['cover'].href + from calibre.ebooks.oeb.base import urlnormalize + for item in m: + if item.href == urlnormalize(href): + self.cover_data = item.data + def convert_text(self, oeb_book): self.log.debug('Serializing oeb input to disk for processing...') - self.opts.no_svg_cover = True - self.opts.no_default_epub_cover = True - self.opts.preserve_cover_aspect_ratio = False - self.setup_cover(self.opts) - self.insert_cover() + self.get_cover_data() + with TemporaryDirectory('_pdf_out') as oeb_dir: from calibre.customize.ui import plugin_for_output_format oeb_output = plugin_for_output_format('oeb') @@ -108,7 +94,7 @@ class PDFOutput(OutputFormatPlugin, CoverManagerPDF): self.write(PDFWriter, [s.path for s in opf.spine]) def write(self, Writer, items): - writer = Writer(self.opts, self.log) + writer = Writer(self.opts, self.log, cover_data=self.cover_data) close = False if not hasattr(self.output_path, 'write'): diff --git a/src/calibre/ebooks/pdf/writer.py b/src/calibre/ebooks/pdf/writer.py index 22e653f275..ff624f6831 100644 --- a/src/calibre/ebooks/pdf/writer.py +++ b/src/calibre/ebooks/pdf/writer.py @@ -15,14 +15,20 @@ from calibre.ptempfile import PersistentTemporaryDirectory from calibre.ebooks.pdf.pageoptions import unit, paper_size, \ orientation from calibre.ebooks.metadata import authors_to_string +from calibre.ptempfile import PersistentTemporaryFile +from calibre import __appname__, __version__, fit_image from PyQt4 import QtCore -from PyQt4.Qt import QUrl, QEventLoop, SIGNAL, QObject, \ - QPrinter, QMetaObject, QSizeF, Qt, QPainter +from PyQt4.Qt import QUrl, QEventLoop, QObject, \ + QPrinter, QMetaObject, QSizeF, Qt, QPainter, QPixmap from PyQt4.QtWebKit import QWebView from pyPdf import PdfFileWriter, PdfFileReader +def get_pdf_printer(): + return QPrinter(QPrinter.HighResolution) + + def get_custom_size(opts): custom_size = None if opts.custom_size != None: @@ -36,12 +42,12 @@ def get_custom_size(opts): custom_size = None return custom_size -def get_pdf_page_size(opts): +def setup_printer(opts, for_comic=False): from calibre.gui2 import is_ok_to_use_qt if not is_ok_to_use_qt(): raise Exception('Not OK to use Qt') - printer = QPrinter(QPrinter.HighResolution) + printer = get_pdf_printer() custom_size = get_custom_size(opts) if opts.output_profile.short_name == 'default': @@ -50,37 +56,41 @@ def get_pdf_page_size(opts): else: printer.setPaperSize(QSizeF(custom_size[0], custom_size[1]), unit(opts.unit)) else: - printer.setPaperSize(QSizeF(opts.output_profile.width / opts.output_profile.dpi, - opts.output_profile.height / opts.output_profile.dpi), QPrinter.Inch) + w = opts.output_profile.comic_screen_size[0] if for_comic else \ + opts.output_profile.width + h = opts.output_profile.comic_screen_size[1] if for_comic else \ + opts.output_profile.height + dpi = opts.output_profile.dpi + printer.setPaperSize(QSizeF(float(w) / dpi, float(h)/dpi), QPrinter.Inch) printer.setPageMargins(0, 0, 0, 0, QPrinter.Point) printer.setOrientation(orientation(opts.orientation)) printer.setOutputFormat(QPrinter.PdfFormat) + return printer - size = printer.paperSize(QPrinter.Millimeter) +def get_printer_page_size(opts, for_comic=False): + printer = setup_printer(opts, for_comic=for_comic) + size = printer.paperSize(QPrinter.Millimeter) + return size.width() / 10., size.height() / 10. - return size.width() / 10, size.height() / 10 +def draw_image_page(printer, painter, p, preserve_aspect_ratio=True): + page_rect = printer.pageRect() + if preserve_aspect_ratio: + aspect_ratio = float(p.width())/p.height() + nw, nh = page_rect.width(), page_rect.height() + if aspect_ratio > 1: + nh = int(page_rect.width()/aspect_ratio) + else: # Width is smaller than height + nw = page_rect.height()*aspect_ratio + __, nnw, nnh = fit_image(nw, nh, page_rect.width(), + page_rect.height()) + dx = int((page_rect.width() - nnw)/2.) + dy = int((page_rect.height() - nnh)/2.) + page_rect.moveTo(dx, dy) + page_rect.setHeight(nnh) + page_rect.setWidth(nnw) + painter.drawPixmap(page_rect, p, p.rect()) -def get_imagepdf_page_size(opts): - printer = QPrinter(QPrinter.HighResolution) - custom_size = get_custom_size(opts) - - if opts.output_profile.short_name == 'default': - if custom_size == None: - printer.setPaperSize(paper_size(opts.paper_size)) - else: - printer.setPaperSize(QSizeF(custom_size[0], custom_size[1]), unit(opts.unit)) - else: - printer.setPaperSize(QSizeF(opts.output_profile.comic_screen_size[0] / opts.output_profile.dpi, - opts.output_profile.comic_screen_size[1] / opts.output_profile.dpi), QPrinter.Inch) - - printer.setPageMargins(0, 0, 0, 0, QPrinter.Point) - printer.setOrientation(orientation(opts.orientation)) - printer.setOutputFormat(QPrinter.PdfFormat) - - size = printer.paperSize(QPrinter.Millimeter) - - return size.width() / 10, size.height() / 10 class PDFMetadata(object): def __init__(self, oeb_metadata=None): @@ -94,9 +104,9 @@ class PDFMetadata(object): self.author = authors_to_string([x.value for x in oeb_metadata.creator]) -class PDFWriter(QObject): +class PDFWriter(QObject): # {{{ - def __init__(self, opts, log): + def __init__(self, opts, log, cover_data=None): from calibre.gui2 import is_ok_to_use_qt if not is_ok_to_use_qt(): raise Exception('Not OK to use Qt') @@ -107,14 +117,15 @@ class PDFWriter(QObject): self.loop = QEventLoop() self.view = QWebView() self.view.setRenderHints(QPainter.Antialiasing|QPainter.TextAntialiasing|QPainter.SmoothPixmapTransform) - self.connect(self.view, SIGNAL('loadFinished(bool)'), self._render_html) + self.view.loadFinished.connect(self._render_html, + type=Qt.QueuedConnection) self.render_queue = [] self.combine_queue = [] self.tmp_path = PersistentTemporaryDirectory('_pdf_output_parts') self.opts = opts - - self.size = get_pdf_page_size(opts) + self.size = get_printer_page_size(opts) + self.cover_data = cover_data def dump(self, items, out_stream, pdf_metadata): self.metadata = pdf_metadata @@ -143,17 +154,20 @@ class PDFWriter(QObject): self.view.load(QUrl.fromLocalFile(item)) + def get_printer(self): + printer = get_pdf_printer() + printer.setPaperSize(QSizeF(self.size[0] * 10, self.size[1] * 10), QPrinter.Millimeter) + printer.setPageMargins(0, 0, 0, 0, QPrinter.Point) + printer.setOrientation(orientation(self.opts.orientation)) + printer.setOutputFormat(QPrinter.PdfFormat) + printer.setFullPage(True) + return printer + def _render_html(self, ok): if ok: item_path = os.path.join(self.tmp_path, '%i.pdf' % len(self.combine_queue)) - self.logger.debug('\tRendering item %s as %i' % (os.path.basename(str(self.view.url().toLocalFile())), len(self.combine_queue))) - - printer = QPrinter(QPrinter.HighResolution) - printer.setPaperSize(QSizeF(self.size[0] * 10, self.size[1] * 10), QPrinter.Millimeter) - printer.setPageMargins(0, 0, 0, 0, QPrinter.Point) - printer.setOrientation(orientation(self.opts.orientation)) - printer.setOutputFormat(QPrinter.PdfFormat) + printer = self.get_printer() printer.setOutputFileName(item_path) self.view.print_(printer) self._render_book() @@ -163,9 +177,27 @@ class PDFWriter(QObject): shutil.rmtree(self.tmp_path, True) self.tmp_path = PersistentTemporaryDirectory('_pdf_output_parts') + def insert_cover(self): + if self.cover_data is None: + return + item_path = os.path.join(self.tmp_path, 'cover.pdf') + printer = self.get_printer() + printer.setOutputFileName(item_path) + self.combine_queue.insert(0, item_path) + p = QPixmap() + p.loadFromData(self.cover_data) + if not p.isNull(): + painter = QPainter(printer) + draw_image_page(printer, painter, p, + preserve_aspect_ratio=self.opts.preserve_cover_aspect_ratio) + painter.end() + + def _write(self): self.logger.debug('Combining individual PDF parts...') + self.insert_cover() + try: outPDF = PdfFileWriter(title=self.metadata.title, author=self.metadata.author) for item in self.combine_queue: @@ -177,23 +209,50 @@ class PDFWriter(QObject): self._delete_tmpdir() self.loop.exit(0) +# }}} -class ImagePDFWriter(PDFWriter): +class ImagePDFWriter(object): - def __init__(self, opts, log): - PDFWriter.__init__(self, opts, log) - self.size = get_imagepdf_page_size(opts) + def __init__(self, opts, log, cover_data=None): + self.opts = opts + self.log = log + self.size = get_printer_page_size(opts, for_comic=True) - def _render_next(self): - item = str(self.render_queue.pop(0)) - self.combine_queue.append(os.path.join(self.tmp_path, '%i.pdf' % (len(self.combine_queue) + 1))) + def dump(self, items, out_stream, pdf_metadata): + f = PersistentTemporaryFile('_comic2pdf.pdf') + f.close() + try: + self.render_images(f.name, pdf_metadata, items) + with open(f.name, 'rb') as x: + shutil.copyfileobj(x, out_stream) + finally: + os.remove(f.name) - self.logger.debug('Processing %s...' % item) + def render_images(self, outpath, mi, items): + printer = get_pdf_printer() + printer.setPaperSize(QSizeF(self.size[0] * 10, self.size[1] * 10), QPrinter.Millimeter) + printer.setPageMargins(0, 0, 0, 0, QPrinter.Point) + printer.setOrientation(orientation(self.opts.orientation)) + printer.setOutputFormat(QPrinter.PdfFormat) + printer.setOutputFileName(outpath) + printer.setDocName(mi.title) + printer.setCreator(u'%s [%s]'%(__appname__, __version__)) + # Seems to be no way to set author + printer.setFullPage(True) - height = 'height: %fcm;' % (self.size[1] * 1.3) + painter = QPainter(printer) + painter.setRenderHints(QPainter.Antialiasing|QPainter.SmoothPixmapTransform) - html = '' % (item, height) - - self.view.setHtml(html) + for i, imgpath in enumerate(items): + self.log('Rendering image:', i) + p = QPixmap() + p.load(imgpath) + if not p.isNull(): + if i > 0: + printer.newPage() + draw_image_page(printer, painter, p) + else: + self.log.warn('Failed to load image', i) + painter.end() diff --git a/src/calibre/gui2/convert/pdf_output.py b/src/calibre/gui2/convert/pdf_output.py index 0c63085991..1544d3f812 100644 --- a/src/calibre/gui2/convert/pdf_output.py +++ b/src/calibre/gui2/convert/pdf_output.py @@ -18,7 +18,8 @@ class PluginWidget(Widget, Ui_Form): HELP = _('Options specific to')+' PDF '+_('output') def __init__(self, parent, get_option, get_help, db=None, book_id=None): - Widget.__init__(self, parent, 'pdf_output', ['paper_size', 'orientation']) + Widget.__init__(self, parent, 'pdf_output', ['paper_size', + 'orientation', 'preserve_cover_aspect_ratio']) self.db, self.book_id = db, book_id self.initialize_options(get_option, get_help, db, book_id) diff --git a/src/calibre/gui2/convert/pdf_output.ui b/src/calibre/gui2/convert/pdf_output.ui index ca9bd6b40f..0adb8df495 100644 --- a/src/calibre/gui2/convert/pdf_output.ui +++ b/src/calibre/gui2/convert/pdf_output.ui @@ -40,7 +40,7 @@ - + Qt::Vertical @@ -53,6 +53,13 @@ + + + + Preserve &aspect ratio of cover + + +