Pull from trunk

This commit is contained in:
Kovid Goyal 2010-05-21 15:44:35 -06:00
commit bda0779201
15 changed files with 485 additions and 215 deletions

View File

@ -4,6 +4,70 @@
# for important features/bug fixes. # for important features/bug fixes.
# Also, each release can have new and improved recipes. # 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 <meta> 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 - version: 0.6.53
date: 2010-05-15 date: 2010-05-15
@ -157,7 +221,7 @@
new features: new features:
- title: "Add merge book feature" - title: "Add merge book feature"
type: major 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. "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" Meta information from the books will be merged as well as individual book files in different formats"

View File

@ -5,7 +5,7 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
''' '''
theatlantic.com theatlantic.com
''' '''
import string import string, re
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import Tag, NavigableString from calibre.ebooks.BeautifulSoup import Tag, NavigableString
@ -23,6 +23,8 @@ class TheAtlantic(BasicNewsRecipe):
remove_tags = [dict(id=['header', 'printAds', 'pageControls'])] remove_tags = [dict(id=['header', 'printAds', 'pageControls'])]
no_stylesheets = True no_stylesheets = True
preprocess_regexps = [(re.compile(r'<!--.*?-->', re.DOTALL), lambda m: '')]
def print_version(self, url): def print_version(self, url):
return url.replace('/archive/', '/print/') return url.replace('/archive/', '/print/')

View File

@ -22,7 +22,7 @@ class Economist(BasicNewsRecipe):
' Needs a subscription from ')+INDEX ' Needs a subscription from ')+INDEX
oldest_article = 7.0 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']), remove_tags = [dict(name=['script', 'noscript', 'title', 'iframe', 'cf_floatingcontent']),
dict(attrs={'class':['dblClkTrk']})] dict(attrs={'class':['dblClkTrk']})]
remove_tags_before = dict(name=lambda tag: tag.name=='title' and tag.parent.name=='body') remove_tags_before = dict(name=lambda tag: tag.name=='title' and tag.parent.name=='body')

View File

@ -15,7 +15,7 @@ class Economist(BasicNewsRecipe):
' Much slower than the subscription based version.') ' Much slower than the subscription based version.')
oldest_article = 7.0 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']), remove_tags = [dict(name=['script', 'noscript', 'title', 'iframe', 'cf_floatingcontent']),
dict(attrs={'class':['dblClkTrk']})] dict(attrs={'class':['dblClkTrk']})]
remove_tags_before = dict(name=lambda tag: tag.name=='title' and tag.parent.name=='body') remove_tags_before = dict(name=lambda tag: tag.name=='title' and tag.parent.name=='body')

View File

