diff --git a/Changelog.yaml b/Changelog.yaml
index d6204743f8..c4a8ffc2dd 100644
--- a/Changelog.yaml
+++ b/Changelog.yaml
@@ -4,6 +4,70 @@
# for important features/bug fixes.
# Also, each release can have new and improved recipes.
+- version: 0.6.54
+ date: 2010-05-21
+
+ new features:
+ - title: "EPUB Output: Add option to toggle preserving the aspect ratio of the cover."
+ type: major
+ description: >
+ "By default calibre creates an SVG based cover that scales with the screen size of the reader used to view it. Previosuly this scaling
+ was limited to preserve the aspect ratio of the image. This would often result in white borders at the sides or top and bottom of the image.
+ No, by default, calibre will setup the cover to not preserve aspect ratio, doing away with the white borders. The downside is that if the
+ aspect ratio of the cover is very different from the reader, it will look distorted. The old behavior can be restored via
+ Preferences->Conversion->EPUB Output."
+
+ - title: "Conversion pipeline: calibre will now automatically replace all ligatures in the input document."
+ type: major
+ description: >
+ "Conversion pipeline: calibre will now automatically replace all ligatures in the input document with the normal character
+ sequence they are meant to represent. This is because most readers lack the font support to display ligatures.
+ This can be turned off via an option under Look & Feel, in the Conversion settings."
+
+ - title: "Support for the iPapyrus and Newsmy readers and the Sony Ericsson XPERIA X10"
+
+ - title: "PDF Output: Set the first page to the cover."
+ tickets: [5581]
+
+ bug fixes:
+ - title: "Conversion pipeline: Handle input documents with no text. Allows conversion of MOBI files tha are only a sequence of images."
+ tickets: [5554]
+
+ - title: "Fix text justification control not working with translated version of calibre"
+ tickets: [5551]
+
+ - title: "HTML Input: Encoding detection fixed for tags that have newlines in their content attributes"
+ tickets: [5567]
+
+ - title: "EPUB Input: Handle malformed UUID in EPUB with obfuscated fonts."
+ tickets: [5552]
+
+ - title: "Don't resort when editing columns in the main GUI"
+
+ - title: "Fix regression in Kobo driver that caused it to only detect books in the root directory of the device"
+
+ new recipes:
+ - title: La Stampa and Libero
+ author: Gabriele Marini
+
+ - title: Der Tagesspiegel
+ author: ipaschke
+
+ - title: EMG and Agro Gerilla
+ author: Darko Miletic
+
+ - title: American Prospect, FactCheck and PolitiFact
+ author: Michael Heinz
+
+ improved recipes:
+ - Times Online
+ - The Atlantic
+ - Il Messagero
+ - Leggo
+ - Instapaper
+ - New York Review of Books
+ - NIN Online
+
- version: 0.6.53
date: 2010-05-15
@@ -157,7 +221,7 @@
new features:
- title: "Add merge book feature"
type: major
- desc: >
+ description: >
"You can now merge multiple books into a single book, by clicking the arrow next to the edit meta information button.
Meta information from the books will be merged as well as individual book files in different formats"
diff --git a/resources/recipes/atlantic.recipe b/resources/recipes/atlantic.recipe
index c6db016010..a41a931e37 100644
--- a/resources/recipes/atlantic.recipe
+++ b/resources/recipes/atlantic.recipe
@@ -5,7 +5,7 @@
'''
theatlantic.com
'''
-import string
+import string, re
from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import Tag, NavigableString
@@ -23,6 +23,8 @@ class TheAtlantic(BasicNewsRecipe):
remove_tags = [dict(id=['header', 'printAds', 'pageControls'])]
no_stylesheets = True
+ preprocess_regexps = [(re.compile(r'', re.DOTALL), lambda m: '')]
+
def print_version(self, url):
return url.replace('/archive/', '/print/')
diff --git a/resources/recipes/economist.recipe b/resources/recipes/economist.recipe
index 35e06e65e6..4ae0bb8b05 100644
--- a/resources/recipes/economist.recipe
+++ b/resources/recipes/economist.recipe
@@ -22,7 +22,7 @@ class Economist(BasicNewsRecipe):
' Needs a subscription from ')+INDEX
oldest_article = 7.0
- cover_url = 'http://www.economist.com/images/covers/currentcovereu_large.jpg'
+ cover_url = 'http://www.economist.com/images/covers/currentcoverus_large.jpg'
remove_tags = [dict(name=['script', 'noscript', 'title', 'iframe', 'cf_floatingcontent']),
dict(attrs={'class':['dblClkTrk']})]
remove_tags_before = dict(name=lambda tag: tag.name=='title' and tag.parent.name=='body')
diff --git a/resources/recipes/economist_free.recipe b/resources/recipes/economist_free.recipe
index 32e108d2d6..cdcd457501 100644
--- a/resources/recipes/economist_free.recipe
+++ b/resources/recipes/economist_free.recipe
@@ -15,7 +15,7 @@ class Economist(BasicNewsRecipe):
' Much slower than the subscription based version.')
oldest_article = 7.0
- cover_url = 'http://www.economist.com/images/covers/currentcovereu_large.jpg'
+ cover_url = 'http://www.economist.com/images/covers/currentcoverus_large.jpg'
remove_tags = [dict(name=['script', 'noscript', 'title', 'iframe', 'cf_floatingcontent']),
dict(attrs={'class':['dblClkTrk']})]
remove_tags_before = dict(name=lambda tag: tag.name=='title' and tag.parent.name=='body')
diff --git a/resources/recipes/tagesspiegel.recipe b/resources/recipes/tagesspiegel.recipe
new file mode 100644
index 0000000000..e5d2600ae0
--- /dev/null
+++ b/resources/recipes/tagesspiegel.recipe
@@ -0,0 +1,86 @@
+__license__ = 'GPL v3'
+__copyright__ = '2010 Ingo Paschke '
+
+'''
+Fetch Tagesspiegel.
+'''
+import string, re
+from calibre import strftime
+from calibre.web.feeds.news import BasicNewsRecipe
+
+class TagesspiegelRSS(BasicNewsRecipe):
+ title = u'Der Tagesspiegel'
+ __author__ = 'ipaschke'
+ language = 'de'
+ oldest_article = 7
+ max_articles_per_feed = 100
+
+ extra_css = '''
+ .hcf-overline{color:#990000; font-family:Arial,Helvetica,sans-serif;font-size:xx-small;display:block}
+ .hcf-teaser{font-family:Verdana,Arial,Helvetica;font-size:x-small;margin-top:0}
+ h1{font-family:Arial,Helvetica,sans-serif;font-size:large;clear:right;}
+ .hcf-caption{color:#666666; font-family:Arial,Helvetica,sans-serif;font-size:xx-small;}
+ .hcf-copyright{color:#666666; font-family:Arial,Helvetica,sans-serif;font-size:xx-small;}
+ .hcf-article{font-family:Arial,Helvetica;font-size:x-small}
+ .quote{font-family:Georgia,Palatino,Palatino Linotype,FreeSerif,serif;font-size:x-small}
+ .quote .cite{font-family:Georgia,Palatino,Palatino Linotype,FreeSerif,serif;font-size:xx-small}
+ .hcf-inline-left{float:left;margin-right:15px;position:relative;}
+ .hcf-inline-right{float:right;margin-right:15px;position:relative;}
+ .hcf-smart-box{font-family: Arial, Helvetica, sans-serif; font-size: xx-small; margin: 0px 15px 8px 0px; width: 300px;}
+ '''
+
+ no_stylesheets = True
+ no_javascript = True
+ remove_empty_feeds = True
+ encoding = 'utf-8'
+
+ keep_only_tags = dict(name='div', attrs={'class':["hcf-article"]})
+ remove_tags = [
+ dict(name='link'), dict(name='iframe'),dict(name='style'),dict(name='meta'),dict(name='button'),
+ dict(name='div', attrs={'class':["hcf-jump-to-comments","hcf-clear","hcf-magnify hcf-media-control"] }),
+ dict(name='span', attrs={'class':["hcf-mainsearch",] }),
+ dict(name='ul', attrs={'class':["hcf-tools"] }),
+ ]
+
+ def parse_index(self):
+ soup = self.index_to_soup('http://www.tagesspiegel.de/zeitung/')
+
+ def feed_title(div):
+ return ''.join(div.findAll(text=True, recursive=False)).strip()
+
+ articles = {}
+ key = None
+ ans = []
+
+ for div in soup.findAll(True, attrs={'class':['hcf-teaser', 'hcf-header', 'story headline']}):
+
+ if div['class'] == 'hcf-header':
+ key = string.capwords(feed_title(div.em.a))
+ articles[key] = []
+ ans.append(key)
+
+ elif div['class'] == 'hcf-teaser' and getattr(div.contents[0],'name','') == 'h2':
+ a = div.find('a', href=True)
+ if not a:
+ continue
+ url = 'http://www.tagesspiegel.de' + a['href']
+ title = self.tag_to_string(a, use_alt=True).strip()
+ description = ''
+ pubdate = strftime('%a, %d %b')
+ summary = div.find('p', attrs={'class':'hcf-teaser'})
+ if summary:
+ description = self.tag_to_string(summary, use_alt=False)
+
+ feed = key if key is not None else 'Uncategorized'
+ if not articles.has_key(feed):
+ articles[feed] = []
+ if not 'podcasts' in url:
+ articles[feed].append(
+ dict(title=title, url=url, date=pubdate,
+ description=re.sub('mehr$', '', description),
+ content=''))
+
+ ans = [(key, articles[key]) for key in ans if articles.has_key(key)]
+
+ return ans
+
diff --git a/resources/recipes/times_online.recipe b/resources/recipes/times_online.recipe
index 98e96552ce..a57749c79d 100644
--- a/resources/recipes/times_online.recipe
+++ b/resources/recipes/times_online.recipe
@@ -5,6 +5,7 @@
'''
timesonline.co.uk
'''
+import re
from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import Tag
@@ -26,6 +27,8 @@ class Timesonline(BasicNewsRecipe):
recursions = 9
match_regexps = [r'http://www.timesonline.co.uk/.*page=[2-9]']
+ preprocess_regexps = [(re.compile(r'', re.DOTALL), lambda m: '')]
+
keep_only_tags = [
dict(name='div', attrs= {'id':['region-column1and2-layout2']}),
{'class' : ['subheading']},
@@ -76,8 +79,7 @@ def get_cover_url(self):
soup = self.index_to_soup(index)
link_item = soup.find(name = 'div',attrs ={'class': "float-left margin-right-15"})
if link_item:
- cover_url = 'http://www.timesonline.co.uk' + link_item.img['src']
- print cover_url
+ cover_url = link_item.img['src']
return cover_url
def get_article_url(self, article):
@@ -85,9 +87,9 @@ def get_article_url(self, article):
def preprocess_html(self, soup):
- soup.html['xml:lang'] = self.lang
- soup.html['lang'] = self.lang
- mlang = Tag(soup,'meta',[("http-equiv","Content-Language"),("content",self.lang)])
+ soup.html['xml:lang'] = self.language
+ soup.html['lang'] = self.language
+ mlang = Tag(soup,'meta',[("http-equiv","Content-Language"),("content",self.language)])
mcharset = Tag(soup,'meta',[("http-equiv","Content-Type"),("content","text/html; charset=ISO-8859-1")])
soup.head.insert(0,mlang)
soup.head.insert(1,mcharset)
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 @@
'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 @@ def encrypt_fonts(self, uris, tdir, uuid):
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 @@
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
+
+
+
+