diff --git a/src/calibre/ebooks/epub/output.py b/src/calibre/ebooks/epub/output.py
index 129a63ef3c..71d9d8b423 100644
--- a/src/calibre/ebooks/epub/output.py
+++ b/src/calibre/ebooks/epub/output.py
@@ -46,8 +46,155 @@ block_level_tags = (
'ul',
)
+class CoverManager(object):
-class EPUBOutput(OutputFormatPlugin):
+ '''
+ Manage the cover in the output document. Requires the opts object to have
+ the attributes:
+
+ no_svg_cover
+ no_default_epub_cover
+ preserve_cover_aspect_ratio
+ '''
+
+ NONSVG_TITLEPAGE_COVER = '''\
+
+
+
+
+ Cover
+
+
+
+
+

+
+
+
+ '''
+
+ TITLEPAGE_COVER = '''\
+
+
+
+
+ Cover
+
+
+
+
+
+
+'''
+
+ def default_cover(self):
+ '''
+ Create a generic cover for books that dont have a cover
+ '''
+ from calibre.utils.pil_draw import draw_centered_text
+ from calibre.ebooks.metadata import authors_to_string
+ if self.opts.no_default_epub_cover:
+ return None
+ self.log('Generating default cover')
+ m = self.oeb.metadata
+ title = unicode(m.title[0])
+ authors = [unicode(x) for x in m.creator if x.role == 'aut']
+
+ import cStringIO
+ cover_file = cStringIO.StringIO()
+ try:
+ try:
+ from PIL import Image, ImageDraw, ImageFont
+ Image, ImageDraw, ImageFont
+ except ImportError:
+ import Image, ImageDraw, ImageFont
+ font_path = P('fonts/liberation/LiberationSerif-Bold.ttf')
+ app = '['+__appname__ +' '+__version__+']'
+
+ COVER_WIDTH, COVER_HEIGHT = 590, 750
+ img = Image.new('RGB', (COVER_WIDTH, COVER_HEIGHT), 'white')
+ draw = ImageDraw.Draw(img)
+ # Title
+ font = ImageFont.truetype(font_path, 44)
+ bottom = draw_centered_text(img, draw, font, title, 15, ysep=9)
+ # Authors
+ bottom += 14
+ font = ImageFont.truetype(font_path, 32)
+ authors = authors_to_string(authors)
+ bottom = draw_centered_text(img, draw, font, authors, bottom, ysep=7)
+ # Vanity
+ font = ImageFont.truetype(font_path, 28)
+ width, height = draw.textsize(app, font=font)
+ left = max(int((COVER_WIDTH - width)/2.), 0)
+ top = COVER_HEIGHT - height - 15
+ draw.text((left, top), app, fill=(0,0,0), font=font)
+ # Logo
+ logo = Image.open(I('library.png'), 'r')
+ width, height = logo.size
+ left = max(int((COVER_WIDTH - width)/2.), 0)
+ top = max(int((COVER_HEIGHT - height)/2.), 0)
+ img.paste(logo, (left, max(bottom, top)))
+ img = img.convert('RGB').convert('P', palette=Image.ADAPTIVE)
+
+ img.convert('RGB').save(cover_file, 'JPEG')
+ cover_file.flush()
+ id, href = self.oeb.manifest.generate('cover_image', 'cover_image.jpg')
+ item = self.oeb.manifest.add(id, href, guess_type('t.jpg')[0],
+ data=cover_file.getvalue())
+ m.clear('cover')
+ m.add('cover', item.id)
+
+ return item.href
+ except:
+ self.log.exception('Failed to generate default cover')
+ return None
+
+
+ def insert_cover(self):
+ from calibre.ebooks.oeb.base import urldefrag
+ from calibre import guess_type
+ g, m = self.oeb.guide, self.oeb.manifest
+ item = None
+ ar = 'xMidYMid meet' if self.opts.preserve_cover_aspect_ratio else \
+ 'none'
+ svg_template = self.TITLEPAGE_COVER.replace('__ar__', ar)
+ if 'titlepage' not in g:
+ if 'cover' in g:
+ href = g['cover'].href
+ else:
+ href = self.default_cover()
+ if href is not None:
+ templ = self.NONSVG_TITLEPAGE_COVER if self.opts.no_svg_cover \
+ else svg_template
+ tp = templ%unquote(href)
+ id, href = m.generate('titlepage', 'titlepage.xhtml')
+ item = m.add(id, href, guess_type('t.xhtml')[0],
+ data=etree.fromstring(tp))
+ else:
+ item = self.oeb.manifest.hrefs[
+ urldefrag(self.oeb.guide['titlepage'].href)[0]]
+ if item is not None:
+ self.oeb.spine.insert(0, item, True)
+ if 'cover' not in self.oeb.guide.refs:
+ self.oeb.guide.add('cover', 'Title Page', 'a')
+ self.oeb.guide.refs['cover'].href = item.href
+ if 'titlepage' in self.oeb.guide.refs:
+ self.oeb.guide.refs['titlepage'].href = item.href
+
+
+class EPUBOutput(OutputFormatPlugin, CoverManager):
name = 'EPUB Output'
author = 'Kovid Goyal'
@@ -92,51 +239,21 @@ class EPUBOutput(OutputFormatPlugin):
'as a blank page.')
),
+ OptionRecommendation(name='preserve_cover_aspect_ratio',
+ recommended_value=False, help=_(
+ 'When using an SVG cover, this option will cause the cover to scale '
+ 'to cover the available screen area, but still preserve its aspect ratio '
+ '(ratio of width to height). That means there may be white borders '
+ 'at the sides or top and bottom of the image, but the image will '
+ 'never be distorted. Without this option the image may be slightly '
+ 'distorted, but there will be no borders.'
+ )
+ ),
+
])
recommendations = set([('pretty_print', True, OptionRecommendation.HIGH)])
- NONSVG_TITLEPAGE_COVER = '''\
-
-
-
-
- Cover
-
-
-
-
-

-
-
-
- '''
-
- TITLEPAGE_COVER = '''\
-
-
-
-
- Cover
-
-
-
-
-
-
-'''
def workaround_webkit_quirks(self):
from calibre.ebooks.oeb.base import XPath
@@ -259,97 +376,6 @@ class EPUBOutput(OutputFormatPlugin):
ans += '\n'
return ans
- def default_cover(self):
- '''
- Create a generic cover for books that dont have a cover
- '''
- from calibre.utils.pil_draw import draw_centered_text
- from calibre.ebooks.metadata import authors_to_string
- if self.opts.no_default_epub_cover:
- return None
- self.log('Generating default cover')
- m = self.oeb.metadata
- title = unicode(m.title[0])
- authors = [unicode(x) for x in m.creator if x.role == 'aut']
-
- import cStringIO
- cover_file = cStringIO.StringIO()
- try:
- try:
- from PIL import Image, ImageDraw, ImageFont
- Image, ImageDraw, ImageFont
- except ImportError:
- import Image, ImageDraw, ImageFont
- font_path = P('fonts/liberation/LiberationSerif-Bold.ttf')
- app = '['+__appname__ +' '+__version__+']'
-
- COVER_WIDTH, COVER_HEIGHT = 590, 750
- img = Image.new('RGB', (COVER_WIDTH, COVER_HEIGHT), 'white')
- draw = ImageDraw.Draw(img)
- # Title
- font = ImageFont.truetype(font_path, 44)
- bottom = draw_centered_text(img, draw, font, title, 15, ysep=9)
- # Authors
- bottom += 14
- font = ImageFont.truetype(font_path, 32)
- authors = authors_to_string(authors)
- bottom = draw_centered_text(img, draw, font, authors, bottom, ysep=7)
- # Vanity
- font = ImageFont.truetype(font_path, 28)
- width, height = draw.textsize(app, font=font)
- left = max(int((COVER_WIDTH - width)/2.), 0)
- top = COVER_HEIGHT - height - 15
- draw.text((left, top), app, fill=(0,0,0), font=font)
- # Logo
- logo = Image.open(I('library.png'), 'r')
- width, height = logo.size
- left = max(int((COVER_WIDTH - width)/2.), 0)
- top = max(int((COVER_HEIGHT - height)/2.), 0)
- img.paste(logo, (left, max(bottom, top)))
- img = img.convert('RGB').convert('P', palette=Image.ADAPTIVE)
-
- img.convert('RGB').save(cover_file, 'JPEG')
- cover_file.flush()
- id, href = self.oeb.manifest.generate('cover_image', 'cover_image.jpg')
- item = self.oeb.manifest.add(id, href, guess_type('t.jpg')[0],
- data=cover_file.getvalue())
- m.clear('cover')
- m.add('cover', item.id)
-
- return item.href
- except:
- self.log.exception('Failed to generate default cover')
- return None
-
-
- def insert_cover(self):
- from calibre.ebooks.oeb.base import urldefrag
- from calibre import guess_type
- g, m = self.oeb.guide, self.oeb.manifest
- item = None
- if 'titlepage' not in g:
- if 'cover' in g:
- href = g['cover'].href
- else:
- href = self.default_cover()
- if href is not None:
- templ = self.NONSVG_TITLEPAGE_COVER if self.opts.no_svg_cover \
- else self.TITLEPAGE_COVER
- tp = templ%unquote(href)
- id, href = m.generate('titlepage', 'titlepage.xhtml')
- item = m.add(id, href, guess_type('t.xhtml')[0],
- data=etree.fromstring(tp))
- else:
- item = self.oeb.manifest.hrefs[
- urldefrag(self.oeb.guide['titlepage'].href)[0]]
- if item is not None:
- self.oeb.spine.insert(0, item, True)
- if 'cover' not in self.oeb.guide.refs:
- self.oeb.guide.add('cover', 'Title Page', 'a')
- self.oeb.guide.refs['cover'].href = item.href
- if 'titlepage' in self.oeb.guide.refs:
- self.oeb.guide.refs['titlepage'].href = item.href
-
def condense_ncx(self, ncx_path):
if not self.opts.pretty_print:
tree = etree.parse(ncx_path)
diff --git a/src/calibre/ebooks/pdf/output.py b/src/calibre/ebooks/pdf/output.py
index b2d649c2cf..e302f67441 100644
--- a/src/calibre/ebooks/pdf/output.py
+++ b/src/calibre/ebooks/pdf/output.py
@@ -15,11 +15,39 @@ 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
+from calibre.ebooks.pdf.writer import PDFWriter, ImagePDFWriter, PDFMetadata, \
+ get_pdf_page_size
from calibre.ebooks.pdf.pageoptions import UNITS, PAPER_SIZES, \
ORIENTATIONS
+from calibre.ebooks.epub.output import CoverManager
-class PDFOutput(OutputFormatPlugin):
+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):
name = 'PDF Output'
author = 'John Schember'
@@ -47,6 +75,7 @@ class PDFOutput(OutputFormatPlugin):
])
def convert(self, oeb_book, output_path, input_plugin, opts, log):
+ self.oeb = oeb_book
self.input_plugin, self.opts, self.log = input_plugin, opts, log
self.output_path = output_path
self.metadata = oeb_book.metadata
@@ -63,6 +92,10 @@ class PDFOutput(OutputFormatPlugin):
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.setup_cover(self.opts)
+ self.insert_cover()
with TemporaryDirectory('_pdf_out') as oeb_dir:
from calibre.customize.ui import plugin_for_output_format
oeb_output = plugin_for_output_format('oeb')
diff --git a/src/calibre/ebooks/pdf/writer.py b/src/calibre/ebooks/pdf/writer.py
index 9b5094ac95..22e653f275 100644
--- a/src/calibre/ebooks/pdf/writer.py
+++ b/src/calibre/ebooks/pdf/writer.py
@@ -18,11 +18,70 @@ from calibre.ebooks.metadata import authors_to_string
from PyQt4 import QtCore
from PyQt4.Qt import QUrl, QEventLoop, SIGNAL, QObject, \
- QPrinter, QMetaObject, QSizeF, Qt
+ QPrinter, QMetaObject, QSizeF, Qt, QPainter
from PyQt4.QtWebKit import QWebView
from pyPdf import PdfFileWriter, PdfFileReader
+def get_custom_size(opts):
+ custom_size = None
+ if opts.custom_size != None:
+ width, sep, height = opts.custom_size.partition('x')
+ if height != '':
+ try:
+ width = int(width)
+ height = int(height)
+ custom_size = (width, height)
+ except:
+ custom_size = None
+ return custom_size
+
+def get_pdf_page_size(opts):
+ 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)
+ custom_size = get_custom_size(opts)
+
+ if opts.output_profile.short_name == 'default':
+ if custom_size is 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.width / opts.output_profile.dpi,
+ opts.output_profile.height / 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
+
+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):
self.title = _('Unknown')
@@ -36,6 +95,7 @@ class PDFMetadata(object):
class PDFWriter(QObject):
+
def __init__(self, opts, log):
from calibre.gui2 import is_ok_to_use_qt
if not is_ok_to_use_qt():
@@ -46,25 +106,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.render_queue = []
self.combine_queue = []
self.tmp_path = PersistentTemporaryDirectory('_pdf_output_parts')
- self.custom_size = None
- if opts.custom_size != None:
- width, sep, height = opts.custom_size.partition('x')
- if height != '':
- try:
- width = int(width)
- height = int(height)
- self.custom_size = (width, height)
- except:
- self.custom_size = None
-
self.opts = opts
- self.size = self._size()
+ self.size = get_pdf_page_size(opts)
def dump(self, items, out_stream, pdf_metadata):
self.metadata = pdf_metadata
@@ -77,27 +127,6 @@ class PDFWriter(QObject):
QMetaObject.invokeMethod(self, "_render_book", Qt.QueuedConnection)
self.loop.exec_()
- def _size(self):
- '''
- The size of a pdf page in cm.
- '''
- printer = QPrinter(QPrinter.HighResolution)
-
- if self.opts.output_profile.short_name == 'default':
- if self.custom_size == None:
- printer.setPaperSize(paper_size(self.opts.paper_size))
- else:
- printer.setPaperSize(QSizeF(self.custom_size[0], self.custom_size[1]), unit(self.opts.unit))
- else:
- printer.setPaperSize(QSizeF(self.opts.output_profile.width / self.opts.output_profile.dpi, self.opts.output_profile.height / self.opts.output_profile.dpi), QPrinter.Inch)
-
- printer.setPageMargins(0, 0, 0, 0, QPrinter.Point)
- printer.setOrientation(orientation(self.opts.orientation))
- printer.setOutputFormat(QPrinter.PdfFormat)
-
- size = printer.paperSize(QPrinter.Millimeter)
-
- return size.width() / 10, size.height() / 10
@QtCore.pyqtSignature('_render_book()')
def _render_book(self):
@@ -151,6 +180,10 @@ class PDFWriter(QObject):
class ImagePDFWriter(PDFWriter):
+ def __init__(self, opts, log):
+ PDFWriter.__init__(self, opts, log)
+ self.size = get_imagepdf_page_size(opts)
+
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)))
@@ -163,22 +196,4 @@ class ImagePDFWriter(PDFWriter):
self.view.setHtml(html)
- def _size(self):
- printer = QPrinter(QPrinter.HighResolution)
-
- if self.opts.output_profile.short_name == 'default':
- if self.custom_size == None:
- printer.setPaperSize(paper_size(self.opts.paper_size))
- else:
- printer.setPaperSize(QSizeF(self.custom_size[0], self.custom_size[1]), unit(self.opts.unit))
- else:
- printer.setPaperSize(QSizeF(self.opts.output_profile.comic_screen_size[0] / self.opts.output_profile.dpi, self.opts.output_profile.comic_screen_size[1] / self.opts.output_profile.dpi), QPrinter.Inch)
-
- printer.setPageMargins(0, 0, 0, 0, QPrinter.Point)
- printer.setOrientation(orientation(self.opts.orientation))
- printer.setOutputFormat(QPrinter.PdfFormat)
-
- size = printer.paperSize(QPrinter.Millimeter)
-
- return size.width() / 10, size.height() / 10
diff --git a/src/calibre/gui2/convert/epub_output.py b/src/calibre/gui2/convert/epub_output.py
index 57027d9315..8130b00273 100644
--- a/src/calibre/gui2/convert/epub_output.py
+++ b/src/calibre/gui2/convert/epub_output.py
@@ -18,8 +18,11 @@ class PluginWidget(Widget, Ui_Form):
def __init__(self, parent, get_option, get_help, db=None, book_id=None):
Widget.__init__(self, parent, 'epub_output',
['dont_split_on_page_breaks', 'flow_size',
- 'no_default_epub_cover', 'no_svg_cover']
+ 'no_default_epub_cover', 'no_svg_cover',
+ 'preserve_cover_aspect_ratio',]
)
+ for i in range(2):
+ self.opt_no_svg_cover.toggle()
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/epub_output.ui b/src/calibre/gui2/convert/epub_output.ui
index 7f92ec3087..abca2405e8 100644
--- a/src/calibre/gui2/convert/epub_output.ui
+++ b/src/calibre/gui2/convert/epub_output.ui
@@ -14,13 +14,34 @@
Form
- -
+
-
Do not &split on page breaks
+ -
+
+
+ No default &cover
+
+
+
+ -
+
+
+ No &SVG cover
+
+
+
+ -
+
+
+ Preserve cover &aspect ratio
+
+
+
-
@@ -60,22 +81,25 @@
- -
-
-
- No default &cover
-
-
-
- -
-
-
- No &SVG cover
-
-
-
-
+
+
+ opt_no_svg_cover
+ toggled(bool)
+ opt_preserve_cover_aspect_ratio
+ setDisabled(bool)
+
+
+ 81
+ 73
+
+
+ 237
+ 68
+
+
+
+