diff --git a/src/calibre/ebooks/epub/output.py b/src/calibre/ebooks/epub/output.py index 3a353103c8..61aa2c359a 100644 --- a/src/calibre/ebooks/epub/output.py +++ b/src/calibre/ebooks/epub/output.py @@ -12,7 +12,7 @@ from urllib import unquote from calibre.customize.conversion import OutputFormatPlugin from calibre.ptempfile import TemporaryDirectory from calibre.constants import __appname__, __version__ -from calibre import strftime, guess_type, prepare_string_for_xml, CurrentDir +from calibre import guess_type, CurrentDir from calibre.customize.conversion import OptionRecommendation from calibre.constants import filesystem_encoding @@ -110,37 +110,6 @@ class EPUBOutput(OutputFormatPlugin): ''' - TITLEPAGE = '''\ - - - %(title)s - - - -

%(title)s

-
- -
-

%(author)s

-

Produced by %(app)s

- - -''' def workaround_webkit_quirks(self): from calibre.ebooks.oeb.base import XPath for x in self.oeb.spine: @@ -262,42 +231,80 @@ class EPUBOutput(OutputFormatPlugin): ''' 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') - from calibre.ebooks.metadata import authors_to_string m = self.oeb.metadata title = unicode(m.title[0]) - a = [unicode(x) for x in m.creator if x.role == 'aut'] - author = authors_to_string(a) - img_data = open(I('library.png'), 'rb').read() - id, href = self.oeb.manifest.generate('calibre-logo', - 'calibre-logo.png') - self.oeb.manifest.add(id, href, 'image/png', data=img_data) - title, author = map(prepare_string_for_xml, (title, author)) - if not author or not author.strip(): - author = strftime('%d %b, %Y') - html = self.TITLEPAGE%dict(title=title, author=author, - app=__appname__ +' '+__version__, - img=href) - id, href = self.oeb.manifest.generate('calibre-titlepage', - 'calibre-titlepage.xhtml') - return self.oeb.manifest.add(id, href, guess_type('t.xhtml')[0], - data=etree.fromstring(html)) + 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: - tp = self.TITLEPAGE_COVER%unquote(g['cover'].href) + href = g['cover'].href + else: + href = self.default_cover() + if href is not None: + tp = self.TITLEPAGE_COVER%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.default_cover() else: item = self.oeb.manifest.hrefs[ urldefrag(self.oeb.guide['titlepage'].href)[0]] diff --git a/src/calibre/ebooks/metadata/epub.py b/src/calibre/ebooks/metadata/epub.py index b063d3ef52..8d77cf2833 100644 --- a/src/calibre/ebooks/metadata/epub.py +++ b/src/calibre/ebooks/metadata/epub.py @@ -99,15 +99,33 @@ class OCFDirReader(OCFReader): return open(os.path.join(self.root, path), *args, **kwargs) def get_cover(opf, opf_path, stream): + import posixpath from calibre.ebooks import render_html_svg_workaround from calibre.utils.logging import default_log + raster_cover = opf.raster_cover + stream.seek(0) + zf = ZipFile(stream) + if raster_cover: + base = posixpath.dirname(opf_path) + cpath = posixpath.normpath(posixpath.join(base, raster_cover)) + try: + member = zf.getinfo(cpath) + except: + pass + else: + f = zf.open(member) + data = f.read() + f.close() + zf.close() + return data + cpage = opf.first_spine_item() if not cpage: return + with TemporaryDirectory('_epub_meta') as tdir: with CurrentDir(tdir): - stream.seek(0) - ZipFile(stream).extractall() + zf.extractall() opf_path = opf_path.replace('/', os.sep) cpage = os.path.join(tdir, os.path.dirname(opf_path), cpage) if not os.path.exists(cpage): diff --git a/src/calibre/ebooks/metadata/opf2.py b/src/calibre/ebooks/metadata/opf2.py index 4bc74bc8e6..7eeac5cc85 100644 --- a/src/calibre/ebooks/metadata/opf2.py +++ b/src/calibre/ebooks/metadata/opf2.py @@ -433,6 +433,8 @@ class OPF(object): tags_path = XPath('descendant::*[re:match(name(), "subject", "i")]') isbn_path = XPath('descendant::*[re:match(name(), "identifier", "i") and '+ '(re:match(@scheme, "isbn", "i") or re:match(@opf:scheme, "isbn", "i"))]') + raster_cover_path = XPath('descendant::*[re:match(name(), "meta", "i") and ' + + 're:match(@name, "cover", "i") and @content]') identifier_path = XPath('descendant::*[re:match(name(), "identifier", "i")]') application_id_path = XPath('descendant::*[re:match(name(), "identifier", "i") and '+ '(re:match(@opf:scheme, "calibre|libprs500", "i") or re:match(@scheme, "calibre|libprs500", "i"))]') @@ -804,6 +806,14 @@ class OPF(object): if os.access(os.path.join(self.base_dir, prefix+suffix), os.R_OK): return cpath + @property + def raster_cover(self): + covers = self.raster_cover_path(self.metadata) + if covers: + cover_id = covers[0].get('content') + for item in self.itermanifest(): + if item.get('id', None) == cover_id: + return item.get('href', None) @dynamic_property def cover(self): diff --git a/src/calibre/utils/pil_draw.py b/src/calibre/utils/pil_draw.py new file mode 100644 index 0000000000..66a483e75c --- /dev/null +++ b/src/calibre/utils/pil_draw.py @@ -0,0 +1,33 @@ +#!/usr/bin/env python +# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:ai +from __future__ import with_statement + +__license__ = 'GPL v3' +__copyright__ = '2010, Kovid Goyal ' +__docformat__ = 'restructuredtext en' + +def _get_line(draw, font, tokens, line_width): + line, rest = tokens, [] + while True: + width, height = draw.textsize(' '.join(line), font=font) + if width < line_width: + return line, rest + rest = line[-1:] + rest + line = line[:-1] + +def draw_centered_line(img, draw, font, line, top): + width, height = draw.textsize(line, font=font) + left = max(int((img.size[0] - width)/2.), 0) + draw.text((left, top), line, fill=(0,0,0), font=font) + return top + height + +def draw_centered_text(img, draw, font, text, top, margin=10, ysep=5): + img_width, img_height = img.size + tokens = text.split(' ') + while tokens: + line, tokens = _get_line(draw, font, tokens, img_width-2*margin) + bottom = draw_centered_line(img, draw, font, ' '.join(line), top) + top = bottom + ysep + return top - ysep + +