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
-
-
-
-
-

-
-
-
- '''%(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
+
+
+