@ -0,0 +1,86 @@
__license__ = 'GPL v3'
__copyright__ = '2010 Ingo Paschke <ipaschke@gmail.com>'
'''
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

View File

@ -5,6 +5,7 @@ __copyright__ = '2008-2009, Darko Miletic <darko.miletic at gmail.com>'
''' '''
timesonline.co.uk timesonline.co.uk
''' '''
import re
from calibre.web.feeds.news import BasicNewsRecipe from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import Tag from calibre.ebooks.BeautifulSoup import Tag
@ -26,6 +27,8 @@ class Timesonline(BasicNewsRecipe):
recursions = 9 recursions = 9
match_regexps = [r'http://www.timesonline.co.uk/.*page=[2-9]'] match_regexps = [r'http://www.timesonline.co.uk/.*page=[2-9]']
preprocess_regexps = [(re.compile(r'<!--.*?-->', re.DOTALL), lambda m: '')]
keep_only_tags = [ keep_only_tags = [
dict(name='div', attrs= {'id':['region-column1and2-layout2']}), dict(name='div', attrs= {'id':['region-column1and2-layout2']}),
{'class' : ['subheading']}, {'class' : ['subheading']},
@ -76,8 +79,7 @@ class Timesonline(BasicNewsRecipe):
soup = self.index_to_soup(index) soup = self.index_to_soup(index)
link_item = soup.find(name = 'div',attrs ={'class': "float-left margin-right-15"}) link_item = soup.find(name = 'div',attrs ={'class': "float-left margin-right-15"})
if link_item: if link_item:
cover_url = 'http://www.timesonline.co.uk' + link_item.img['src'] cover_url = link_item.img['src']
print cover_url
return cover_url return cover_url
def get_article_url(self, article): def get_article_url(self, article):
@ -85,9 +87,9 @@ class Timesonline(BasicNewsRecipe):
def preprocess_html(self, soup): def preprocess_html(self, soup):
soup.html['xml:lang'] = self.lang soup.html['xml:lang'] = self.language
soup.html['lang'] = self.lang soup.html['lang'] = self.language
mlang = Tag(soup,'meta',[("http-equiv","Content-Language"),("content",self.lang)]) 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")]) mcharset = Tag(soup,'meta',[("http-equiv","Content-Type"),("content","text/html; charset=ISO-8859-1")])
soup.head.insert(0,mlang) soup.head.insert(0,mlang)
soup.head.insert(1,mcharset) soup.head.insert(1,mcharset)

View File

@ -46,8 +46,155 @@ block_level_tags = (
'ul', '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 = '''\
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="calibre:cover" content="true" />
<title>Cover</title>
<style type="text/css" title="override_css">
@page {padding: 0pt; margin:0pt}
body { text-align: center; padding:0pt; margin: 0pt; }
div { padding:0pt; margin: 0pt; }
</style>
</head>
<body>
<div>
<img src="%s" alt="cover" style="height: 100%%" />
</div>
</body>
</html>
'''
TITLEPAGE_COVER = '''\
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="calibre:cover" content="true" />
<title>Cover</title>
<style type="text/css" title="override_css">
@page {padding: 0pt; margin:0pt}
body { text-align: center; padding:0pt; margin: 0pt; }
</style>
</head>
<body>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="100%%" height="100%%" viewBox="0 0 600 800"
preserveAspectRatio="__ar__">
<image width="600" height="800" xlink:href="%s"/>
</svg>
</body>
</html>
'''
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' name = 'EPUB Output'
author = 'Kovid Goyal' author = 'Kovid Goyal'
@ -92,51 +239,21 @@ class EPUBOutput(OutputFormatPlugin):
'as a blank page.') '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)]) recommendations = set([('pretty_print', True, OptionRecommendation.HIGH)])
NONSVG_TITLEPAGE_COVER = '''\
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="calibre:cover" content="true" />
<title>Cover</title>
<style type="text/css" title="override_css">
@page {padding: 0pt; margin:0pt}
body { text-align: center; padding:0pt; margin: 0pt; }
div { padding:0pt; margin: 0pt; }
</style>
</head>
<body>
<div>
<img src="%s" alt="cover" style="height: 100%%" />
</div>
</body>
</html>
'''
TITLEPAGE_COVER = '''\
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="calibre:cover" content="true" />
<title>Cover</title>
<style type="text/css" title="override_css">
@page {padding: 0pt; margin:0pt}
body { text-align: center; padding:0pt; margin: 0pt; }
</style>
</head>
<body>
<svg version="1.1" xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink"
width="100%%" height="100%%" viewBox="0 0 600 800"
preserveAspectRatio="xMidYMid meet">
<image width="600" height="800" xlink:href="%s"/>
</svg>
</body>
</html>
'''
def workaround_webkit_quirks(self): def workaround_webkit_quirks(self):
from calibre.ebooks.oeb.base import XPath from calibre.ebooks.oeb.base import XPath
@ -259,97 +376,6 @@ class EPUBOutput(OutputFormatPlugin):
ans += '\n</encryption>' ans += '\n</encryption>'
return ans 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): def condense_ncx(self, ncx_path):
if not self.opts.pretty_print: if not self.opts.pretty_print:
tree = etree.parse(ncx_path) tree = etree.parse(ncx_path)

View File

@ -15,11 +15,39 @@ from calibre.customize.conversion import OutputFormatPlugin, \
OptionRecommendation OptionRecommendation
from calibre.ebooks.metadata.opf2 import OPF from calibre.ebooks.metadata.opf2 import OPF
from calibre.ptempfile import TemporaryDirectory 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, \ from calibre.ebooks.pdf.pageoptions import UNITS, PAPER_SIZES, \
ORIENTATIONS 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 = '''\
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<meta name="calibre:cover" content="true" />
<title>Cover</title>
<style type="text/css" title="override_css">
@page {padding: 0pt; margin:0pt}
body { text-align: center; padding:0pt; margin: 0pt; }
div { padding:0pt; margin: 0pt; }
</style>
</head>
<body>
<div>
<img src="%%s" alt="cover" width="%d" height="%d" />
</div>
</body>
</html>
'''%(int(width*factor), int(height*factor)-5)
class PDFOutput(OutputFormatPlugin, CoverManagerPDF):
name = 'PDF Output' name = 'PDF Output'
author = 'John Schember' author = 'John Schember'
@ -47,6 +75,7 @@ class PDFOutput(OutputFormatPlugin):
]) ])
def convert(self, oeb_book, output_path, input_plugin, opts, log): 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.input_plugin, self.opts, self.log = input_plugin, opts, log
self.output_path = output_path self.output_path = output_path
self.metadata = oeb_book.metadata self.metadata = oeb_book.metadata
@ -63,6 +92,10 @@ class PDFOutput(OutputFormatPlugin):
def convert_text(self, oeb_book): def convert_text(self, oeb_book):
self.log.debug('Serializing oeb input to disk for processing...') 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: with TemporaryDirectory('_pdf_out') as oeb_dir:
from calibre.customize.ui import plugin_for_output_format from calibre.customize.ui import plugin_for_output_format
oeb_output = plugin_for_output_format('oeb') oeb_output = plugin_for_output_format('oeb')

View File

@ -18,11 +18,70 @@ from calibre.ebooks.metadata import authors_to_string
from PyQt4 import QtCore from PyQt4 import QtCore
from PyQt4.Qt import QUrl, QEventLoop, SIGNAL, QObject, \ from PyQt4.Qt import QUrl, QEventLoop, SIGNAL, QObject, \
QPrinter, QMetaObject, QSizeF, Qt QPrinter, QMetaObject, QSizeF, Qt, QPainter
from PyQt4.QtWebKit import QWebView from PyQt4.QtWebKit import QWebView
from pyPdf import PdfFileWriter, PdfFileReader 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): class PDFMetadata(object):
def __init__(self, oeb_metadata=None): def __init__(self, oeb_metadata=None):
self.title = _('Unknown') self.title = _('Unknown')
@ -36,6 +95,7 @@ class PDFMetadata(object):
class PDFWriter(QObject): class PDFWriter(QObject):
def __init__(self, opts, log): def __init__(self, opts, log):
from calibre.gui2 import is_ok_to_use_qt from calibre.gui2 import is_ok_to_use_qt
if not is_ok_to_use_qt(): if not is_ok_to_use_qt():
@ -46,25 +106,15 @@ class PDFWriter(QObject):
self.loop = QEventLoop() self.loop = QEventLoop()
self.view = QWebView() self.view = QWebView()
self.view.setRenderHints(QPainter.Antialiasing|QPainter.TextAntialiasing|QPainter.SmoothPixmapTransform)
self.connect(self.view, SIGNAL('loadFinished(bool)'), self._render_html) self.connect(self.view, SIGNAL('loadFinished(bool)'), self._render_html)
self.render_queue = [] self.render_queue = []
self.combine_queue = [] self.combine_queue = []
self.tmp_path = PersistentTemporaryDirectory('_pdf_output_parts') 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.opts = opts
self.size = self._size() self.size = get_pdf_page_size(opts)
def dump(self, items, out_stream, pdf_metadata): def dump(self, items, out_stream, pdf_metadata):
self.metadata = pdf_metadata self.metadata = pdf_metadata
@ -77,27 +127,6 @@ class PDFWriter(QObject):
QMetaObject.invokeMethod(self, "_render_book", Qt.QueuedConnection) QMetaObject.invokeMethod(self, "_render_book", Qt.QueuedConnection)
self.loop.exec_() 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()') @QtCore.pyqtSignature('_render_book()')
def _render_book(self): def _render_book(self):
@ -151,6 +180,10 @@ class PDFWriter(QObject):
class ImagePDFWriter(PDFWriter): class ImagePDFWriter(PDFWriter):
def __init__(self, opts, log):
PDFWriter.__init__(self, opts, log)
self.size = get_imagepdf_page_size(opts)
def _render_next(self): def _render_next(self):
item = str(self.render_queue.pop(0)) item = str(self.render_queue.pop(0))
self.combine_queue.append(os.path.join(self.tmp_path, '%i.pdf' % (len(self.combine_queue) + 1))) 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) 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

View File

@ -18,8 +18,11 @@ class PluginWidget(Widget, Ui_Form):
def __init__(self, parent, get_option, get_help, db=None, book_id=None): def __init__(self, parent, get_option, get_help, db=None, book_id=None):
Widget.__init__(self, parent, 'epub_output', Widget.__init__(self, parent, 'epub_output',
['dont_split_on_page_breaks', 'flow_size', ['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.db, self.book_id = db, book_id
self.initialize_options(get_option, get_help, db, book_id) self.initialize_options(get_option, get_help, db, book_id)

View File

@ -14,13 +14,34 @@
<string>Form</string> <string>Form</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="0" column="0" colspan="2"> <item row="0" column="0">
<widget class="QCheckBox" name="opt_dont_split_on_page_breaks"> <widget class="QCheckBox" name="opt_dont_split_on_page_breaks">
<property name="text"> <property name="text">
<string>Do not &amp;split on page breaks</string> <string>Do not &amp;split on page breaks</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0">
<widget class="QCheckBox" name="opt_no_default_epub_cover">
<property name="text">
<string>No default &amp;cover</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="opt_no_svg_cover">
<property name="text">
<string>No &amp;SVG cover</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QCheckBox" name="opt_preserve_cover_aspect_ratio">
<property name="text">
<string>Preserve cover &amp;aspect ratio</string>
</property>
</widget>
</item>
<item row="3" column="0"> <item row="3" column="0">
<widget class="QLabel" name="label"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
@ -60,22 +81,25 @@
</property> </property>
</spacer> </spacer>
</item> </item>
<item row="1" column="0">
<widget class="QCheckBox" name="opt_no_default_epub_cover">
<property name="text">
<string>No default &amp;cover</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QCheckBox" name="opt_no_svg_cover">
<property name="text">
<string>No &amp;SVG cover</string>
</property>
</widget>
</item>
</layout> </layout>
</widget> </widget>
<resources/> <resources/>
<connections/> <connections>
<connection>
<sender>opt_no_svg_cover</sender>
<signal>toggled(bool)</signal>
<receiver>opt_preserve_cover_aspect_ratio</receiver>
<slot>setDisabled(bool)</slot>
<hints>
<hint type="sourcelabel">
<x>81</x>
<y>73</y>
</hint>
<hint type="destinationlabel">
<x>237</x>
<y>68</y>
</hint>
</hints>
</connection>
</connections>
</ui> </ui>

View File

@ -145,7 +145,7 @@ First perform the following steps in |app|
For an iPad: For an iPad:
Install the ReadMe app on your iPad using iTunes. Open Safari and browse to:: Install the ReadMe app on your iPad using iTunes. Open the Readme builtin browser and browse to::
http://192.168.1.2:8080/ http://192.168.1.2:8080/

View File

@ -49,6 +49,17 @@ class Article(object):
self.date = published self.date = published
self.utctime = dt_factory(self.date, assume_utc=True, as_utc=True) self.utctime = dt_factory(self.date, assume_utc=True, as_utc=True)
self.localtime = self.utctime.astimezone(local_tz) self.localtime = self.utctime.astimezone(local_tz)
self._formatted_date = None
@dynamic_property
def formatted_date(self):
def fget(self):
if self._formatted_date is None:
self._formatted_date = self.localtime.strftime(" [%a, %d %b %H:%M]")
return self._formatted_date
def fset(self, val):
self._formatted_date = val
return property(fget=fget, fset=fset)
@dynamic_property @dynamic_property
def title(self): def title(self):
@ -150,6 +161,8 @@ class Feed(object):
self.articles.append(article) self.articles.append(article)
else: else:
self.logger.debug('Skipping article %s (%s) from feed %s as it is too old.'%(title, article.localtime.strftime('%a, %d %b, %Y %H:%M'), self.title)) self.logger.debug('Skipping article %s (%s) from feed %s as it is too old.'%(title, article.localtime.strftime('%a, %d %b, %Y %H:%M'), self.title))
d = item.get('date', '')
article.formatted_date = d
def parse_article(self, item): def parse_article(self, item):

View File

@ -1179,6 +1179,8 @@ class BasicNewsRecipe(Recipe):
body.insert(len(body.contents), elem) body.insert(len(body.contents), elem)
with open(last, 'wb') as fi: with open(last, 'wb') as fi:
fi.write(unicode(soup).encode('utf-8')) fi.write(unicode(soup).encode('utf-8'))
if len(feeds) == 0:
raise Exception('All feeds are empty, aborting.')
if len(feeds) > 1: if len(feeds) > 1:
for i, f in enumerate(feeds): for i, f in enumerate(feeds):

View File

@ -160,7 +160,7 @@ class FeedTemplate(Template):
<li id="${'article_%d'%i}" py:if="getattr(article, 'downloaded', <li id="${'article_%d'%i}" py:if="getattr(article, 'downloaded',
False)" style="padding-bottom:0.5em" class="calibre_rescale_100"> False)" style="padding-bottom:0.5em" class="calibre_rescale_100">
<a class="article calibre_rescale_120" href="${article.url}">${article.title}</a> <a class="article calibre_rescale_120" href="${article.url}">${article.title}</a>
<span class="article_date">${article.localtime.strftime(" [%a, %d %b %H:%M]")}</span> <span class="article_date">${article.formatted_date}</span>
<div class="article_description calibre_rescale_70" py:if="article.summary"> <div class="article_description calibre_rescale_70" py:if="article.summary">
${Markup(cutoff(article.text_summary))} ${Markup(cutoff(article.text_summary))}
</div> </div>