mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Pull from trunk
This commit is contained in:
commit
bda0779201
@ -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"
|
||||||
|
|
||||||
|
@ -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/')
|
||||||
|
@ -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')
|
||||||
|
@ -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')
|
||||||
|
86
resources/recipes/tagesspiegel.recipe
Normal file
86
resources/recipes/tagesspiegel.recipe
Normal 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
|
||||||
|
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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')
|
||||||
|
@ -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
|
|
||||||
|
|
||||||
|
@ -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)
|
||||||
|
|
||||||
|
@ -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 &split on page breaks</string>
|
<string>Do not &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 &cover</string>
|
||||||
|
</property>
|
||||||
|
</widget>
|
||||||
|
</item>
|
||||||
|
<item row="2" column="0">
|
||||||
|
<widget class="QCheckBox" name="opt_no_svg_cover">
|
||||||
|
<property name="text">
|
||||||
|
<string>No &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 &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 &cover</string>
|
|
||||||
</property>
|
|
||||||
</widget>
|
|
||||||
</item>
|
|
||||||
<item row="2" column="0">
|
|
||||||
<widget class="QCheckBox" name="opt_no_svg_cover">
|
|
||||||
<property name="text">
|
|
||||||
<string>No &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>
|
||||||
|
@ -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/
|
||||||
|
|
||||||
|
@ -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):
|
||||||
|
@ -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):
|
||||||
|
@ -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>
|
||||||
|
Loading…
x
Reference in New Issue
Block a user