mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
0.9.12
This commit is contained in:
commit
94aaddf772
@ -19,6 +19,46 @@
|
||||
# new recipes:
|
||||
# - title:
|
||||
|
||||
- version: 0.9.12
|
||||
date: 2012-12-28
|
||||
|
||||
new features:
|
||||
- title: "Drivers for Kibano e-reader and Slick ER-700-2"
|
||||
tickets: [1093570, 1093732]
|
||||
|
||||
- title: "Add support for downloading metadata from Amazon Brazil."
|
||||
tickets: [1092594]
|
||||
|
||||
- title: "Copy to library: Allow specifying the destination library by path."
|
||||
tickets: [1093231]
|
||||
|
||||
- title: "When adding empty books, allow setting of the series for the new books. Also select the newly added book records after adding."
|
||||
|
||||
- title: "PDF Output: Add a checkbox to override the page size defined by the output profile. This allows you to specify a custom page size even if the output profile is not set to default."
|
||||
|
||||
- title: "Add usb ids for newer kindle fire to the linux mtp driver"
|
||||
|
||||
bug fixes:
|
||||
- title: "Linux: Temporarily redirect stdout to get rid of the annoying and pointless message about mtpz during libmtp initialization"
|
||||
|
||||
- title: "Fix multiple 'All column' coloring rules not being applied"
|
||||
tickets: [1093574]
|
||||
|
||||
- title: "Use custom icons in the content server as well."
|
||||
tickets: [1092098]
|
||||
|
||||
improved recipes:
|
||||
- La Voce
|
||||
- Harpers Magazine (printed edition)
|
||||
- Pajamas Media
|
||||
- NSFW corp
|
||||
- The Hindu
|
||||
- Nikkei News
|
||||
|
||||
new recipes:
|
||||
- title: Various Ukranian news sources
|
||||
author: rpalyvoda
|
||||
|
||||
- version: 0.9.11
|
||||
date: 2012-12-21
|
||||
|
||||
@ -34,6 +74,7 @@
|
||||
- title: "Allow setting the color of all columns with a single rule in Preferences->Look & Feel->Column Coloring"
|
||||
|
||||
- title: "MOBI: When reading metadata from mobi files, put the contents of the ASIN field into an identifier named mobi-asin. Note that this value is not used when downloading metadata as it is not possible to know which (country specific) amazon website the ASIN comes from."
|
||||
tickets: [1090394]
|
||||
|
||||
bug fixes:
|
||||
- title: "Windows build: Fix a regression in 0.9.9 that caused calibre to not start on some windows system that were missing the VC.90 dlls (some older XP systems)"
|
||||
|
@ -49,7 +49,7 @@ All the |app| python code is in the ``calibre`` package. This package contains t
|
||||
* Metadata reading, writing, and downloading is all in ebooks.metadata
|
||||
* Conversion happens in a pipeline, for the structure of the pipeline,
|
||||
see :ref:`conversion-introduction`. The pipeline consists of an input
|
||||
plugin, various transforms and an output plugin. The code constructs
|
||||
plugin, various transforms and an output plugin. The that code constructs
|
||||
and drives the pipeline is in plumber.py. The pipeline works on a
|
||||
representation of an ebook that is like an unzipped epub, with
|
||||
manifest, spine, toc, guide, html content, etc. The
|
||||
@ -74,10 +74,6 @@ After installing Bazaar, you can get the |app| source code with the command::
|
||||
|
||||
On Windows you will need the complete path name, that will be something like :file:`C:\\Program Files\\Bazaar\\bzr.exe`.
|
||||
|
||||
To update a branch to the latest code, use the command::
|
||||
|
||||
bzr merge
|
||||
|
||||
|app| is a very large project with a very long source control history, so the
|
||||
above can take a while (10mins to an hour depending on your internet speed).
|
||||
|
||||
@ -88,6 +84,11 @@ using::
|
||||
|
||||
bzr branch --stacked lp:calibre
|
||||
|
||||
|
||||
To update a branch to the latest code, use the command::
|
||||
|
||||
bzr merge
|
||||
|
||||
Submitting your changes to be included
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
|
@ -1,18 +1,22 @@
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2008-2010, Darko Miletic <darko.miletic at gmail.com>'
|
||||
__copyright__ = '2008-2012, Darko Miletic <darko.miletic at gmail.com>'
|
||||
'''
|
||||
harpers.org - paid subscription/ printed issue articles
|
||||
This recipe only get's article's published in text format
|
||||
images and pdf's are ignored
|
||||
If you have institutional subscription based on access IP you do not need to enter
|
||||
anything in username/password fields
|
||||
'''
|
||||
|
||||
import time
|
||||
import urllib
|
||||
from calibre import strftime
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class Harpers_full(BasicNewsRecipe):
|
||||
title = "Harper's Magazine - articles from printed edition"
|
||||
__author__ = 'Darko Miletic'
|
||||
description = "Harper's Magazine: Founded June 1850."
|
||||
description = "Harper's Magazine, the oldest general-interest monthly in America, explores the issues that drive our national conversation, through long-form narrative journalism and essays, and such celebrated features as the iconic Harper's Index."
|
||||
publisher = "Harpers's"
|
||||
category = 'news, politics, USA'
|
||||
oldest_article = 30
|
||||
@ -21,52 +25,69 @@ class Harpers_full(BasicNewsRecipe):
|
||||
use_embedded_content = False
|
||||
delay = 1
|
||||
language = 'en'
|
||||
needs_subscription = True
|
||||
masthead_url = 'http://www.harpers.org/media/image/Harpers_305x100.gif'
|
||||
publication_type = 'magazine'
|
||||
INDEX = strftime('http://www.harpers.org/archive/%Y/%m')
|
||||
LOGIN = 'http://www.harpers.org'
|
||||
cover_url = strftime('http://www.harpers.org/media/pages/%Y/%m/gif/0001.gif')
|
||||
extra_css = ' body{font-family: "Georgia",serif} '
|
||||
encoding = 'utf8'
|
||||
needs_subscription = 'optional'
|
||||
masthead_url = 'http://harpers.org/wp-content/themes/harpers/images/pheader.gif'
|
||||
publication_type = 'magazine'
|
||||
INDEX = strftime('http://harpers.org/archive/%Y/%m')
|
||||
LOGIN = 'http://harpers.org/wp-content/themes/harpers/ajax_login.php'
|
||||
extra_css = """
|
||||
body{font-family: adobe-caslon-pro,serif}
|
||||
.category{font-size: small}
|
||||
.articlePost p:first-letter{display: inline; font-size: xx-large; font-weight: bold}
|
||||
"""
|
||||
|
||||
conversion_options = {
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher' : publisher
|
||||
, 'language' : language
|
||||
'comment' : description
|
||||
, 'tags' : category
|
||||
, 'publisher' : publisher
|
||||
, 'language' : language
|
||||
}
|
||||
|
||||
keep_only_tags = [ dict(name='div', attrs={'id':'cached'}) ]
|
||||
keep_only_tags = [ dict(name='div', attrs={'class':['postdetailFull','articlePost']}) ]
|
||||
remove_tags = [
|
||||
dict(name='table', attrs={'class':['rcnt','rcnt topline']})
|
||||
,dict(name='link')
|
||||
dict(name='div', attrs={'class':'fRight rightDivPad'})
|
||||
,dict(name=['link','meta','object','embed','iframe'])
|
||||
]
|
||||
remove_attributes=['xmlns']
|
||||
remove_attributes=['xmlns']
|
||||
|
||||
def get_browser(self):
|
||||
br = BasicNewsRecipe.get_browser()
|
||||
br.open('http://harpers.org/')
|
||||
if self.username is not None and self.password is not None:
|
||||
br.open(self.LOGIN)
|
||||
br.select_form(nr=1)
|
||||
br['handle' ] = self.username
|
||||
br['password'] = self.password
|
||||
br.submit()
|
||||
tt = time.localtime()*1000
|
||||
data = urllib.urlencode({ 'm':self.username
|
||||
,'p':self.password
|
||||
,'rt':'http://harpers.org/'
|
||||
,'tt':tt
|
||||
})
|
||||
br.open(self.LOGIN, data)
|
||||
return br
|
||||
|
||||
def parse_index(self):
|
||||
articles = []
|
||||
print 'Processing ' + self.INDEX
|
||||
soup = self.index_to_soup(self.INDEX)
|
||||
for item in soup.findAll('div', attrs={'class':'title'}):
|
||||
text_link = item.parent.find('img',attrs={'alt':'Text'})
|
||||
if text_link:
|
||||
url = self.LOGIN + item.a['href']
|
||||
title = item.a.contents[0]
|
||||
date = strftime(' %B %Y')
|
||||
articles.append({
|
||||
'title' :title
|
||||
,'date' :date
|
||||
,'url' :url
|
||||
,'description':''
|
||||
})
|
||||
count = 0
|
||||
for item in soup.findAll('div', attrs={'class':'articleData'}):
|
||||
text_links = item.findAll('h2')
|
||||
for text_link in text_links:
|
||||
if count == 0:
|
||||
lcover_url = item.find(attrs={'class':'dwpdf'})
|
||||
if lcover_url:
|
||||
self.cover_url = lcover_url.a['href']
|
||||
count = 1
|
||||
else:
|
||||
url = text_link.a['href']
|
||||
title = text_link.a.contents[0]
|
||||
date = strftime(' %B %Y')
|
||||
articles.append({
|
||||
'title' :title
|
||||
,'date' :date
|
||||
,'url' :url
|
||||
,'description':''
|
||||
})
|
||||
return [(soup.head.title.string, articles)]
|
||||
|
||||
def print_version(self, url):
|
||||
return url + '?single=1'
|
||||
|
@ -16,10 +16,14 @@ class TheHindu(BasicNewsRecipe):
|
||||
|
||||
keep_only_tags = [dict(id='content')]
|
||||
remove_tags = [dict(attrs={'class':['article-links', 'breadcr']}),
|
||||
dict(id=['email-section', 'right-column', 'printfooter'])]
|
||||
dict(id=['email-section', 'right-column', 'printfooter', 'topover',
|
||||
'slidebox', 'th_footer'])]
|
||||
|
||||
extra_css = '.photo-caption { font-size: smaller }'
|
||||
|
||||
def preprocess_raw_html(self, raw, url):
|
||||
return raw.replace('<body><p>', '<p>').replace('</p></body>', '</p>')
|
||||
|
||||
def postprocess_html(self, soup, first_fetch):
|
||||
for t in soup.findAll(['table', 'tr', 'td','center']):
|
||||
t.name = 'div'
|
||||
|
@ -2,7 +2,7 @@
|
||||
__license__ = 'GPL v3'
|
||||
__author__ = 'Gabriele Marini, based on Darko Miletic'
|
||||
__copyright__ = '2009, Darko Miletic <darko.miletic at gmail.com>'
|
||||
__description__ = 'La Stampa 05/05/2010'
|
||||
__description__ = 'La Stampa 28/12/2012'
|
||||
|
||||
'''
|
||||
http://www.lastampa.it/
|
||||
@ -14,10 +14,11 @@ class LaStampa(BasicNewsRecipe):
|
||||
title = u'La Stampa'
|
||||
language = 'it'
|
||||
__author__ = 'Gabriele Marini'
|
||||
oldest_article = 15
|
||||
#oldest_article = 15
|
||||
oldest_articlce = 7 #for daily schedule
|
||||
max_articles_per_feed = 50
|
||||
recursion = 100
|
||||
cover_url = 'http://www.lastampa.it/edicola/PDF/1.pdf'
|
||||
cover_url = 'http://www1.lastampa.it/edicola/PDF/1.pdf'
|
||||
use_embedded_content = False
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
@ -33,35 +34,41 @@ class LaStampa(BasicNewsRecipe):
|
||||
if link:
|
||||
return link[0]['href']
|
||||
|
||||
keep_only_tags = [dict(attrs={'class':['boxocchiello2','titoloRub','titologir','catenaccio','sezione','articologirata']}),
|
||||
keep_only_tags = [dict(attrs={'class':['boxocchiello2','titoloRub','titologir','autore-girata','luogo-girata','catenaccio','sezione','articologirata','bodytext','news-single-img','ls-articoloCorpo','ls-blog-list-1col']}),
|
||||
dict(name='div', attrs={'id':'corpoarticolo'})
|
||||
]
|
||||
|
||||
remove_tags = [dict(name='div', attrs={'id':'menutop'}),
|
||||
dict(name='div', attrs={'id':'fwnetblocco'}),
|
||||
dict(name='table', attrs={'id':'strumenti'}),
|
||||
dict(name='table', attrs={'id':'imgesterna'}),
|
||||
dict(name='a', attrs={'class':'linkblu'}),
|
||||
dict(name='a', attrs={'class':'link'}),
|
||||
|
||||
remove_tags = [dict(name='div', attrs={'id':['menutop','fwnetblocco']}),
|
||||
dict(attrs={'class':['ls-toolbarCommenti','ls-boxCommentsBlog']}),
|
||||
dict(name='table', attrs={'id':['strumenti','imgesterna']}),
|
||||
dict(name='a', attrs={'class':['linkblu','link']}),
|
||||
dict(name='span', attrs={'class':['boxocchiello','boxocchiello2','sezione']})
|
||||
]
|
||||
|
||||
feeds = [
|
||||
(u'Home', u'http://www.lastampa.it/redazione/rss_home.xml'),
|
||||
(u'Editoriali', u'http://www.lastampa.it/cmstp/rubriche/oggetti/rss.asp?ID_blog=25'),
|
||||
(u'Politica', u'http://www.lastampa.it/redazione/cmssezioni/politica/rss_politica.xml'),
|
||||
(u'ArciItaliana', u'http://www.lastampa.it/cmstp/rubriche/oggetti/rss.asp?ID_blog=14'),
|
||||
(u'Cronache', u'http://www.lastampa.it/redazione/cmssezioni/cronache/rss_cronache.xml'),
|
||||
(u'Esteri', u'http://www.lastampa.it/redazione/cmssezioni/esteri/rss_esteri.xml'),
|
||||
(u'Danni Collaterali', u'http://www.lastampa.it/cmstp/rubriche/oggetti/rss.asp?ID_blog=90'),
|
||||
(u'Economia', u'http://www.lastampa.it/redazione/cmssezioni/economia/rss_economia.xml'),
|
||||
(u'Tecnologia ', u'http://www.lastampa.it/cmstp/rubriche/oggetti/rss.asp?ID_blog=30'),
|
||||
(u'Spettacoli', u'http://www.lastampa.it/redazione/cmssezioni/spettacoli/rss_spettacoli.xml'),
|
||||
(u'Sport', u'http://www.lastampa.it/sport/rss_home.xml'),
|
||||
(u'Torino', u'http://rss.feedsportal.com/c/32418/f/466938/index.rss'),
|
||||
(u'Motori', u'http://www.lastampa.it/cmstp/rubriche/oggetti/rss.asp?ID_blog=57'),
|
||||
(u'Scienza', u'http://www.lastampa.it/cmstp/rubriche/oggetti/rss.asp?ID_blog=38'),
|
||||
(u'Fotografia', u'http://rss.feedsportal.com/c/32418/f/478449/index.rss'),
|
||||
(u'Scuola', u'http://www.lastampa.it/cmstp/rubriche/oggetti/rss.asp?ID_blog=60'),
|
||||
(u'Tempo Libero', u'http://www.lastampa.it/tempolibero/rss_home.xml')
|
||||
feeds = [(u'BuonGiorno',u'http://www.lastampa.it/cultura/opinioni/buongiorno/rss.xml'),
|
||||
(u'Jena', u'http://www.lastampa.it/cultura/opinioni/jena/rss.xml'),
|
||||
(u'Editoriali', u'http://www.lastampa.it/cultura/opinioni/editoriali'),
|
||||
(u'Finestra sull America', u'http://lastampa.feedsportal.com/c/32418/f/625713/index.rss'),
|
||||
(u'HomePage', u'http://www.lastampa.it/rss.xml'),
|
||||
(u'Politica Italia', u'http://www.lastampa.it/italia/politica/rss.xml'),
|
||||
(u'ArciItaliana', u'http://www.lastampa.it/rss/blog/arcitaliana'),
|
||||
(u'Cronache', u'http://www.lastampa.it/italia/cronache/rss.xml'),
|
||||
(u'Esteri', u'http://www.lastampa.it/esteri/rss.xml'),
|
||||
(u'Danni Collaterali', u'http://www.lastampa.it/rss/blog/danni-collaterali'),
|
||||
(u'Economia', u'http://www.lastampa.it/economia/rss.xml'),
|
||||
(u'Tecnologia ', u'http://www.lastampa.it/tecnologia/rss.xml'),
|
||||
(u'Spettacoli', u'http://www.lastampa.it/spettacoli/rss.xml'),
|
||||
(u'Sport', u'http://www.lastampa.it/sport/rss.xml'),
|
||||
(u'Torino', u'http://www.lastampa.it/cronaca/rss.xml'),
|
||||
(u'Motori', u'http://www.lastampa.it/motori/rss.xml'),
|
||||
(u'Scienza', u'http://www.lastampa.it/scienza/rss.xml'),
|
||||
(u'Cultura', u'http://www.lastampa.it/cultura/rss.xml'),
|
||||
(u'Scuola', u'http://www.lastampa.it/cultura/scuola/rss.xml'),
|
||||
(u'Benessere', u'http://www.lastampa.it/scienza/benessere/rss.xml'),
|
||||
(u'Cucina', u'http://www.lastampa.it/societa/cucina/rss.xml'),
|
||||
(u'Casa', u'http://www.lastampa.it/societa/casa/rss.xml'),
|
||||
(u'Moda',u'http://www.lastampa.it/societa/moda/rss.xml'),
|
||||
(u'Giochi',u'http://www.lastampa.it/tecnologia/giochi/rss.xml'),
|
||||
(u'Viaggi',u'http://www.lastampa.it/societa/viaggi/rss.xml'),
|
||||
(u'Ambiente', u'http://www.lastampa.it/scienza/ambiente/rss.xml')
|
||||
]
|
||||
|
@ -7,9 +7,9 @@ class AdvancedUserRecipe1324114228(BasicNewsRecipe):
|
||||
max_articles_per_feed = 100
|
||||
auto_cleanup = True
|
||||
masthead_url = 'http://www.lavoce.info/binary/la_voce/testata/lavoce.1184661635.gif'
|
||||
feeds = [(u'La Voce', u'http://www.lavoce.info/feed_rss.php?id_feed=1')]
|
||||
feeds = [(u'La Voce', u'http://www.lavoce.info/feed/')]
|
||||
__author__ = 'faber1971'
|
||||
description = 'Italian website on Economy - v1.01 (17, December 2011)'
|
||||
description = 'Italian website on Economy - v1.02 (27, December 2012)'
|
||||
language = 'it'
|
||||
|
||||
|
||||
|
12
recipes/lvivs_ks_ghazieta.recipe
Normal file
12
recipes/lvivs_ks_ghazieta.recipe
Normal file
@ -0,0 +1,12 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class AdvancedUserRecipe1356270446(BasicNewsRecipe):
|
||||
title = u'\u041b\u044c\u0432\u0456\u0432\u0441\u044c\u043a\u0430 \u0433\u0430\u0437\u0435\u0442\u0430'
|
||||
__author__ = 'rpalyvoda'
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
language = 'uk'
|
||||
cover_url = 'http://lvivska.com/sites/all/themes/biblos/images/logo.png'
|
||||
masthead_url = 'http://lvivska.com/sites/all/themes/biblos/images/logo.png'
|
||||
auto_cleanup = True
|
||||
feeds = [(u'\u041d\u043e\u0432\u0438\u043d\u0438', u'http://lvivska.com/rss/news.xml'), (u'\u041f\u043e\u043b\u0456\u0442\u0438\u043a\u0430', u'http://lvivska.com/rss/politic.xml'), (u'\u0415\u043a\u043e\u043d\u043e\u043c\u0456\u043a\u0430', u'http://lvivska.com/rss/economic.xml'), (u'\u041f\u0440\u0430\u0432\u043e', u'http://lvivska.com/rss/law.xml'), (u'\u0421\u0432\u0456\u0442', u'http://lvivska.com/rss/world.xml'), (u'\u0416\u0438\u0442\u0442\u044f', u'http://lvivska.com/rss/life.xml'), (u'\u041a\u0443\u043b\u044c\u0442\u0443\u0440\u0430', u'http://lvivska.com/rss/culture.xml'), (u'\u041b\u0430\u0441\u0443\u043d', u'http://lvivska.com/rss/cooking.xml'), (u'\u0421\u0442\u0438\u043b\u044c', u'http://lvivska.com/rss/style.xml'), (u'Galicia Incognita', u'http://lvivska.com/rss/galiciaincognita.xml'), (u'\u0421\u043f\u043e\u0440\u0442', u'http://lvivska.com/rss/sport.xml'), (u'\u0415\u043a\u043e\u043b\u043e\u0433\u0456\u044f', u'http://lvivska.com/rss/ecology.xml'), (u"\u0417\u0434\u043e\u0440\u043e\u0432'\u044f", u'http://lvivska.com/rss/health.xml'), (u'\u0410\u0432\u0442\u043e', u'http://lvivska.com/rss/auto.xml'), (u'\u0411\u043b\u043e\u0433\u0438', u'http://lvivska.com/rss/blog.xml')]
|
@ -13,8 +13,11 @@ class NikkeiNet_paper_subscription(BasicNewsRecipe):
|
||||
max_articles_per_feed = 30
|
||||
language = 'ja'
|
||||
no_stylesheets = True
|
||||
cover_url = 'http://parts.nikkei.com/parts/ds/images/common/logo_r1.svg'
|
||||
masthead_url = 'http://parts.nikkei.com/parts/ds/images/common/logo_r1.svg'
|
||||
#cover_url = 'http://parts.nikkei.com/parts/ds/images/common/logo_r1.svg'
|
||||
cover_url = 'http://cdn.nikkei.co.jp/parts/ds/images/common/st_nikkei_r1_20101003_1.gif'
|
||||
#masthead_url = 'http://parts.nikkei.com/parts/ds/images/common/logo_r1.svg'
|
||||
masthead_url = 'http://cdn.nikkei.co.jp/parts/ds/images/common/st_nikkei_r1_20101003_1.gif'
|
||||
cover_margins = (10, 188, '#ffffff')
|
||||
|
||||
remove_tags_before = {'class':"cmn-indent"}
|
||||
remove_tags = [
|
||||
@ -40,8 +43,11 @@ class NikkeiNet_paper_subscription(BasicNewsRecipe):
|
||||
print "-------------------------open top page-------------------------------------"
|
||||
br.open('http://www.nikkei.com/')
|
||||
print "-------------------------open first login form-----------------------------"
|
||||
link = br.links(url_regex="www.nikkei.com/etc/accounts/login").next()
|
||||
br.follow_link(link)
|
||||
try:
|
||||
url = br.links(url_regex="www.nikkei.com/etc/accounts/login").next().url
|
||||
except StopIteration:
|
||||
url = 'http://www.nikkei.com/etc/accounts/login?dps=3&pageflag=top&url=http%3A%2F%2Fwww.nikkei.com%2F'
|
||||
br.open(url) #br.follow_link(link)
|
||||
#response = br.response()
|
||||
#print response.get_data()
|
||||
print "-------------------------JS redirect(send autoPostForm)--------------------"
|
||||
|
@ -6,7 +6,6 @@ www.nsfwcorp.com
|
||||
'''
|
||||
|
||||
import urllib
|
||||
from calibre import strftime
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class NotSafeForWork(BasicNewsRecipe):
|
||||
@ -21,8 +20,9 @@ class NotSafeForWork(BasicNewsRecipe):
|
||||
needs_subscription = True
|
||||
auto_cleanup = False
|
||||
INDEX = 'https://www.nsfwcorp.com'
|
||||
LOGIN = INDEX + '/login'
|
||||
use_embedded_content = False
|
||||
LOGIN = INDEX + '/login/target/'
|
||||
SETTINGS = INDEX + '/settings/'
|
||||
use_embedded_content = True
|
||||
language = 'en'
|
||||
publication_type = 'magazine'
|
||||
masthead_url = 'http://assets.nsfwcorp.com/media/headers/nsfw_banner.jpg'
|
||||
@ -46,15 +46,6 @@ class NotSafeForWork(BasicNewsRecipe):
|
||||
, 'language' : language
|
||||
}
|
||||
|
||||
remove_tags_before = dict(attrs={'id':'fromToLine'})
|
||||
remove_tags_after = dict(attrs={'id':'unlockButtonDiv'})
|
||||
remove_tags=[
|
||||
dict(name=['meta', 'link', 'iframe', 'embed', 'object'])
|
||||
,dict(name='a', attrs={'class':'switchToDeskNotes'})
|
||||
,dict(attrs={'id':'unlockButtonDiv'})
|
||||
]
|
||||
remove_attributes = ['lang']
|
||||
|
||||
def get_browser(self):
|
||||
br = BasicNewsRecipe.get_browser()
|
||||
br.open(self.LOGIN)
|
||||
@ -65,30 +56,12 @@ class NotSafeForWork(BasicNewsRecipe):
|
||||
br.open(self.LOGIN, data)
|
||||
return br
|
||||
|
||||
def parse_index(self):
|
||||
articles = []
|
||||
soup = self.index_to_soup(self.INDEX)
|
||||
dispatches = soup.find(attrs={'id':'dispatches'})
|
||||
if dispatches:
|
||||
for item in dispatches.findAll('h3'):
|
||||
description = u''
|
||||
title_link = item.find('span', attrs={'class':'dispatchTitle'})
|
||||
description_link = item.find('span', attrs={'class':'dispatchSubtitle'})
|
||||
feed_link = item.find('a', href=True)
|
||||
if feed_link:
|
||||
url = self.INDEX + feed_link['href']
|
||||
title = self.tag_to_string(title_link)
|
||||
description = self.tag_to_string(description_link)
|
||||
date = strftime(self.timefmt)
|
||||
articles.append({
|
||||
'title' :title
|
||||
,'date' :date
|
||||
,'url' :url
|
||||
,'description':description
|
||||
})
|
||||
return [('Dispatches', articles)]
|
||||
def get_feeds(self):
|
||||
self.feeds = []
|
||||
soup = self.index_to_soup(self.SETTINGS)
|
||||
for item in soup.findAll('input', attrs={'type':'text'}):
|
||||
if item.has_key('value') and item['value'].startswith('http://www.nsfwcorp.com/feed/'):
|
||||
self.feeds.append(item['value'])
|
||||
return self.feeds
|
||||
return self.feeds
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for item in soup.findAll(style=True):
|
||||
del item['style']
|
||||
return soup
|
||||
|
@ -1,27 +1,27 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
from calibre.ebooks.BeautifulSoup import BeautifulSoup
|
||||
|
||||
class PajamasMedia(BasicNewsRecipe):
|
||||
title = u'Pajamas Media'
|
||||
description = u'Provides exclusive news and opinion for forty countries.'
|
||||
language = 'en'
|
||||
__author__ = 'Krittika Goyal'
|
||||
oldest_article = 1 #days
|
||||
oldest_article = 2 #days
|
||||
max_articles_per_feed = 25
|
||||
recursions = 1
|
||||
match_regexps = [r'http://pajamasmedia.com/blog/.*/2/$']
|
||||
#encoding = 'latin1'
|
||||
|
||||
remove_stylesheets = True
|
||||
#remove_tags_before = dict(name='h1', attrs={'class':'heading'})
|
||||
remove_tags_after = dict(name='div', attrs={'class':'paged-nav'})
|
||||
remove_tags = [
|
||||
dict(name='iframe'),
|
||||
dict(name='div', attrs={'class':['pages']}),
|
||||
#dict(name='div', attrs={'id':['bookmark']}),
|
||||
#dict(name='span', attrs={'class':['related_link', 'slideshowcontrols']}),
|
||||
#dict(name='ul', attrs={'class':'articleTools'}),
|
||||
]
|
||||
auto_cleanup = True
|
||||
##remove_tags_before = dict(name='h1', attrs={'class':'heading'})
|
||||
#remove_tags_after = dict(name='div', attrs={'class':'paged-nav'})
|
||||
#remove_tags = [
|
||||
#dict(name='iframe'),
|
||||
#dict(name='div', attrs={'class':['pages']}),
|
||||
##dict(name='div', attrs={'id':['bookmark']}),
|
||||
##dict(name='span', attrs={'class':['related_link', 'slideshowcontrols']}),
|
||||
##dict(name='ul', attrs={'class':'articleTools'}),
|
||||
#]
|
||||
|
||||
feeds = [
|
||||
('pajamas Media',
|
||||
@ -29,20 +29,20 @@ class PajamasMedia(BasicNewsRecipe):
|
||||
|
||||
]
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
story = soup.find(name='div', attrs={'id':'innerpage-content'})
|
||||
#td = heading.findParent(name='td')
|
||||
#td.extract()
|
||||
#def preprocess_html(self, soup):
|
||||
#story = soup.find(name='div', attrs={'id':'innerpage-content'})
|
||||
##td = heading.findParent(name='td')
|
||||
##td.extract()
|
||||
|
||||
soup = BeautifulSoup('<html><head><title>t</title></head><body></body></html>')
|
||||
body = soup.find(name='body')
|
||||
body.insert(0, story)
|
||||
return soup
|
||||
#soup = BeautifulSoup('<html><head><title>t</title></head><body></body></html>')
|
||||
#body = soup.find(name='body')
|
||||
#body.insert(0, story)
|
||||
#return soup
|
||||
|
||||
def postprocess_html(self, soup, first):
|
||||
if not first:
|
||||
h = soup.find(attrs={'class':'innerpage-header'})
|
||||
if h: h.extract()
|
||||
auth = soup.find(attrs={'class':'author'})
|
||||
if auth: auth.extract()
|
||||
return soup
|
||||
#def postprocess_html(self, soup, first):
|
||||
#if not first:
|
||||
#h = soup.find(attrs={'class':'innerpage-header'})
|
||||
#if h: h.extract()
|
||||
#auth = soup.find(attrs={'class':'author'})
|
||||
#if auth: auth.extract()
|
||||
#return soup
|
||||
|
13
recipes/ukraiyns_kii_tizhdien.recipe
Normal file
13
recipes/ukraiyns_kii_tizhdien.recipe
Normal file
@ -0,0 +1,13 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class AdvancedUserRecipe1356283265(BasicNewsRecipe):
|
||||
title = u'\u0423\u043a\u0440\u0430\u0457\u043d\u0441\u044c\u043a\u0438\u0439 \u0422\u0438\u0436\u0434\u0435\u043d\u044c'
|
||||
__author__ = 'rpalyvoda'
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
language = 'uk'
|
||||
cover_url = 'http://tyzhden.ua/Images/Style1/tyzhden.ua-logo2.gif'
|
||||
masthead_url = 'http://tyzhden.ua/Images/Style1/tyzhden.ua-logo2.gif'
|
||||
auto_cleanup = True
|
||||
|
||||
feeds = [(u'\u041d\u043e\u0432\u0438\u043d\u0438', u'http://tyzhden.ua/RSS/News/'), (u'\u041e\u0440\u0438\u0433\u0456\u043d\u0430\u043b\u044c\u043d\u0456 \u043d\u043e\u0432\u0438\u043d\u0438', u'http://tyzhden.ua/RSS/News.Original/'), (u'\u041f\u0443\u0431\u043b\u0456\u043a\u0430\u0446\u0456\u0457', u'http://tyzhden.ua/RSS/Publications/')]
|
13
recipes/zaxid_net.recipe
Normal file
13
recipes/zaxid_net.recipe
Normal file
@ -0,0 +1,13 @@
|
||||
from calibre.web.feeds.news import BasicNewsRecipe
|
||||
|
||||
class AdvancedUserRecipe1356281741(BasicNewsRecipe):
|
||||
title = u'Zaxid.net'
|
||||
__author__ = 'rpalyvoda'
|
||||
oldest_article = 7
|
||||
max_articles_per_feed = 100
|
||||
language = 'uk'
|
||||
cover_url = 'http://upload.wikimedia.org/wikipedia/uk/b/bc/Zaxid-net.jpg'
|
||||
masthead_url = 'http://upload.wikimedia.org/wikipedia/uk/b/bc/Zaxid-net.jpg'
|
||||
auto_cleanup = True
|
||||
|
||||
feeds = [(u'\u0422\u043e\u043f \u043d\u043e\u0432\u0438\u043d\u0438', u'http://feeds.feedburner.com/zaxid/topNews'), (u'\u0421\u0442\u0440\u0456\u0447\u043a\u0430 \u043d\u043e\u0432\u0438\u043d', u'http://feeds.feedburner.com/zaxid/AllNews'), (u'\u041d\u043e\u0432\u0438\u043d\u0438 \u041b\u044c\u0432\u043e\u0432\u0430', u'http://feeds.feedburner.com/zaxid/Lviv'), (u'\u041d\u043e\u0432\u0438\u043d\u0438 \u0423\u043a\u0440\u0430\u0457\u043d\u0438', u'http://feeds.feedburner.com/zaxid/Ukraine'), (u'\u041d\u043e\u0432\u0438\u043d\u0438 \u0441\u0432\u0456\u0442\u0443', u'http://feeds.feedburner.com/zaxid/World'), (u'\u041d\u043e\u0432\u0438\u043d\u0438 - \u0420\u0430\u0434\u0456\u043e 24', u'\u0420\u0430\u0434\u0456\u043e 24'), (u'\u0411\u043b\u043e\u0433\u0438', u'http://feeds.feedburner.com/zaxid/Blogs'), (u"\u041f\u0443\u0431\u043b\u0456\u043a\u0430\u0446\u0456\u0457 - \u0406\u043d\u0442\u0435\u0440\u0432'\u044e", u'http://feeds.feedburner.com/zaxid/Interview'), (u'\u041f\u0443\u0431\u043b\u0456\u043a\u0430\u0446\u0456\u0457 - \u0421\u0442\u0430\u0442\u0442\u0456', u'http://feeds.feedburner.com/zaxid/Articles'), (u'\u0410\u0444\u0456\u0448\u0430', u'http://zaxid.net/rss/subcategory/140.xml'), (u'\u0413\u0430\u043b\u0438\u0447\u0438\u043d\u0430', u'http://feeds.feedburner.com/zaxid/Galicia'), (u'\u041a\u0443\u043b\u044c\u0442\u0443\u0440\u0430.NET', u'http://feeds.feedburner.com/zaxid/KulturaNET'), (u"\u043d\u0435\u0412\u0456\u0434\u043e\u043c\u0456 \u043b\u044c\u0432\u0456\u0432'\u044f\u043d\u0438", u'http://feeds.feedburner.com/zaxid/UnknownLviv'), (u'\u041b\u0435\u043e\u043f\u043e\u043b\u0456\u0441 MULTIPLEX', u'http://feeds.feedburner.com/zaxid/LeopolisMULTIPLEX'), (u'\u0411\u0438\u0442\u0432\u0430 \u0437\u0430 \u043c\u043e\u0432\u0443', u'http://zaxid.net/rss/subcategory/138.xml'), (u'\u0422\u0440\u0430\u043d\u0441\u043f\u043e\u0440\u0442\u043d\u0430 \u0441\u0445\u0435\u043c\u0430 \u041b\u044c\u0432\u043e\u0432\u0430', u'http://zaxid.net/rss/subcategory/132.xml'), (u'\u0414\u0435\u043c\u0456\u0444\u043e\u043b\u043e\u0433\u0456\u0437\u0430\u0446\u0456\u044f', u'http://zaxid.net/rss/subcategory/130.xml'), (u"\u041c\u0438 \u043f\u0430\u043c'\u044f\u0442\u0430\u0454\u043c\u043e", u'http://feeds.feedburner.com/zaxid/WeRemember'), (u'20 \u0440\u043e\u043a\u0456\u0432 \u041d\u0435\u0437\u0430\u043b\u0435\u0436\u043d\u043e\u0441\u0442\u0456', u'http://zaxid.net/rss/subcategory/129.xml'), (u'\u041f\u0440\u0430\u0432\u043e \u043d\u0430 \u0434\u0438\u0442\u0438\u043d\u0441\u0442\u0432\u043e', u'http://feeds.feedburner.com/zaxid/Childhood'), (u'\u0410\u043d\u043e\u043d\u0441\u0438', u'http://feeds.feedburner.com/zaxid/Announcements')]
|
Binary file not shown.
@ -81,6 +81,7 @@ body {
|
||||
background-color: #39a9cf;
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
border-radius: 5px;
|
||||
text-shadow: #27211b 1px 1px 1px;
|
||||
-moz-box-shadow: 5px 5px 5px #222;
|
||||
-webkit-box-shadow: 5px 5px 5px #222;
|
||||
|
@ -12,6 +12,7 @@ let g:syntastic_cpp_include_dirs = [
|
||||
\'/usr/include/fontconfig',
|
||||
\'src/qtcurve/common', 'src/qtcurve',
|
||||
\'src/unrar',
|
||||
\'src/qt-harfbuzz/src',
|
||||
\'/usr/include/ImageMagick',
|
||||
\]
|
||||
let g:syntastic_c_include_dirs = g:syntastic_cpp_include_dirs
|
||||
|
@ -6,12 +6,13 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2009, Kovid Goyal <kovid@kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os, socket, struct, subprocess, sys, glob
|
||||
import os, socket, struct, subprocess, glob
|
||||
from distutils.spawn import find_executable
|
||||
|
||||
from PyQt4 import pyqtconfig
|
||||
|
||||
from setup import isosx, iswindows, islinux, is64bit
|
||||
is64bit
|
||||
|
||||
OSX_SDK = '/Developer/SDKs/MacOSX10.5.sdk'
|
||||
|
||||
@ -81,6 +82,7 @@ def consolidate(envvar, default):
|
||||
pyqt = pyqtconfig.Configuration()
|
||||
|
||||
qt_inc = pyqt.qt_inc_dir
|
||||
qt_private_inc = []
|
||||
qt_lib = pyqt.qt_lib_dir
|
||||
ft_lib_dirs = []
|
||||
ft_libs = []
|
||||
@ -140,6 +142,8 @@ elif isosx:
|
||||
png_libs = ['png12']
|
||||
ft_libs = ['freetype']
|
||||
ft_inc_dirs = ['/sw/include/freetype2']
|
||||
bq = glob.glob('/sw/build/qt-*/include')[-1]
|
||||
qt_private_inc = ['%s/%s'%(bq, m) for m in ('QtGui', 'QtCore')]
|
||||
else:
|
||||
# Include directories
|
||||
png_inc_dirs = pkgconfig_include_dirs('libpng', 'PNG_INC_DIR',
|
||||
|
@ -102,7 +102,8 @@ class Check(Command):
|
||||
errors = True
|
||||
if errors:
|
||||
cPickle.dump(cache, open(self.CACHE, 'wb'), -1)
|
||||
subprocess.call(['gvim', '-f', f])
|
||||
subprocess.call(['gvim', '-S',
|
||||
self.j(self.SRC, '../session.vim'), '-f', f])
|
||||
raise SystemExit(1)
|
||||
cache[f] = mtime
|
||||
for x in builtins:
|
||||
|
@ -18,7 +18,7 @@ from setup.build_environment import (chmlib_inc_dirs,
|
||||
msvc, MT, win_inc, win_lib, win_ddk, magick_inc_dirs, magick_lib_dirs,
|
||||
magick_libs, chmlib_lib_dirs, sqlite_inc_dirs, icu_inc_dirs,
|
||||
icu_lib_dirs, win_ddk_lib_dirs, ft_libs, ft_lib_dirs, ft_inc_dirs,
|
||||
zlib_libs, zlib_lib_dirs, zlib_inc_dirs, is64bit)
|
||||
zlib_libs, zlib_lib_dirs, zlib_inc_dirs, is64bit, qt_private_inc)
|
||||
MT
|
||||
isunix = islinux or isosx or isbsd
|
||||
|
||||
@ -183,6 +183,13 @@ extensions = [
|
||||
sip_files = ['calibre/gui2/progress_indicator/QProgressIndicator.sip']
|
||||
),
|
||||
|
||||
Extension('qt_hack',
|
||||
['calibre/ebooks/pdf/render/qt_hack.cpp'],
|
||||
inc_dirs = qt_private_inc + ['calibre/ebooks/pdf/render', 'qt-harfbuzz/src'],
|
||||
headers = ['calibre/ebooks/pdf/render/qt_hack.h'],
|
||||
sip_files = ['calibre/ebooks/pdf/render/qt_hack.sip']
|
||||
),
|
||||
|
||||
Extension('unrar',
|
||||
['unrar/%s.cpp'%(x.partition('.')[0]) for x in '''
|
||||
rar.o strlist.o strfn.o pathfn.o savepos.o smallfn.o global.o file.o
|
||||
@ -545,6 +552,9 @@ class Build(Command):
|
||||
VERSION = 1.0.0
|
||||
CONFIG += %s
|
||||
''')%(ext.name, ' '.join(ext.headers), ' '.join(ext.sources), archs)
|
||||
if ext.inc_dirs:
|
||||
idir = ' '.join(ext.inc_dirs)
|
||||
pro += 'INCLUDEPATH = %s\n'%idir
|
||||
pro = pro.replace('\\', '\\\\')
|
||||
open(ext.name+'.pro', 'wb').write(pro)
|
||||
qmc = [QMAKE, '-o', 'Makefile']
|
||||
|
@ -12,14 +12,14 @@ msgstr ""
|
||||
"Report-Msgid-Bugs-To: Debian iso-codes team <pkg-isocodes-"
|
||||
"devel@lists.alioth.debian.org>\n"
|
||||
"POT-Creation-Date: 2011-11-25 14:01+0000\n"
|
||||
"PO-Revision-Date: 2012-12-16 13:27+0000\n"
|
||||
"PO-Revision-Date: 2012-12-22 17:18+0000\n"
|
||||
"Last-Translator: Ferran Rius <frius64@hotmail.com>\n"
|
||||
"Language-Team: Catalan <linux@softcatala.org>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-12-17 04:42+0000\n"
|
||||
"X-Generator: Launchpad (build 16372)\n"
|
||||
"X-Launchpad-Export-Date: 2012-12-23 04:38+0000\n"
|
||||
"X-Generator: Launchpad (build 16378)\n"
|
||||
"Language: ca\n"
|
||||
|
||||
#. name for aaa
|
||||
@ -19044,19 +19044,19 @@ msgstr "Nshi"
|
||||
|
||||
#. name for nsd
|
||||
msgid "Nisu; Southern"
|
||||
msgstr ""
|
||||
msgstr "Nisu; Meridional"
|
||||
|
||||
#. name for nse
|
||||
msgid "Nsenga"
|
||||
msgstr ""
|
||||
msgstr "Nsenga"
|
||||
|
||||
#. name for nsg
|
||||
msgid "Ngasa"
|
||||
msgstr ""
|
||||
msgstr "Ngasa"
|
||||
|
||||
#. name for nsh
|
||||
msgid "Ngoshie"
|
||||
msgstr ""
|
||||
msgstr "Ngishe"
|
||||
|
||||
#. name for nsi
|
||||
msgid "Nigerian Sign Language"
|
||||
@ -19064,7 +19064,7 @@ msgstr "Llenguatge de signes nigerià"
|
||||
|
||||
#. name for nsk
|
||||
msgid "Naskapi"
|
||||
msgstr ""
|
||||
msgstr "Naskapi"
|
||||
|
||||
#. name for nsl
|
||||
msgid "Norwegian Sign Language"
|
||||
@ -19076,7 +19076,7 @@ msgstr "Naga; Sumi"
|
||||
|
||||
#. name for nsn
|
||||
msgid "Nehan"
|
||||
msgstr ""
|
||||
msgstr "Nehan"
|
||||
|
||||
#. name for nso
|
||||
msgid "Sotho; Northern"
|
||||
@ -19096,7 +19096,7 @@ msgstr "Llenguatge de signes marítim"
|
||||
|
||||
#. name for nss
|
||||
msgid "Nali"
|
||||
msgstr ""
|
||||
msgstr "Nali"
|
||||
|
||||
#. name for nst
|
||||
msgid "Naga; Tase"
|
||||
@ -19108,15 +19108,15 @@ msgstr "Nàhuatl; Sierra Negra"
|
||||
|
||||
#. name for nsv
|
||||
msgid "Nisu; Southwestern"
|
||||
msgstr ""
|
||||
msgstr "Nisu; Sudoccidental"
|
||||
|
||||
#. name for nsw
|
||||
msgid "Navut"
|
||||
msgstr ""
|
||||
msgstr "Navut"
|
||||
|
||||
#. name for nsx
|
||||
msgid "Nsongo"
|
||||
msgstr ""
|
||||
msgstr "Nsongo"
|
||||
|
||||
#. name for nsy
|
||||
msgid "Nasal"
|
||||
@ -19124,19 +19124,19 @@ msgstr ""
|
||||
|
||||
#. name for nsz
|
||||
msgid "Nisenan"
|
||||
msgstr ""
|
||||
msgstr "Nisenan"
|
||||
|
||||
#. name for nte
|
||||
msgid "Nathembo"
|
||||
msgstr ""
|
||||
msgstr "Nathembo"
|
||||
|
||||
#. name for nti
|
||||
msgid "Natioro"
|
||||
msgstr ""
|
||||
msgstr "Natioro"
|
||||
|
||||
#. name for ntj
|
||||
msgid "Ngaanyatjarra"
|
||||
msgstr ""
|
||||
msgstr "Ngaanyatjarra"
|
||||
|
||||
#. name for ntk
|
||||
msgid "Ikoma-Nata-Isenye"
|
||||
@ -19144,11 +19144,11 @@ msgstr ""
|
||||
|
||||
#. name for ntm
|
||||
msgid "Nateni"
|
||||
msgstr ""
|
||||
msgstr "Nateni"
|
||||
|
||||
#. name for nto
|
||||
msgid "Ntomba"
|
||||
msgstr ""
|
||||
msgstr "Ntomba"
|
||||
|
||||
#. name for ntp
|
||||
msgid "Tepehuan; Northern"
|
||||
@ -19160,15 +19160,15 @@ msgstr ""
|
||||
|
||||
#. name for nts
|
||||
msgid "Natagaimas"
|
||||
msgstr ""
|
||||
msgstr "Natagaimas"
|
||||
|
||||
#. name for ntu
|
||||
msgid "Natügu"
|
||||
msgstr ""
|
||||
msgstr "Santa Cruz: Septentrional"
|
||||
|
||||
#. name for ntw
|
||||
msgid "Nottoway"
|
||||
msgstr ""
|
||||
msgstr "Nottoway"
|
||||
|
||||
#. name for nty
|
||||
msgid "Mantsi"
|
||||
@ -19176,7 +19176,7 @@ msgstr ""
|
||||
|
||||
#. name for ntz
|
||||
msgid "Natanzi"
|
||||
msgstr ""
|
||||
msgstr "Natanzi"
|
||||
|
||||
#. name for nua
|
||||
msgid "Yuaga"
|
||||
@ -19184,35 +19184,35 @@ msgstr ""
|
||||
|
||||
#. name for nuc
|
||||
msgid "Nukuini"
|
||||
msgstr ""
|
||||
msgstr "Nukini"
|
||||
|
||||
#. name for nud
|
||||
msgid "Ngala"
|
||||
msgstr ""
|
||||
msgstr "Ngala"
|
||||
|
||||
#. name for nue
|
||||
msgid "Ngundu"
|
||||
msgstr ""
|
||||
msgstr "Ngundu"
|
||||
|
||||
#. name for nuf
|
||||
msgid "Nusu"
|
||||
msgstr ""
|
||||
msgstr "Nusu"
|
||||
|
||||
#. name for nug
|
||||
msgid "Nungali"
|
||||
msgstr ""
|
||||
msgstr "Nungali"
|
||||
|
||||
#. name for nuh
|
||||
msgid "Ndunda"
|
||||
msgstr ""
|
||||
msgstr "Ndunda"
|
||||
|
||||
#. name for nui
|
||||
msgid "Ngumbi"
|
||||
msgstr ""
|
||||
msgstr "Ngumbi"
|
||||
|
||||
#. name for nuj
|
||||
msgid "Nyole"
|
||||
msgstr ""
|
||||
msgstr "Nyole"
|
||||
|
||||
#. name for nuk
|
||||
msgid "Nuu-chah-nulth"
|
||||
@ -19220,11 +19220,11 @@ msgstr ""
|
||||
|
||||
#. name for nul
|
||||
msgid "Nusa Laut"
|
||||
msgstr ""
|
||||
msgstr "Nusa Laut"
|
||||
|
||||
#. name for num
|
||||
msgid "Niuafo'ou"
|
||||
msgstr ""
|
||||
msgstr "Niuafo'ou"
|
||||
|
||||
#. name for nun
|
||||
msgid "Anong"
|
||||
@ -19232,31 +19232,31 @@ msgstr ""
|
||||
|
||||
#. name for nuo
|
||||
msgid "Nguôn"
|
||||
msgstr ""
|
||||
msgstr "Nguon"
|
||||
|
||||
#. name for nup
|
||||
msgid "Nupe-Nupe-Tako"
|
||||
msgstr ""
|
||||
msgstr "Nupe"
|
||||
|
||||
#. name for nuq
|
||||
msgid "Nukumanu"
|
||||
msgstr ""
|
||||
msgstr "Nukumanu"
|
||||
|
||||
#. name for nur
|
||||
msgid "Nukuria"
|
||||
msgstr ""
|
||||
msgstr "Nuguria"
|
||||
|
||||
#. name for nus
|
||||
msgid "Nuer"
|
||||
msgstr ""
|
||||
msgstr "Nuer"
|
||||
|
||||
#. name for nut
|
||||
msgid "Nung (Viet Nam)"
|
||||
msgstr ""
|
||||
msgstr "Nung (VietNam)"
|
||||
|
||||
#. name for nuu
|
||||
msgid "Ngbundu"
|
||||
msgstr ""
|
||||
msgstr "Ngbundu"
|
||||
|
||||
#. name for nuv
|
||||
msgid "Nuni; Northern"
|
||||
@ -19264,7 +19264,7 @@ msgstr "Nuni; Septentrional"
|
||||
|
||||
#. name for nuw
|
||||
msgid "Nguluwan"
|
||||
msgstr ""
|
||||
msgstr "Nguluwà"
|
||||
|
||||
#. name for nux
|
||||
msgid "Mehek"
|
||||
@ -19272,7 +19272,7 @@ msgstr ""
|
||||
|
||||
#. name for nuy
|
||||
msgid "Nunggubuyu"
|
||||
msgstr ""
|
||||
msgstr "Nunggubuyu"
|
||||
|
||||
#. name for nuz
|
||||
msgid "Nahuatl; Tlamacazapa"
|
||||
@ -19280,27 +19280,27 @@ msgstr "Nàhuatl; Tlamacazapa"
|
||||
|
||||
#. name for nvh
|
||||
msgid "Nasarian"
|
||||
msgstr ""
|
||||
msgstr "Nasarià"
|
||||
|
||||
#. name for nvm
|
||||
msgid "Namiae"
|
||||
msgstr ""
|
||||
msgstr "Namiae"
|
||||
|
||||
#. name for nwa
|
||||
msgid "Nawathinehena"
|
||||
msgstr ""
|
||||
msgstr "Nawathinahana"
|
||||
|
||||
#. name for nwb
|
||||
msgid "Nyabwa"
|
||||
msgstr ""
|
||||
msgstr "Nyabwa-Nyédébwa"
|
||||
|
||||
#. name for nwc
|
||||
msgid "Newari; Old"
|
||||
msgstr ""
|
||||
msgstr "Newar; Antic"
|
||||
|
||||
#. name for nwe
|
||||
msgid "Ngwe"
|
||||
msgstr ""
|
||||
msgstr "Ngwe"
|
||||
|
||||
#. name for nwi
|
||||
msgid "Tanna; Southwest"
|
||||
@ -19308,23 +19308,23 @@ msgstr ""
|
||||
|
||||
#. name for nwm
|
||||
msgid "Nyamusa-Molo"
|
||||
msgstr ""
|
||||
msgstr "Nyamusa-Molo"
|
||||
|
||||
#. name for nwr
|
||||
msgid "Nawaru"
|
||||
msgstr ""
|
||||
msgstr "Nawaru"
|
||||
|
||||
#. name for nwx
|
||||
msgid "Newar; Middle"
|
||||
msgstr ""
|
||||
msgstr "Newar; Mitjà"
|
||||
|
||||
#. name for nwy
|
||||
msgid "Nottoway-Meherrin"
|
||||
msgstr ""
|
||||
msgstr "Nottoway"
|
||||
|
||||
#. name for nxa
|
||||
msgid "Nauete"
|
||||
msgstr ""
|
||||
msgstr "Naueti"
|
||||
|
||||
#. name for nxd
|
||||
msgid "Ngando (Democratic Republic of Congo)"
|
||||
@ -19332,7 +19332,7 @@ msgstr "Ngando (República Democràtica del Congo)"
|
||||
|
||||
#. name for nxe
|
||||
msgid "Nage"
|
||||
msgstr ""
|
||||
msgstr "Nage"
|
||||
|
||||
#. name for nxg
|
||||
msgid "Ngad'a"
|
||||
@ -19340,7 +19340,7 @@ msgstr "Ngada; Central"
|
||||
|
||||
#. name for nxi
|
||||
msgid "Nindi"
|
||||
msgstr ""
|
||||
msgstr "Nindi"
|
||||
|
||||
#. name for nxl
|
||||
msgid "Nuaulu; South"
|
||||
@ -19348,39 +19348,39 @@ msgstr "Nuaulu; Meridional"
|
||||
|
||||
#. name for nxm
|
||||
msgid "Numidian"
|
||||
msgstr ""
|
||||
msgstr "Líbic"
|
||||
|
||||
#. name for nxn
|
||||
msgid "Ngawun"
|
||||
msgstr ""
|
||||
msgstr "Ngawun"
|
||||
|
||||
#. name for nxq
|
||||
msgid "Naxi"
|
||||
msgstr ""
|
||||
msgstr "Naxi"
|
||||
|
||||
#. name for nxr
|
||||
msgid "Ninggerum"
|
||||
msgstr ""
|
||||
msgstr "Ninggirum"
|
||||
|
||||
#. name for nxu
|
||||
msgid "Narau"
|
||||
msgstr ""
|
||||
msgstr "Narau"
|
||||
|
||||
#. name for nxx
|
||||
msgid "Nafri"
|
||||
msgstr ""
|
||||
msgstr "Nafri"
|
||||
|
||||
#. name for nya
|
||||
msgid "Nyanja"
|
||||
msgstr ""
|
||||
msgstr "Nyanja"
|
||||
|
||||
#. name for nyb
|
||||
msgid "Nyangbo"
|
||||
msgstr ""
|
||||
msgstr "Nyangbo"
|
||||
|
||||
#. name for nyc
|
||||
msgid "Nyanga-li"
|
||||
msgstr ""
|
||||
msgstr "Nyanga-li"
|
||||
|
||||
#. name for nyd
|
||||
msgid "Nyore"
|
||||
@ -19388,7 +19388,7 @@ msgstr ""
|
||||
|
||||
#. name for nye
|
||||
msgid "Nyengo"
|
||||
msgstr ""
|
||||
msgstr "Nyengo"
|
||||
|
||||
#. name for nyf
|
||||
msgid "Giryama"
|
||||
@ -19396,11 +19396,11 @@ msgstr ""
|
||||
|
||||
#. name for nyg
|
||||
msgid "Nyindu"
|
||||
msgstr ""
|
||||
msgstr "Nyindu"
|
||||
|
||||
#. name for nyh
|
||||
msgid "Nyigina"
|
||||
msgstr ""
|
||||
msgstr "Nyigina"
|
||||
|
||||
#. name for nyi
|
||||
msgid "Ama (Sudan)"
|
||||
@ -19408,35 +19408,35 @@ msgstr ""
|
||||
|
||||
#. name for nyj
|
||||
msgid "Nyanga"
|
||||
msgstr ""
|
||||
msgstr "Nyanga"
|
||||
|
||||
#. name for nyk
|
||||
msgid "Nyaneka"
|
||||
msgstr ""
|
||||
msgstr "Nyaneka"
|
||||
|
||||
#. name for nyl
|
||||
msgid "Nyeu"
|
||||
msgstr ""
|
||||
msgstr "Nyeu"
|
||||
|
||||
#. name for nym
|
||||
msgid "Nyamwezi"
|
||||
msgstr ""
|
||||
msgstr "Nyamwesi"
|
||||
|
||||
#. name for nyn
|
||||
msgid "Nyankole"
|
||||
msgstr ""
|
||||
msgstr "Nyankore"
|
||||
|
||||
#. name for nyo
|
||||
msgid "Nyoro"
|
||||
msgstr ""
|
||||
msgstr "Nyoro"
|
||||
|
||||
#. name for nyp
|
||||
msgid "Nyang'i"
|
||||
msgstr ""
|
||||
msgstr "Nyangi"
|
||||
|
||||
#. name for nyq
|
||||
msgid "Nayini"
|
||||
msgstr ""
|
||||
msgstr "Nayini"
|
||||
|
||||
#. name for nyr
|
||||
msgid "Nyiha (Malawi)"
|
||||
@ -19444,31 +19444,31 @@ msgstr ""
|
||||
|
||||
#. name for nys
|
||||
msgid "Nyunga"
|
||||
msgstr ""
|
||||
msgstr "Nyunga"
|
||||
|
||||
#. name for nyt
|
||||
msgid "Nyawaygi"
|
||||
msgstr ""
|
||||
msgstr "Nyawaygi"
|
||||
|
||||
#. name for nyu
|
||||
msgid "Nyungwe"
|
||||
msgstr ""
|
||||
msgstr "Nyungwe"
|
||||
|
||||
#. name for nyv
|
||||
msgid "Nyulnyul"
|
||||
msgstr ""
|
||||
msgstr "Nyulnyui"
|
||||
|
||||
#. name for nyw
|
||||
msgid "Nyaw"
|
||||
msgstr ""
|
||||
msgstr "Nyaw"
|
||||
|
||||
#. name for nyx
|
||||
msgid "Nganyaywana"
|
||||
msgstr ""
|
||||
msgstr "Nganyaywana"
|
||||
|
||||
#. name for nyy
|
||||
msgid "Nyakyusa-Ngonde"
|
||||
msgstr ""
|
||||
msgstr "Nyakyusa-Ngonde"
|
||||
|
||||
#. name for nza
|
||||
msgid "Mbembe; Tigon"
|
||||
@ -19476,15 +19476,15 @@ msgstr "Mbembe Tigon"
|
||||
|
||||
#. name for nzb
|
||||
msgid "Njebi"
|
||||
msgstr ""
|
||||
msgstr "Njebi"
|
||||
|
||||
#. name for nzi
|
||||
msgid "Nzima"
|
||||
msgstr ""
|
||||
msgstr "Nzema"
|
||||
|
||||
#. name for nzk
|
||||
msgid "Nzakara"
|
||||
msgstr ""
|
||||
msgstr "Nzakara"
|
||||
|
||||
#. name for nzm
|
||||
msgid "Naga; Zeme"
|
||||
@ -19500,7 +19500,7 @@ msgstr "Teke; Nzikou"
|
||||
|
||||
#. name for nzy
|
||||
msgid "Nzakambay"
|
||||
msgstr ""
|
||||
msgstr "Nzakambay"
|
||||
|
||||
#. name for nzz
|
||||
msgid "Dogon; Nanga Dama"
|
||||
@ -19508,11 +19508,11 @@ msgstr "Dogon; Nanga Dama"
|
||||
|
||||
#. name for oaa
|
||||
msgid "Orok"
|
||||
msgstr ""
|
||||
msgstr "Orok"
|
||||
|
||||
#. name for oac
|
||||
msgid "Oroch"
|
||||
msgstr ""
|
||||
msgstr "Orotx"
|
||||
|
||||
#. name for oar
|
||||
msgid "Aramaic; Old (up to 700 BCE)"
|
||||
@ -29600,7 +29600,7 @@ msgstr ""
|
||||
|
||||
#. name for yiv
|
||||
msgid "Nisu; Northern"
|
||||
msgstr ""
|
||||
msgstr "Yi; Eshan-Xinping"
|
||||
|
||||
#. name for yix
|
||||
msgid "Yi; Axi"
|
||||
|
@ -9,14 +9,14 @@ msgstr ""
|
||||
"Project-Id-Version: calibre\n"
|
||||
"Report-Msgid-Bugs-To: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"POT-Creation-Date: 2011-11-25 14:01+0000\n"
|
||||
"PO-Revision-Date: 2012-08-15 10:30+0000\n"
|
||||
"Last-Translator: Jellby <Unknown>\n"
|
||||
"PO-Revision-Date: 2012-12-24 08:05+0000\n"
|
||||
"Last-Translator: Adolfo Jayme Barrientos <fitoschido@gmail.com>\n"
|
||||
"Language-Team: Español; Castellano <>\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-08-16 04:40+0000\n"
|
||||
"X-Generator: Launchpad (build 15810)\n"
|
||||
"X-Launchpad-Export-Date: 2012-12-25 04:46+0000\n"
|
||||
"X-Generator: Launchpad (build 16378)\n"
|
||||
|
||||
#. name for aaa
|
||||
msgid "Ghotuo"
|
||||
@ -9584,27 +9584,27 @@ msgstr "Holikachuk"
|
||||
|
||||
#. name for hoj
|
||||
msgid "Hadothi"
|
||||
msgstr ""
|
||||
msgstr "Hadothi"
|
||||
|
||||
#. name for hol
|
||||
msgid "Holu"
|
||||
msgstr ""
|
||||
msgstr "Holu"
|
||||
|
||||
#. name for hom
|
||||
msgid "Homa"
|
||||
msgstr ""
|
||||
msgstr "Homa"
|
||||
|
||||
#. name for hoo
|
||||
msgid "Holoholo"
|
||||
msgstr ""
|
||||
msgstr "Holoholo"
|
||||
|
||||
#. name for hop
|
||||
msgid "Hopi"
|
||||
msgstr ""
|
||||
msgstr "Hopi"
|
||||
|
||||
#. name for hor
|
||||
msgid "Horo"
|
||||
msgstr ""
|
||||
msgstr "Horo"
|
||||
|
||||
#. name for hos
|
||||
msgid "Ho Chi Minh City Sign Language"
|
||||
@ -9612,27 +9612,27 @@ msgstr "Lengua de signos de Ho Chi Minh"
|
||||
|
||||
#. name for hot
|
||||
msgid "Hote"
|
||||
msgstr ""
|
||||
msgstr "Hote"
|
||||
|
||||
#. name for hov
|
||||
msgid "Hovongan"
|
||||
msgstr ""
|
||||
msgstr "Hovongan"
|
||||
|
||||
#. name for how
|
||||
msgid "Honi"
|
||||
msgstr ""
|
||||
msgstr "Honi"
|
||||
|
||||
#. name for hoy
|
||||
msgid "Holiya"
|
||||
msgstr ""
|
||||
msgstr "Holiya"
|
||||
|
||||
#. name for hoz
|
||||
msgid "Hozo"
|
||||
msgstr ""
|
||||
msgstr "Hozo"
|
||||
|
||||
#. name for hpo
|
||||
msgid "Hpon"
|
||||
msgstr ""
|
||||
msgstr "Hpon"
|
||||
|
||||
#. name for hps
|
||||
msgid "Hawai'i Pidgin Sign Language"
|
||||
@ -9640,15 +9640,15 @@ msgstr "Lengua de signos pidyin hawaiana"
|
||||
|
||||
#. name for hra
|
||||
msgid "Hrangkhol"
|
||||
msgstr ""
|
||||
msgstr "Hrangkhol"
|
||||
|
||||
#. name for hre
|
||||
msgid "Hre"
|
||||
msgstr ""
|
||||
msgstr "Hre"
|
||||
|
||||
#. name for hrk
|
||||
msgid "Haruku"
|
||||
msgstr ""
|
||||
msgstr "Haruku"
|
||||
|
||||
#. name for hrm
|
||||
msgid "Miao; Horned"
|
||||
@ -9656,19 +9656,19 @@ msgstr ""
|
||||
|
||||
#. name for hro
|
||||
msgid "Haroi"
|
||||
msgstr ""
|
||||
msgstr "Haroi"
|
||||
|
||||
#. name for hrr
|
||||
msgid "Horuru"
|
||||
msgstr ""
|
||||
msgstr "Horuru"
|
||||
|
||||
#. name for hrt
|
||||
msgid "Hértevin"
|
||||
msgstr ""
|
||||
msgstr "Hértevin"
|
||||
|
||||
#. name for hru
|
||||
msgid "Hruso"
|
||||
msgstr ""
|
||||
msgstr "Hruso"
|
||||
|
||||
#. name for hrv
|
||||
msgid "Croatian"
|
||||
|
@ -9,14 +9,14 @@ msgstr ""
|
||||
"Report-Msgid-Bugs-To: Debian iso-codes team <pkg-isocodes-"
|
||||
"devel@lists.alioth.debian.org>\n"
|
||||
"POT-Creation-Date: 2011-11-25 14:01+0000\n"
|
||||
"PO-Revision-Date: 2012-12-14 00:48+0000\n"
|
||||
"PO-Revision-Date: 2012-12-21 03:31+0000\n"
|
||||
"Last-Translator: Fábio Malcher Miranda <mirand863@hotmail.com>\n"
|
||||
"Language-Team: Brazilian Portuguese\n"
|
||||
"MIME-Version: 1.0\n"
|
||||
"Content-Type: text/plain; charset=UTF-8\n"
|
||||
"Content-Transfer-Encoding: 8bit\n"
|
||||
"X-Launchpad-Export-Date: 2012-12-15 05:02+0000\n"
|
||||
"X-Generator: Launchpad (build 16372)\n"
|
||||
"X-Launchpad-Export-Date: 2012-12-22 04:59+0000\n"
|
||||
"X-Generator: Launchpad (build 16378)\n"
|
||||
"Language: \n"
|
||||
|
||||
#. name for aaa
|
||||
@ -1189,7 +1189,7 @@ msgstr ""
|
||||
|
||||
#. name for anz
|
||||
msgid "Anem"
|
||||
msgstr ""
|
||||
msgstr "Anem"
|
||||
|
||||
#. name for aoa
|
||||
msgid "Angolar"
|
||||
@ -1197,27 +1197,27 @@ msgstr ""
|
||||
|
||||
#. name for aob
|
||||
msgid "Abom"
|
||||
msgstr ""
|
||||
msgstr "Abom"
|
||||
|
||||
#. name for aoc
|
||||
msgid "Pemon"
|
||||
msgstr ""
|
||||
msgstr "Pemon"
|
||||
|
||||
#. name for aod
|
||||
msgid "Andarum"
|
||||
msgstr ""
|
||||
msgstr "Andarum"
|
||||
|
||||
#. name for aoe
|
||||
msgid "Angal Enen"
|
||||
msgstr ""
|
||||
msgstr "Angal Enen"
|
||||
|
||||
#. name for aof
|
||||
msgid "Bragat"
|
||||
msgstr ""
|
||||
msgstr "Bragat"
|
||||
|
||||
#. name for aog
|
||||
msgid "Angoram"
|
||||
msgstr ""
|
||||
msgstr "Angoram"
|
||||
|
||||
#. name for aoh
|
||||
msgid "Arma"
|
||||
|
@ -4,7 +4,7 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
__appname__ = u'calibre'
|
||||
numeric_version = (0, 9, 11)
|
||||
numeric_version = (0, 9, 12)
|
||||
__version__ = u'.'.join(map(unicode, numeric_version))
|
||||
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"
|
||||
|
||||
@ -100,6 +100,7 @@ class Plugins(collections.Mapping):
|
||||
'freetype',
|
||||
'woff',
|
||||
'unrar',
|
||||
'qt_hack',
|
||||
]
|
||||
if iswindows:
|
||||
plugins.extend(['winutil', 'wpd', 'winfonts'])
|
||||
|
@ -661,7 +661,7 @@ from calibre.devices.nuut2.driver import NUUT2
|
||||
from calibre.devices.iriver.driver import IRIVER_STORY
|
||||
from calibre.devices.binatone.driver import README
|
||||
from calibre.devices.hanvon.driver import (N516, EB511, ALEX, AZBOOKA, THEBOOK,
|
||||
LIBREAIR, ODYSSEY)
|
||||
LIBREAIR, ODYSSEY, KIBANO)
|
||||
from calibre.devices.edge.driver import EDGE
|
||||
from calibre.devices.teclast.driver import (TECLAST_K3, NEWSMY, IPAPYRUS,
|
||||
SOVOS, PICO, SUNSTECH_EB700, ARCHOS7O, STASH, WEXLER)
|
||||
@ -712,7 +712,7 @@ plugins += [
|
||||
BOOQ,
|
||||
EB600,
|
||||
README,
|
||||
N516,
|
||||
N516, KIBANO,
|
||||
THEBOOK, LIBREAIR,
|
||||
EB511,
|
||||
ELONEX,
|
||||
|
@ -41,6 +41,20 @@ class N516(USBMS):
|
||||
def can_handle(self, device_info, debug=False):
|
||||
return not is_alex(device_info)
|
||||
|
||||
class KIBANO(N516):
|
||||
|
||||
name = 'Kibano driver'
|
||||
gui_name = 'Kibano'
|
||||
description = _('Communicate with the Kibano eBook reader.')
|
||||
FORMATS = ['epub', 'pdf', 'txt']
|
||||
BCD = [0x323]
|
||||
|
||||
VENDOR_NAME = 'EBOOK'
|
||||
# We use EXTERNAL_SD_CARD for main mem as some devices have not working
|
||||
# main memories
|
||||
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['INTERNAL_SD_CARD',
|
||||
'EXTERNAL_SD_CARD']
|
||||
|
||||
class THEBOOK(N516):
|
||||
name = 'The Book driver'
|
||||
gui_name = 'The Book'
|
||||
|
@ -13,6 +13,7 @@ const calibre_device_entry_t calibre_mtp_device_table[] = {
|
||||
|
||||
// Amazon Kindle Fire HD
|
||||
, { "Amazon", 0x1949, "Fire HD", 0x0007, DEVICE_FLAGS_ANDROID_BUGS}
|
||||
, { "Amazon", 0x1949, "Fire HD", 0x0008, DEVICE_FLAGS_ANDROID_BUGS}
|
||||
, { "Amazon", 0x1949, "Fire HD", 0x000a, DEVICE_FLAGS_ANDROID_BUGS}
|
||||
|
||||
// Nexus 10
|
||||
|
@ -8,7 +8,9 @@
|
||||
|
||||
#define UNICODE
|
||||
#include <Python.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdlib.h>
|
||||
#include <libmtp.h>
|
||||
|
||||
@ -728,7 +730,20 @@ initlibmtp(void) {
|
||||
if (MTPError == NULL) return;
|
||||
PyModule_AddObject(m, "MTPError", MTPError);
|
||||
|
||||
// Redirect stdout to get rid of the annoying message about mtpz. Really,
|
||||
// who designs a library without anyway to control/redirect the debugging
|
||||
// output, and hardcoded paths that cannot be changed?
|
||||
int bak, new;
|
||||
fflush(stdout);
|
||||
bak = dup(STDOUT_FILENO);
|
||||
new = open("/dev/null", O_WRONLY);
|
||||
dup2(new, STDOUT_FILENO);
|
||||
close(new);
|
||||
LIBMTP_Init();
|
||||
fflush(stdout);
|
||||
dup2(bak, STDOUT_FILENO);
|
||||
close(bak);
|
||||
|
||||
LIBMTP_Set_Debug(LIBMTP_DEBUG_NONE);
|
||||
|
||||
Py_INCREF(&DeviceType);
|
||||
|
@ -19,9 +19,10 @@ class TECLAST_K3(USBMS):
|
||||
PRODUCT_ID = [0x3203]
|
||||
BCD = [0x0000, 0x0100]
|
||||
|
||||
VENDOR_NAME = ['TECLAST', 'IMAGIN', 'RK28XX', 'PER3274B', 'BEBOOK']
|
||||
VENDOR_NAME = ['TECLAST', 'IMAGIN', 'RK28XX', 'PER3274B', 'BEBOOK',
|
||||
'RK2728', 'MR700']
|
||||
WINDOWS_MAIN_MEM = WINDOWS_CARD_A_MEM = ['DIGITAL_PLAYER', 'TL-K5',
|
||||
'EREADER', 'USB-MSC', 'PER3274B', 'BEBOOK']
|
||||
'EREADER', 'USB-MSC', 'PER3274B', 'BEBOOK', 'USER']
|
||||
|
||||
MAIN_MEMORY_VOLUME_LABEL = 'K3 Main Memory'
|
||||
STORAGE_CARD_VOLUME_LABEL = 'K3 Storage Card'
|
||||
|
@ -14,50 +14,32 @@ import os
|
||||
from calibre.customize.conversion import OutputFormatPlugin, \
|
||||
OptionRecommendation
|
||||
from calibre.ptempfile import TemporaryDirectory
|
||||
from calibre.constants import iswindows
|
||||
|
||||
UNITS = [
|
||||
'millimeter',
|
||||
'point',
|
||||
'inch' ,
|
||||
'pica' ,
|
||||
'didot',
|
||||
'cicero',
|
||||
'devicepixel',
|
||||
]
|
||||
UNITS = ['millimeter', 'centimeter', 'point', 'inch' , 'pica' , 'didot',
|
||||
'cicero', 'devicepixel']
|
||||
|
||||
PAPER_SIZES = ['b2',
|
||||
'a9',
|
||||
'executive',
|
||||
'tabloid',
|
||||
'b4',
|
||||
'b5',
|
||||
'b6',
|
||||
'b7',
|
||||
'b0',
|
||||
'b1',
|
||||
'letter',
|
||||
'b3',
|
||||
'a7',
|
||||
'a8',
|
||||
'b8',
|
||||
'b9',
|
||||
'a3',
|
||||
'a1',
|
||||
'folio',
|
||||
'c5e',
|
||||
'dle',
|
||||
'a0',
|
||||
'ledger',
|
||||
'legal',
|
||||
'a6',
|
||||
'a2',
|
||||
'b10',
|
||||
'a5',
|
||||
'comm10e',
|
||||
'a4']
|
||||
PAPER_SIZES = [u'a0', u'a1', u'a2', u'a3', u'a4', u'a5', u'a6', u'b0', u'b1',
|
||||
u'b2', u'b3', u'b4', u'b5', u'b6', u'legal', u'letter']
|
||||
|
||||
ORIENTATIONS = ['portrait', 'landscape']
|
||||
class PDFMetadata(object): # {{{
|
||||
def __init__(self, oeb_metadata=None):
|
||||
from calibre import force_unicode
|
||||
from calibre.ebooks.metadata import authors_to_string
|
||||
self.title = _(u'Unknown')
|
||||
self.author = _(u'Unknown')
|
||||
self.tags = u''
|
||||
|
||||
if oeb_metadata != None:
|
||||
if len(oeb_metadata.title) >= 1:
|
||||
self.title = oeb_metadata.title[0].value
|
||||
if len(oeb_metadata.creator) >= 1:
|
||||
self.author = authors_to_string([x.value for x in oeb_metadata.creator])
|
||||
if oeb_metadata.subject:
|
||||
self.tags = u', '.join(map(unicode, oeb_metadata.subject))
|
||||
|
||||
self.title = force_unicode(self.title)
|
||||
self.author = force_unicode(self.author)
|
||||
# }}}
|
||||
|
||||
class PDFOutput(OutputFormatPlugin):
|
||||
|
||||
@ -66,9 +48,14 @@ class PDFOutput(OutputFormatPlugin):
|
||||
file_type = 'pdf'
|
||||
|
||||
options = set([
|
||||
OptionRecommendation(name='override_profile_size', recommended_value=False,
|
||||
help=_('Normally, the PDF page size is set by the output profile'
|
||||
' chosen under page options. This option will cause the '
|
||||
' page size settings under PDF Output to override the '
|
||||
' size specified by the output profile.')),
|
||||
OptionRecommendation(name='unit', recommended_value='inch',
|
||||
level=OptionRecommendation.LOW, short_switch='u', choices=UNITS,
|
||||
help=_('The unit of measure. Default is inch. Choices '
|
||||
help=_('The unit of measure for page sizes. Default is inch. Choices '
|
||||
'are %s '
|
||||
'Note: This does not override the unit for margins!') % UNITS),
|
||||
OptionRecommendation(name='paper_size', recommended_value='letter',
|
||||
@ -80,10 +67,6 @@ class PDFOutput(OutputFormatPlugin):
|
||||
help=_('Custom size of the document. Use the form widthxheight '
|
||||
'EG. `123x321` to specify the width and height. '
|
||||
'This overrides any specified paper-size.')),
|
||||
OptionRecommendation(name='orientation', recommended_value='portrait',
|
||||
level=OptionRecommendation.LOW, choices=ORIENTATIONS,
|
||||
help=_('The orientation of the page. Default is portrait. Choices '
|
||||
'are %s') % ORIENTATIONS),
|
||||
OptionRecommendation(name='preserve_cover_aspect_ratio',
|
||||
recommended_value=False,
|
||||
help=_('Preserve the aspect ratio of the cover, instead'
|
||||
@ -108,6 +91,12 @@ class PDFOutput(OutputFormatPlugin):
|
||||
OptionRecommendation(name='pdf_mono_font_size',
|
||||
recommended_value=16, help=_(
|
||||
'The default font size for monospaced text')),
|
||||
# OptionRecommendation(name='old_pdf_engine', recommended_value=False,
|
||||
# help=_('Use the old, less capable engine to generate the PDF')),
|
||||
# OptionRecommendation(name='uncompressed_pdf',
|
||||
# recommended_value=False, help=_(
|
||||
# 'Generate an uncompressed PDF, useful for debugging, '
|
||||
# 'only works with the new PDF engine.')),
|
||||
])
|
||||
|
||||
def convert(self, oeb_book, output_path, input_plugin, opts, log):
|
||||
@ -200,33 +189,19 @@ class PDFOutput(OutputFormatPlugin):
|
||||
if k in family_map:
|
||||
val[i].value = family_map[k]
|
||||
|
||||
def remove_font_specification(self):
|
||||
# Qt produces image based pdfs on windows when non-generic fonts are specified
|
||||
# This might change in Qt WebKit 2.3+ you will have to test.
|
||||
for item in self.oeb.manifest:
|
||||
if not hasattr(item.data, 'cssRules'): continue
|
||||
for i, rule in enumerate(item.data.cssRules):
|
||||
if rule.type != rule.STYLE_RULE: continue
|
||||
ff = rule.style.getProperty('font-family')
|
||||
if ff is None: continue
|
||||
val = ff.propertyValue
|
||||
for i in xrange(val.length):
|
||||
k = icu_lower(val[i].value)
|
||||
if k not in {'serif', 'sans', 'sans-serif', 'sansserif',
|
||||
'monospace', 'cursive', 'fantasy'}:
|
||||
val[i].value = ''
|
||||
|
||||
def convert_text(self, oeb_book):
|
||||
from calibre.ebooks.pdf.writer import PDFWriter
|
||||
from calibre.utils.config import tweaks
|
||||
if tweaks.get('new_pdf_engine', False):
|
||||
from calibre.ebooks.pdf.render.from_html import PDFWriter
|
||||
PDFWriter
|
||||
else:
|
||||
from calibre.ebooks.pdf.writer import PDFWriter
|
||||
from calibre.ebooks.metadata.opf2 import OPF
|
||||
|
||||
self.log.debug('Serializing oeb input to disk for processing...')
|
||||
self.get_cover_data()
|
||||
|
||||
if iswindows:
|
||||
self.remove_font_specification()
|
||||
else:
|
||||
self.handle_embedded_fonts()
|
||||
self.handle_embedded_fonts()
|
||||
|
||||
with TemporaryDirectory('_pdf_out') as oeb_dir:
|
||||
from calibre.customize.ui import plugin_for_output_format
|
||||
@ -240,9 +215,9 @@ class PDFOutput(OutputFormatPlugin):
|
||||
'toc', None))
|
||||
|
||||
def write(self, Writer, items, toc):
|
||||
from calibre.ebooks.pdf.writer import PDFMetadata
|
||||
writer = Writer(self.opts, self.log, cover_data=self.cover_data,
|
||||
toc=toc)
|
||||
writer.report_progress = self.report_progress
|
||||
|
||||
close = False
|
||||
if not hasattr(self.output_path, 'write'):
|
||||
|
@ -75,6 +75,20 @@ class Worker(Thread): # Get details {{{
|
||||
9: ['sept'],
|
||||
12: ['déc'],
|
||||
},
|
||||
'br': {
|
||||
1: ['janeiro'],
|
||||
2: ['fevereiro'],
|
||||
3: ['março'],
|
||||
4: ['abril'],
|
||||
5: ['maio'],
|
||||
6: ['junho'],
|
||||
7: ['julho'],
|
||||
8: ['agosto'],
|
||||
9: ['setembro'],
|
||||
10: ['outubro'],
|
||||
11: ['novembro'],
|
||||
12: ['dezembro'],
|
||||
},
|
||||
'es': {
|
||||
1: ['enero'],
|
||||
2: ['febrero'],
|
||||
@ -89,7 +103,7 @@ class Worker(Thread): # Get details {{{
|
||||
11: ['noviembre'],
|
||||
12: ['diciembre'],
|
||||
},
|
||||
'jp': {
|
||||
'jp': {
|
||||
1: [u'1月'],
|
||||
2: [u'2月'],
|
||||
3: [u'3月'],
|
||||
@ -117,6 +131,7 @@ class Worker(Thread): # Get details {{{
|
||||
text()="Product details" or \
|
||||
text()="Détails sur le produit" or \
|
||||
text()="Detalles del producto" or \
|
||||
text()="Detalhes do produto" or \
|
||||
text()="登録情報"]/../div[@class="content"]
|
||||
'''
|
||||
# Editor: is for Spanish
|
||||
@ -126,6 +141,7 @@ class Worker(Thread): # Get details {{{
|
||||
starts-with(text(), "Editore:") or \
|
||||
starts-with(text(), "Editeur") or \
|
||||
starts-with(text(), "Editor:") or \
|
||||
starts-with(text(), "Editora:") or \
|
||||
starts-with(text(), "出版社:")]
|
||||
'''
|
||||
self.language_xpath = '''
|
||||
@ -141,7 +157,7 @@ class Worker(Thread): # Get details {{{
|
||||
'''
|
||||
|
||||
self.ratings_pat = re.compile(
|
||||
r'([0-9.]+) ?(out of|von|su|étoiles sur|つ星のうち|de un máximo de) ([\d\.]+)( (stars|Sternen|stelle|estrellas)){0,1}')
|
||||
r'([0-9.]+) ?(out of|von|su|étoiles sur|つ星のうち|de un máximo de|de) ([\d\.]+)( (stars|Sternen|stelle|estrellas|estrelas)){0,1}')
|
||||
|
||||
lm = {
|
||||
'eng': ('English', 'Englisch'),
|
||||
@ -150,6 +166,7 @@ class Worker(Thread): # Get details {{{
|
||||
'deu': ('German', 'Deutsch'),
|
||||
'spa': ('Spanish', 'Espa\xf1ol', 'Espaniol'),
|
||||
'jpn': ('Japanese', u'日本語'),
|
||||
'por': ('Portuguese', 'Português'),
|
||||
}
|
||||
self.lang_map = {}
|
||||
for code, names in lm.iteritems():
|
||||
@ -505,6 +522,7 @@ class Amazon(Source):
|
||||
'it' : _('Italy'),
|
||||
'jp' : _('Japan'),
|
||||
'es' : _('Spain'),
|
||||
'br' : _('Brazil'),
|
||||
}
|
||||
|
||||
options = (
|
||||
@ -570,6 +588,8 @@ class Amazon(Source):
|
||||
url = 'http://amzn.com/'+asin
|
||||
elif domain == 'uk':
|
||||
url = 'http://www.amazon.co.uk/dp/'+asin
|
||||
elif domain == 'br':
|
||||
url = 'http://www.amazon.com.br/dp/'+asin
|
||||
else:
|
||||
url = 'http://www.amazon.%s/dp/%s'%(domain, asin)
|
||||
if url:
|
||||
@ -629,7 +649,7 @@ class Amazon(Source):
|
||||
q['field-isbn'] = isbn
|
||||
else:
|
||||
# Only return book results
|
||||
q['search-alias'] = 'stripbooks'
|
||||
q['search-alias'] = 'digital-text' if domain == 'br' else 'stripbooks'
|
||||
if title:
|
||||
title_tokens = list(self.get_title_tokens(title))
|
||||
if title_tokens:
|
||||
@ -661,6 +681,8 @@ class Amazon(Source):
|
||||
udomain = 'co.uk'
|
||||
elif domain == 'jp':
|
||||
udomain = 'co.jp'
|
||||
elif domain == 'br':
|
||||
udomain = 'com.br'
|
||||
url = 'http://www.amazon.%s/s/?'%udomain + urlencode(encoded_q)
|
||||
return url, domain
|
||||
|
||||
@ -978,6 +1000,16 @@ if __name__ == '__main__': # tests {{{
|
||||
),
|
||||
] # }}}
|
||||
|
||||
br_tests = [ # {{{
|
||||
(
|
||||
{'title':'Guerra dos Tronos'},
|
||||
[title_test('A Guerra dos Tronos - As Crônicas de Gelo e Fogo',
|
||||
exact=True), authors_test(['George R. R. Martin'])
|
||||
]
|
||||
|
||||
),
|
||||
] # }}}
|
||||
|
||||
def do_test(domain, start=0, stop=None):
|
||||
tests = globals().get(domain+'_tests')
|
||||
if stop is None:
|
||||
@ -988,7 +1020,7 @@ if __name__ == '__main__': # tests {{{
|
||||
|
||||
do_test('com')
|
||||
|
||||
#do_test('de')
|
||||
# do_test('de')
|
||||
|
||||
# }}}
|
||||
|
||||
|
@ -92,6 +92,31 @@ class BookIndexing
|
||||
this.last_check = [body.scrollWidth, body.scrollHeight]
|
||||
return ans
|
||||
|
||||
all_links_and_anchors: () ->
|
||||
body = document.body
|
||||
links = []
|
||||
anchors = {}
|
||||
for a in document.querySelectorAll("body a[href], body [id], body a[name]")
|
||||
if window.paged_display?.in_paged_mode
|
||||
geom = window.paged_display.column_location(a)
|
||||
else
|
||||
br = a.getBoundingClientRect()
|
||||
[left, top] = viewport_to_document(br.left, br.top, a.ownerDocument)
|
||||
geom = {'left':left, 'top':top, 'width':br.right-br.left, 'height':br.bottom-br.top}
|
||||
|
||||
href = a.getAttribute('href')
|
||||
if href
|
||||
links.push([href, geom])
|
||||
id = a.getAttribute("id")
|
||||
if id and id not in anchors
|
||||
anchors[id] = geom
|
||||
if a.tagName in ['A', "a"]
|
||||
name = a.getAttribute("name")
|
||||
if name and name not in anchors
|
||||
anchors[name] = geom
|
||||
|
||||
return {'links':links, 'anchors':anchors}
|
||||
|
||||
if window?
|
||||
window.book_indexing = new BookIndexing()
|
||||
|
||||
|
@ -242,6 +242,18 @@ class PagedDisplay
|
||||
# Return the number of the column that contains xpos
|
||||
return Math.floor(xpos/this.page_width)
|
||||
|
||||
column_location: (elem) ->
|
||||
# Return the location of elem relative to its containing column
|
||||
br = elem.getBoundingClientRect()
|
||||
[left, top] = calibre_utils.viewport_to_document(br.left, br.top, elem.ownerDocument)
|
||||
c = this.column_at(left)
|
||||
width = Math.min(br.right, (c+1)*this.page_width) - br.left
|
||||
if br.bottom < br.top
|
||||
br.bottom = window.innerHeight
|
||||
height = Math.min(br.bottom, window.innerHeight) - br.top
|
||||
left -= c*this.page_width
|
||||
return {'column':c, 'left':left, 'top':top, 'width':width, 'height':height}
|
||||
|
||||
column_boundaries: () ->
|
||||
# Return the column numbers at the left edge and after the right edge
|
||||
# of the viewport
|
||||
|
@ -18,6 +18,8 @@ inch = 72.0
|
||||
cm = inch / 2.54
|
||||
mm = cm * 0.1
|
||||
pica = 12.0
|
||||
didot = 0.375 * mm
|
||||
cicero = 12 * didot
|
||||
|
||||
_W, _H = (21*cm, 29.7*cm)
|
||||
|
||||
@ -41,6 +43,10 @@ B3 = (_BH*2, _BW)
|
||||
B2 = (_BW*2, _BH*2)
|
||||
B1 = (_BH*4, _BW*2)
|
||||
B0 = (_BW*4, _BH*4)
|
||||
|
||||
PAPER_SIZES = {k:globals()[k.upper()] for k in ('a0 a1 a2 a3 a4 a5 a6 b0 b1 b2'
|
||||
' b3 b4 b5 b6 letter legal').split()}
|
||||
|
||||
# }}}
|
||||
|
||||
# Basic PDF datatypes {{{
|
||||
@ -79,19 +85,12 @@ class String(unicode):
|
||||
raw = codecs.BOM_UTF16_BE + s.encode('utf-16-be')
|
||||
stream.write(b'('+raw+b')')
|
||||
|
||||
class GlyphIndex(object):
|
||||
|
||||
def __init__(self, code, compress):
|
||||
self.code = code
|
||||
self.compress = compress
|
||||
class GlyphIndex(int):
|
||||
|
||||
def pdf_serialize(self, stream):
|
||||
if self.compress:
|
||||
stream.write(pack(b'>sHs', b'(', self.code, b')'))
|
||||
else:
|
||||
byts = bytearray(pack(b'>H', self.code))
|
||||
stream.write('<%s>'%''.join(map(
|
||||
lambda x: bytes(hex(int(x))[2:]).rjust(2, b'0'), byts)))
|
||||
byts = bytearray(pack(b'>H', self))
|
||||
stream.write('<%s>'%''.join(map(
|
||||
lambda x: bytes(hex(x)[2:]).rjust(2, b'0'), byts)))
|
||||
|
||||
class Dictionary(dict):
|
||||
|
||||
@ -132,6 +131,7 @@ class Stream(BytesIO):
|
||||
def __init__(self, compress=False):
|
||||
BytesIO.__init__(self)
|
||||
self.compress = compress
|
||||
self.filters = Array()
|
||||
|
||||
def add_extra_keys(self, d):
|
||||
pass
|
||||
@ -139,7 +139,7 @@ class Stream(BytesIO):
|
||||
def pdf_serialize(self, stream):
|
||||
raw = self.getvalue()
|
||||
dl = len(raw)
|
||||
filters = Array()
|
||||
filters = self.filters
|
||||
if self.compress:
|
||||
filters.append(Name('FlateDecode'))
|
||||
raw = zlib.compress(raw)
|
||||
|
@ -7,23 +7,22 @@ __license__ = 'GPL v3'
|
||||
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import sys, traceback, unicodedata
|
||||
import sys, traceback
|
||||
from math import sqrt
|
||||
from collections import namedtuple
|
||||
from functools import wraps
|
||||
from functools import wraps, partial
|
||||
|
||||
import sip
|
||||
from PyQt4.Qt import (QPaintEngine, QPaintDevice, Qt, QApplication, QPainter,
|
||||
QTransform, QPainterPath, QTextOption, QTextLayout)
|
||||
QTransform, QPainterPath, QImage, QByteArray, QBuffer,
|
||||
qRgba)
|
||||
|
||||
from calibre.constants import DEBUG
|
||||
from calibre.constants import plugins
|
||||
from calibre.ebooks.pdf.render.serialize import (Color, PDFStream, Path)
|
||||
from calibre.ebooks.pdf.render.common import inch, A4
|
||||
from calibre.utils.fonts.sfnt.container import Sfnt
|
||||
from calibre.utils.fonts.sfnt.metrics import FontMetrics
|
||||
|
||||
XDPI = 1200
|
||||
YDPI = 1200
|
||||
|
||||
Point = namedtuple('Point', 'x y')
|
||||
ColorState = namedtuple('ColorState', 'color opacity do')
|
||||
|
||||
@ -34,7 +33,8 @@ def store_error(func):
|
||||
try:
|
||||
func(self, *args, **kwargs)
|
||||
except:
|
||||
self.errors.append(traceback.format_exc())
|
||||
self.errors_occurred = True
|
||||
self.errors(traceback.format_exc())
|
||||
|
||||
return errh
|
||||
|
||||
@ -42,7 +42,7 @@ class GraphicsState(object): # {{{
|
||||
|
||||
def __init__(self):
|
||||
self.ops = {}
|
||||
self.current_state = self.initial_state = {
|
||||
self.initial_state = {
|
||||
'fill': ColorState(Color(0., 0., 0., 1.), 1.0, False),
|
||||
'transform': QTransform(),
|
||||
'dash': [],
|
||||
@ -52,9 +52,10 @@ class GraphicsState(object): # {{{
|
||||
'line_join': 'miter',
|
||||
'clip': (Qt.NoClip, QPainterPath()),
|
||||
}
|
||||
self.current_state = self.initial_state.copy()
|
||||
|
||||
def reset(self):
|
||||
self.current_state = self.initial_state
|
||||
self.current_state = self.initial_state.copy()
|
||||
|
||||
def update_color_state(self, which, color=None, opacity=None,
|
||||
brush_style=None, pen_style=None):
|
||||
@ -77,7 +78,6 @@ class GraphicsState(object): # {{{
|
||||
self.ops[which] = n
|
||||
|
||||
def read(self, state):
|
||||
self.ops = {}
|
||||
flags = state.state()
|
||||
|
||||
if flags & QPaintEngine.DirtyTransform:
|
||||
@ -109,15 +109,12 @@ class GraphicsState(object): # {{{
|
||||
self.update_color_state('fill', opacity=state.opacity())
|
||||
self.update_color_state('stroke', opacity=state.opacity())
|
||||
|
||||
if flags & QPaintEngine.DirtyClipPath:
|
||||
self.ops['clip'] = (state.clipOperation(), state.clipPath())
|
||||
elif flags & QPaintEngine.DirtyClipRegion:
|
||||
path = QPainterPath()
|
||||
for rect in state.clipRegion().rects():
|
||||
path.addRect(rect)
|
||||
self.ops['clip'] = (state.clipOperation(), path)
|
||||
if flags & QPaintEngine.DirtyClipPath or flags & QPaintEngine.DirtyClipRegion:
|
||||
self.ops['clip'] = True
|
||||
|
||||
def __call__(self, engine):
|
||||
if not self.ops:
|
||||
return
|
||||
pdf = engine.pdf
|
||||
ops = self.ops
|
||||
current_transform = self.current_state['transform']
|
||||
@ -127,58 +124,34 @@ class GraphicsState(object): # {{{
|
||||
if reset_stack:
|
||||
pdf.restore_stack()
|
||||
pdf.save_stack()
|
||||
|
||||
# We apply clip before transform as the clip may have to be merged with
|
||||
# the previous clip path so it is easiest to work with clips that are
|
||||
# pre-transformed
|
||||
prev_op, prev_clip_path = self.current_state['clip']
|
||||
if 'clip' in ops:
|
||||
op, path = ops['clip']
|
||||
self.current_state['clip'] = (op, path)
|
||||
transform = ops.get('transform', QTransform())
|
||||
if not transform.isIdentity() and path is not None:
|
||||
# Pre transform the clip path
|
||||
path = current_transform.map(path)
|
||||
self.current_state['clip'] = (op, path)
|
||||
|
||||
if op == Qt.ReplaceClip:
|
||||
pass
|
||||
elif op == Qt.IntersectClip:
|
||||
if prev_op != Qt.NoClip:
|
||||
self.current_state['clip'] = (op, path.intersected(prev_clip_path))
|
||||
elif op == Qt.UniteClip:
|
||||
if prev_clip_path is not None:
|
||||
path.addPath(prev_clip_path)
|
||||
else:
|
||||
self.current_state['clip'] = (Qt.NoClip, QPainterPath())
|
||||
op, path = self.current_state['clip']
|
||||
if op != Qt.NoClip:
|
||||
engine.add_clip(path)
|
||||
elif reset_stack and prev_op != Qt.NoClip:
|
||||
# Re-apply the previous clip path since no clipping operation was
|
||||
# specified
|
||||
engine.add_clip(prev_clip_path)
|
||||
|
||||
if reset_stack:
|
||||
# Since we have reset the stack we need to re-apply all previous
|
||||
# operations, that are different from the default value (clip is
|
||||
# handled separately).
|
||||
for op in set(self.current_state) - (set(ops)|{'clip'}):
|
||||
if self.current_state[op] != self.initial_state[op]:
|
||||
for op in set(self.initial_state) - {'clip'}:
|
||||
if op in ops: # These will be applied below
|
||||
self.current_state[op] = self.initial_state[op]
|
||||
elif self.current_state[op] != self.initial_state[op]:
|
||||
self.apply(op, self.current_state[op], engine, pdf)
|
||||
|
||||
# Now apply the new operations
|
||||
for op, val in ops.iteritems():
|
||||
if op != 'clip':
|
||||
if op != 'clip' and self.current_state[op] != val:
|
||||
self.apply(op, val, engine, pdf)
|
||||
self.current_state[op] = val
|
||||
|
||||
if 'clip' in ops:
|
||||
# Get the current clip
|
||||
path = engine.painter().clipPath()
|
||||
if not path.isEmpty():
|
||||
engine.add_clip(path)
|
||||
self.ops = {}
|
||||
|
||||
def apply(self, op, val, engine, pdf):
|
||||
getattr(self, 'apply_'+op)(val, engine, pdf)
|
||||
|
||||
def apply_transform(self, val, engine, pdf):
|
||||
engine.qt_system = val
|
||||
pdf.transform(val)
|
||||
if not val.isIdentity():
|
||||
pdf.transform(val)
|
||||
|
||||
def apply_stroke(self, val, engine, pdf):
|
||||
self.apply_color_state('stroke', val, engine, pdf)
|
||||
@ -214,9 +187,11 @@ class Font(FontMetrics):
|
||||
class PdfEngine(QPaintEngine):
|
||||
|
||||
def __init__(self, file_object, page_width, page_height, left_margin,
|
||||
top_margin, right_margin, bottom_margin, width, height):
|
||||
top_margin, right_margin, bottom_margin, width, height,
|
||||
errors=print, debug=print, compress=True):
|
||||
QPaintEngine.__init__(self, self.features)
|
||||
self.file_object = file_object
|
||||
self.compress = compress
|
||||
self.page_height, self.page_width = page_height, page_width
|
||||
self.left_margin, self.top_margin = left_margin, top_margin
|
||||
self.right_margin, self.bottom_margin = right_margin, bottom_margin
|
||||
@ -235,26 +210,33 @@ class PdfEngine(QPaintEngine):
|
||||
self.bottom_margin) / self.pixel_height
|
||||
|
||||
self.pdf_system = QTransform(sx, 0, 0, -sy, dx, dy)
|
||||
self.qt_system = QTransform()
|
||||
self.do_stroke = True
|
||||
self.do_fill = False
|
||||
self.scale = sqrt(sy**2 + sx**2)
|
||||
self.xscale, self.yscale = sx, sy
|
||||
self.graphics_state = GraphicsState()
|
||||
self.errors, self.debug = [], []
|
||||
self.text_option = QTextOption()
|
||||
self.text_option.setWrapMode(QTextOption.NoWrap)
|
||||
self.errors_occurred = False
|
||||
self.errors, self.debug = errors, debug
|
||||
self.fonts = {}
|
||||
i = QImage(1, 1, QImage.Format_ARGB32)
|
||||
i.fill(qRgba(0, 0, 0, 255))
|
||||
self.alpha_bit = i.constBits().asstring(4).find(b'\xff')
|
||||
self.current_page_num = 1
|
||||
self.current_page_inited = False
|
||||
self.qt_hack, err = plugins['qt_hack']
|
||||
if err:
|
||||
raise RuntimeError('Failed to load qt_hack with err: %s'%err)
|
||||
|
||||
def init_page(self):
|
||||
self.pdf.transform(self.pdf_system)
|
||||
self.pdf.set_rgb_colorspace()
|
||||
width = self.painter.pen().widthF() if self.isActive() else 0
|
||||
width = self.painter().pen().widthF() if self.isActive() else 0
|
||||
self.pdf.set_line_width(width)
|
||||
self.do_stroke = True
|
||||
self.do_fill = False
|
||||
self.graphics_state.reset()
|
||||
self.pdf.save_stack()
|
||||
self.current_page_inited = True
|
||||
|
||||
@property
|
||||
def features(self):
|
||||
@ -264,25 +246,26 @@ class PdfEngine(QPaintEngine):
|
||||
QPaintEngine.PrimitiveTransform)
|
||||
|
||||
def begin(self, device):
|
||||
try:
|
||||
self.pdf = PDFStream(self.file_object, (self.page_width,
|
||||
self.page_height),
|
||||
compress=not DEBUG)
|
||||
self.init_page()
|
||||
except:
|
||||
self.errors.append(traceback.format_exc())
|
||||
return False
|
||||
if not hasattr(self, 'pdf'):
|
||||
try:
|
||||
self.pdf = PDFStream(self.file_object, (self.page_width,
|
||||
self.page_height),
|
||||
compress=self.compress)
|
||||
except:
|
||||
self.errors.append(traceback.format_exc())
|
||||
return False
|
||||
return True
|
||||
|
||||
def end_page(self, start_new=True):
|
||||
self.pdf.restore_stack()
|
||||
self.pdf.end_page()
|
||||
if start_new:
|
||||
self.init_page()
|
||||
def end_page(self):
|
||||
if self.current_page_inited:
|
||||
self.pdf.restore_stack()
|
||||
self.pdf.end_page()
|
||||
self.current_page_inited = False
|
||||
self.current_page_num += 1
|
||||
|
||||
def end(self):
|
||||
try:
|
||||
self.end_page(start_new=False)
|
||||
self.end_page()
|
||||
self.pdf.end()
|
||||
except:
|
||||
self.errors.append(traceback.format_exc())
|
||||
@ -296,16 +279,94 @@ class PdfEngine(QPaintEngine):
|
||||
|
||||
@store_error
|
||||
def drawPixmap(self, rect, pixmap, source_rect):
|
||||
print ('TODO: drawPixmap() currently unimplemented')
|
||||
self.graphics_state(self)
|
||||
source_rect = source_rect.toRect()
|
||||
pixmap = (pixmap if source_rect == pixmap.rect() else
|
||||
pixmap.copy(source_rect))
|
||||
image = pixmap.toImage()
|
||||
ref = self.add_image(image, pixmap.cacheKey())
|
||||
if ref is not None:
|
||||
self.pdf.draw_image(rect.x(), rect.height()+rect.y(), rect.width(),
|
||||
-rect.height(), ref)
|
||||
|
||||
@store_error
|
||||
def drawImage(self, rect, image, source_rect, flags=Qt.AutoColor):
|
||||
print ('TODO: drawImage() currently unimplemented')
|
||||
self.graphics_state(self)
|
||||
source_rect = source_rect.toRect()
|
||||
image = (image if source_rect == image.rect() else
|
||||
image.copy(source_rect))
|
||||
ref = self.add_image(image, image.cacheKey())
|
||||
if ref is not None:
|
||||
self.pdf.draw_image(rect.x(), rect.height()+rect.y(), rect.width(),
|
||||
-rect.height(), ref)
|
||||
|
||||
def add_image(self, img, cache_key):
|
||||
if img.isNull(): return
|
||||
ref = self.pdf.get_image(cache_key)
|
||||
if ref is not None:
|
||||
return ref
|
||||
|
||||
fmt = img.format()
|
||||
image = QImage(img)
|
||||
if (image.depth() == 1 and img.colorTable().size() == 2 and
|
||||
img.colorTable().at(0) == QColor(Qt.black).rgba() and
|
||||
img.colorTable().at(1) == QColor(Qt.white).rgba()):
|
||||
if fmt == QImage.Format_MonoLSB:
|
||||
image = image.convertToFormat(QImage.Format_Mono)
|
||||
fmt = QImage.Format_Mono
|
||||
else:
|
||||
if (fmt != QImage.Format_RGB32 and fmt != QImage.Format_ARGB32):
|
||||
image = image.convertToFormat(QImage.Format_ARGB32)
|
||||
fmt = QImage.Format_ARGB32
|
||||
|
||||
w = image.width()
|
||||
h = image.height()
|
||||
d = image.depth()
|
||||
|
||||
if fmt == QImage.Format_Mono:
|
||||
bytes_per_line = (w + 7) >> 3
|
||||
data = image.constBits().asstring(bytes_per_line * h)
|
||||
return self.pdf.write_image(data, w, h, d, cache_key=cache_key)
|
||||
|
||||
ba = QByteArray()
|
||||
buf = QBuffer(ba)
|
||||
image.save(buf, 'jpeg', 94)
|
||||
data = bytes(ba.data())
|
||||
has_alpha = has_mask = False
|
||||
soft_mask = mask = None
|
||||
|
||||
if fmt == QImage.Format_ARGB32:
|
||||
tmask = image.constBits().asstring(4*w*h)[self.alpha_bit::4]
|
||||
sdata = bytearray(tmask)
|
||||
vals = set(sdata)
|
||||
vals.discard(255)
|
||||
has_mask = bool(vals)
|
||||
vals.discard(0)
|
||||
has_alpha = bool(vals)
|
||||
|
||||
if has_alpha:
|
||||
soft_mask = self.pdf.write_image(tmask, w, h, 8)
|
||||
elif has_mask:
|
||||
# dither the soft mask to 1bit and add it. This also helps PDF
|
||||
# viewers without transparency support
|
||||
bytes_per_line = (w + 7) >> 3
|
||||
mdata = bytearray(0 for i in xrange(bytes_per_line * h))
|
||||
spos = mpos = 0
|
||||
for y in xrange(h):
|
||||
for x in xrange(w):
|
||||
if sdata[spos]:
|
||||
mdata[mpos + x>>3] |= (0x80 >> (x&7))
|
||||
spos += 1
|
||||
mpos += bytes_per_line
|
||||
mdata = bytes(mdata)
|
||||
mask = self.pdf.write_image(mdata, w, h, 1)
|
||||
|
||||
return self.pdf.write_image(data, w, h, 32, mask=mask, dct=True,
|
||||
soft_mask=soft_mask, cache_key=cache_key)
|
||||
|
||||
@store_error
|
||||
def updateState(self, state):
|
||||
self.graphics_state.read(state)
|
||||
self.graphics_state(self)
|
||||
|
||||
def convert_path(self, path):
|
||||
p = Path()
|
||||
@ -333,6 +394,7 @@ class PdfEngine(QPaintEngine):
|
||||
|
||||
@store_error
|
||||
def drawPath(self, path):
|
||||
self.graphics_state(self)
|
||||
p = self.convert_path(path)
|
||||
fill_rule = {Qt.OddEvenFill:'evenodd',
|
||||
Qt.WindingFill:'winding'}[path.fillRule()]
|
||||
@ -347,6 +409,7 @@ class PdfEngine(QPaintEngine):
|
||||
|
||||
@store_error
|
||||
def drawPoints(self, points):
|
||||
self.graphics_state(self)
|
||||
p = Path()
|
||||
for point in points:
|
||||
p.move_to(point.x(), point.y())
|
||||
@ -355,105 +418,58 @@ class PdfEngine(QPaintEngine):
|
||||
|
||||
@store_error
|
||||
def drawRects(self, rects):
|
||||
self.graphics_state(self)
|
||||
for rect in rects:
|
||||
bl = rect.topLeft()
|
||||
self.pdf.draw_rect(bl.x(), bl.y(), rect.width(), rect.height(),
|
||||
stroke=self.do_stroke, fill=self.do_fill)
|
||||
|
||||
def get_text_layout(self, text_item, text):
|
||||
tl = QTextLayout(text, text_item.font(), self.paintDevice())
|
||||
self.text_option.setTextDirection(Qt.RightToLeft if
|
||||
text_item.renderFlags() & text_item.RightToLeft else Qt.LeftToRight)
|
||||
tl.setTextOption(self.text_option)
|
||||
return tl
|
||||
|
||||
def update_glyph_map(self, text, indices, text_item, glyph_map):
|
||||
'''
|
||||
Map glyphs back to the unicode text they represent.
|
||||
'''
|
||||
pos = 0
|
||||
tl = self.get_text_layout(text_item, '')
|
||||
indices = list(indices)
|
||||
|
||||
def get_glyphs(string):
|
||||
tl.setText(string)
|
||||
tl.beginLayout()
|
||||
line = tl.createLine()
|
||||
if not line.isValid():
|
||||
tl.endLayout()
|
||||
return []
|
||||
line.setLineWidth(int(1e12))
|
||||
tl.endLayout()
|
||||
ans = []
|
||||
for run in tl.glyphRuns():
|
||||
ans.extend(run.glyphIndexes())
|
||||
return ans
|
||||
|
||||
ipos = 0
|
||||
while ipos < len(indices):
|
||||
if indices[ipos] in glyph_map:
|
||||
t = glyph_map[indices[ipos]]
|
||||
if t == text[pos:pos+len(t)]:
|
||||
pos += len(t)
|
||||
ipos += 1
|
||||
continue
|
||||
|
||||
found = False
|
||||
for l in xrange(1, 10):
|
||||
string = text[pos:pos+l]
|
||||
g = get_glyphs(string)
|
||||
if g and g[0] == indices[ipos]:
|
||||
found = True
|
||||
glyph_map[g[0]] = string
|
||||
break
|
||||
if not found:
|
||||
self.debug.append(
|
||||
'Failed to find glyph->unicode mapping for text: %s'%text)
|
||||
break
|
||||
ipos += 1
|
||||
pos += l
|
||||
|
||||
return text[pos:]
|
||||
def create_sfnt(self, text_item):
|
||||
get_table = partial(self.qt_hack.get_sfnt_table, text_item)
|
||||
ans = Font(Sfnt(get_table))
|
||||
glyph_map = self.qt_hack.get_glyph_map(text_item)
|
||||
gm = {}
|
||||
for uc, glyph_id in enumerate(glyph_map):
|
||||
if glyph_id not in gm:
|
||||
gm[glyph_id] = unichr(uc)
|
||||
ans.full_glyph_map = gm
|
||||
return ans
|
||||
|
||||
@store_error
|
||||
def drawTextItem(self, point, text_item):
|
||||
# super(PdfEngine, self).drawTextItem(point+QPoint(0, 0), text_item)
|
||||
text = type(u'')(text_item.text()).replace('\n', ' ')
|
||||
text = unicodedata.normalize('NFKC', text)
|
||||
tl = self.get_text_layout(text_item, text)
|
||||
tl.setPosition(point)
|
||||
tl.beginLayout()
|
||||
line = tl.createLine()
|
||||
if not line.isValid():
|
||||
tl.endLayout()
|
||||
# super(PdfEngine, self).drawTextItem(point, text_item)
|
||||
self.graphics_state(self)
|
||||
gi = self.qt_hack.get_glyphs(point, text_item)
|
||||
if not gi.indices:
|
||||
sip.delete(gi)
|
||||
return
|
||||
line.setLineWidth(int(1e12))
|
||||
tl.endLayout()
|
||||
for run in tl.glyphRuns():
|
||||
rf = run.rawFont()
|
||||
name = hash(bytes(rf.fontTable('name')))
|
||||
if name not in self.fonts:
|
||||
self.fonts[name] = Font(Sfnt(rf))
|
||||
metrics = self.fonts[name]
|
||||
indices = run.glyphIndexes()
|
||||
text = self.update_glyph_map(text, indices, text_item, metrics.glyph_map)
|
||||
glyphs = []
|
||||
pdf_pos = point
|
||||
first_baseline = None
|
||||
for i, pos in enumerate(run.positions()):
|
||||
if first_baseline is None:
|
||||
first_baseline = pos.y()
|
||||
glyph_pos = point + pos
|
||||
delta = glyph_pos - pdf_pos
|
||||
glyphs.append((delta.x(), pos.y()-first_baseline, indices[i]))
|
||||
pdf_pos = glyph_pos
|
||||
|
||||
self.pdf.draw_glyph_run([1, 0, 0, -1, point.x(),
|
||||
point.y()], rf.pixelSize(), metrics, glyphs)
|
||||
name = hash(bytes(gi.name))
|
||||
if name not in self.fonts:
|
||||
self.fonts[name] = self.create_sfnt(text_item)
|
||||
metrics = self.fonts[name]
|
||||
for glyph_id in gi.indices:
|
||||
try:
|
||||
metrics.glyph_map[glyph_id] = metrics.full_glyph_map[glyph_id]
|
||||
except (KeyError, ValueError):
|
||||
pass
|
||||
glyphs = []
|
||||
pdf_pos = point
|
||||
first_baseline = None
|
||||
for i, pos in enumerate(gi.positions):
|
||||
if first_baseline is None:
|
||||
first_baseline = pos.y()
|
||||
glyph_pos = pos
|
||||
delta = glyph_pos - pdf_pos
|
||||
glyphs.append((delta.x(), pos.y()-first_baseline, gi.indices[i]))
|
||||
pdf_pos = glyph_pos
|
||||
|
||||
self.pdf.draw_glyph_run([1, 0, 0, -1, point.x(),
|
||||
point.y()], gi.size, metrics, glyphs)
|
||||
sip.delete(gi)
|
||||
|
||||
@store_error
|
||||
def drawPolygon(self, points, mode):
|
||||
self.graphics_state(self)
|
||||
if not points: return
|
||||
p = Path()
|
||||
p.move_to(points[0].x(), points[0].y())
|
||||
@ -465,6 +481,9 @@ class PdfEngine(QPaintEngine):
|
||||
self.pdf.draw_path(p, stroke=True, fill_rule=fill_rule,
|
||||
fill=(mode in (self.OddEvenMode, self.WindingMode, self.ConvexMode)))
|
||||
|
||||
def set_metadata(self, *args, **kwargs):
|
||||
self.pdf.set_metadata(*args, **kwargs)
|
||||
|
||||
def __enter__(self):
|
||||
self.pdf.save_stack()
|
||||
self.saved_ps = (self.do_stroke, self.do_fill)
|
||||
@ -477,23 +496,26 @@ class PdfDevice(QPaintDevice): # {{{
|
||||
|
||||
|
||||
def __init__(self, file_object, page_size=A4, left_margin=inch,
|
||||
top_margin=inch, right_margin=inch, bottom_margin=inch):
|
||||
top_margin=inch, right_margin=inch, bottom_margin=inch,
|
||||
xdpi=1200, ydpi=1200, errors=print, debug=print, compress=True):
|
||||
QPaintDevice.__init__(self)
|
||||
self.xdpi, self.ydpi = xdpi, ydpi
|
||||
self.page_width, self.page_height = page_size
|
||||
self.body_width = self.page_width - left_margin - right_margin
|
||||
self.body_height = self.page_height - top_margin - bottom_margin
|
||||
self.engine = PdfEngine(file_object, self.page_width, self.page_height,
|
||||
left_margin, top_margin, right_margin,
|
||||
bottom_margin, self.width(), self.height())
|
||||
bottom_margin, self.width(), self.height(),
|
||||
errors=errors, debug=debug, compress=compress)
|
||||
|
||||
def paintEngine(self):
|
||||
return self.engine
|
||||
|
||||
def metric(self, m):
|
||||
if m in (self.PdmDpiX, self.PdmPhysicalDpiX):
|
||||
return XDPI
|
||||
return self.xdpi
|
||||
if m in (self.PdmDpiY, self.PdmPhysicalDpiY):
|
||||
return YDPI
|
||||
return self.ydpi
|
||||
if m == self.PdmDepth:
|
||||
return 32
|
||||
if m == self.PdmNumColors:
|
||||
@ -503,20 +525,43 @@ class PdfDevice(QPaintDevice): # {{{
|
||||
if m == self.PdmHeightMM:
|
||||
return int(round(self.body_height * 0.35277777777778))
|
||||
if m == self.PdmWidth:
|
||||
return int(round(self.body_width * XDPI / 72.0))
|
||||
return int(round(self.body_width * self.xdpi / 72.0))
|
||||
if m == self.PdmHeight:
|
||||
return int(round(self.body_height * YDPI / 72.0))
|
||||
return int(round(self.body_height * self.ydpi / 72.0))
|
||||
return 0
|
||||
|
||||
def end_page(self):
|
||||
self.engine.end_page()
|
||||
|
||||
def init_page(self):
|
||||
self.engine.init_page()
|
||||
|
||||
@property
|
||||
def current_page_num(self):
|
||||
return self.engine.current_page_num
|
||||
|
||||
@property
|
||||
def errors_occurred(self):
|
||||
return self.engine.errors_occurred
|
||||
|
||||
def to_px(self, pt, vertical=True):
|
||||
return pt * (self.height()/self.page_height if vertical else
|
||||
self.width()/self.page_width)
|
||||
|
||||
def set_metadata(self, *args, **kwargs):
|
||||
self.engine.set_metadata(*args, **kwargs)
|
||||
|
||||
# }}}
|
||||
|
||||
if __name__ == '__main__':
|
||||
from PyQt4.Qt import (QBrush, QColor, QPoint)
|
||||
QBrush, QColor, QPoint
|
||||
from PyQt4.Qt import (QBrush, QColor, QPoint, QPixmap)
|
||||
QBrush, QColor, QPoint, QPixmap
|
||||
app = QApplication([])
|
||||
p = QPainter()
|
||||
with open('/tmp/painter.pdf', 'wb') as f:
|
||||
dev = PdfDevice(f)
|
||||
dev = PdfDevice(f, compress=False)
|
||||
p.begin(dev)
|
||||
dev.init_page()
|
||||
xmax, ymax = p.viewport().width(), p.viewport().height()
|
||||
try:
|
||||
p.drawRect(0, 0, xmax, ymax)
|
||||
@ -536,6 +581,11 @@ if __name__ == '__main__':
|
||||
# p.scale(1, 1.5)
|
||||
# p.restore()
|
||||
|
||||
# # p.scale(2, 2)
|
||||
# # p.rotate(45)
|
||||
# p.drawPixmap(0, 0, 2048, 2048, QPixmap(I('library.png')))
|
||||
# p.drawRect(0, 0, 2048, 2048)
|
||||
|
||||
# p.save()
|
||||
# p.drawLine(0, 0, 5000, 0)
|
||||
# p.rotate(45)
|
||||
@ -543,22 +593,19 @@ if __name__ == '__main__':
|
||||
# p.restore()
|
||||
|
||||
f = p.font()
|
||||
f.setPointSize(24)
|
||||
f.setPointSize(20)
|
||||
# f.setLetterSpacing(f.PercentageSpacing, 200)
|
||||
# f.setUnderline(True)
|
||||
# f.setOverline(True)
|
||||
# f.setStrikeOut(True)
|
||||
f.setFamily('Calibri')
|
||||
p.setFont(f)
|
||||
# p.setPen(QColor(0, 0, 255))
|
||||
# p.scale(2, 2)
|
||||
# p.rotate(45)
|
||||
# p.setPen(QColor(0, 0, 255))
|
||||
p.drawText(QPoint(100, 300), 'Some text ū --- Д AV ff ff')
|
||||
p.drawText(QPoint(300, 300), 'Some—text not By’s ū --- Д AV ff ff')
|
||||
finally:
|
||||
p.end()
|
||||
for line in dev.engine.debug:
|
||||
print (line)
|
||||
if dev.engine.errors:
|
||||
for err in dev.engine.errors: print (err)
|
||||
if dev.engine.errors_occurred:
|
||||
raise SystemExit(1)
|
||||
|
||||
|
280
src/calibre/ebooks/pdf/render/from_html.py
Normal file
280
src/calibre/ebooks/pdf/render/from_html.py
Normal file
@ -0,0 +1,280 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import json, os
|
||||
from future_builtins import map
|
||||
from math import floor
|
||||
|
||||
from PyQt4.Qt import (QObject, QPainter, Qt, QSize, QString, QTimer,
|
||||
pyqtProperty, QEventLoop, QPixmap, QRect)
|
||||
from PyQt4.QtWebKit import QWebView, QWebPage, QWebSettings
|
||||
|
||||
from calibre import fit_image
|
||||
from calibre.ebooks.oeb.display.webview import load_html
|
||||
from calibre.ebooks.pdf.render.common import (inch, cm, mm, pica, cicero,
|
||||
didot, PAPER_SIZES)
|
||||
from calibre.ebooks.pdf.render.engine import PdfDevice
|
||||
from calibre.ebooks.pdf.render.links import Links
|
||||
|
||||
def get_page_size(opts, for_comic=False): # {{{
|
||||
use_profile = not (opts.override_profile_size or
|
||||
opts.output_profile.short_name == 'default' or
|
||||
opts.output_profile.width > 9999)
|
||||
if use_profile:
|
||||
w = (opts.output_profile.comic_screen_size[0] if for_comic else
|
||||
opts.output_profile.width)
|
||||
h = (opts.output_profile.comic_screen_size[1] if for_comic else
|
||||
opts.output_profile.height)
|
||||
dpi = opts.output_profile.dpi
|
||||
factor = 72.0 / dpi
|
||||
page_size = (factor * w, factor * h)
|
||||
else:
|
||||
page_size = None
|
||||
if opts.custom_size != None:
|
||||
width, sep, height = opts.custom_size.partition('x')
|
||||
if height:
|
||||
try:
|
||||
width = float(width)
|
||||
height = float(height)
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
if opts.unit == 'devicepixel':
|
||||
factor = 72.0 / opts.output_profile.dpi
|
||||
else:
|
||||
{'point':1.0, 'inch':inch, 'cicero':cicero,
|
||||
'didot':didot, 'pica':pica, 'millimeter':mm,
|
||||
'centimeter':cm}[opts.unit]
|
||||
page_size = (factor*width, factor*height)
|
||||
if page_size is None:
|
||||
page_size = PAPER_SIZES[opts.paper_size]
|
||||
return page_size
|
||||
# }}}
|
||||
|
||||
class Page(QWebPage): # {{{
|
||||
|
||||
def __init__(self, opts, log):
|
||||
self.log = log
|
||||
QWebPage.__init__(self)
|
||||
settings = self.settings()
|
||||
settings.setFontSize(QWebSettings.DefaultFontSize,
|
||||
opts.pdf_default_font_size)
|
||||
settings.setFontSize(QWebSettings.DefaultFixedFontSize,
|
||||
opts.pdf_mono_font_size)
|
||||
settings.setFontSize(QWebSettings.MinimumLogicalFontSize, 8)
|
||||
settings.setFontSize(QWebSettings.MinimumFontSize, 8)
|
||||
|
||||
std = {'serif':opts.pdf_serif_family, 'sans':opts.pdf_sans_family,
|
||||
'mono':opts.pdf_mono_family}.get(opts.pdf_standard_font,
|
||||
opts.pdf_serif_family)
|
||||
if std:
|
||||
settings.setFontFamily(QWebSettings.StandardFont, std)
|
||||
if opts.pdf_serif_family:
|
||||
settings.setFontFamily(QWebSettings.SerifFont, opts.pdf_serif_family)
|
||||
if opts.pdf_sans_family:
|
||||
settings.setFontFamily(QWebSettings.SansSerifFont,
|
||||
opts.pdf_sans_family)
|
||||
if opts.pdf_mono_family:
|
||||
settings.setFontFamily(QWebSettings.FixedFont, opts.pdf_mono_family)
|
||||
|
||||
def javaScriptConsoleMessage(self, msg, lineno, msgid):
|
||||
self.log.debug(u'JS:', unicode(msg))
|
||||
|
||||
def javaScriptAlert(self, frame, msg):
|
||||
self.log(unicode(msg))
|
||||
# }}}
|
||||
|
||||
def draw_image_page(page_rect, painter, p, preserve_aspect_ratio=True):
|
||||
if preserve_aspect_ratio:
|
||||
aspect_ratio = float(p.width())/p.height()
|
||||
nw, nh = page_rect.width(), page_rect.height()
|
||||
if aspect_ratio > 1:
|
||||
nh = int(page_rect.width()/aspect_ratio)
|
||||
else: # Width is smaller than height
|
||||
nw = page_rect.height()*aspect_ratio
|
||||
__, nnw, nnh = fit_image(nw, nh, page_rect.width(),
|
||||
page_rect.height())
|
||||
dx = int((page_rect.width() - nnw)/2.)
|
||||
dy = int((page_rect.height() - nnh)/2.)
|
||||
page_rect.moveTo(dx, dy)
|
||||
page_rect.setHeight(nnh)
|
||||
page_rect.setWidth(nnw)
|
||||
painter.drawPixmap(page_rect, p, p.rect())
|
||||
|
||||
class PDFWriter(QObject):
|
||||
|
||||
def _pass_json_value_getter(self):
|
||||
val = json.dumps(self.bridge_value)
|
||||
return QString(val)
|
||||
|
||||
def _pass_json_value_setter(self, value):
|
||||
self.bridge_value = json.loads(unicode(value))
|
||||
|
||||
_pass_json_value = pyqtProperty(QString, fget=_pass_json_value_getter,
|
||||
fset=_pass_json_value_setter)
|
||||
|
||||
def __init__(self, opts, log, cover_data=None, toc=None):
|
||||
from calibre.gui2 import is_ok_to_use_qt
|
||||
if not is_ok_to_use_qt():
|
||||
raise Exception('Not OK to use Qt')
|
||||
QObject.__init__(self)
|
||||
|
||||
self.logger = self.log = log
|
||||
self.opts = opts
|
||||
self.cover_data = cover_data
|
||||
self.paged_js = None
|
||||
self.toc = toc
|
||||
|
||||
self.loop = QEventLoop()
|
||||
self.view = QWebView()
|
||||
self.page = Page(opts, self.log)
|
||||
self.view.setPage(self.page)
|
||||
self.view.setRenderHints(QPainter.Antialiasing|
|
||||
QPainter.TextAntialiasing|QPainter.SmoothPixmapTransform)
|
||||
self.view.loadFinished.connect(self.render_html,
|
||||
type=Qt.QueuedConnection)
|
||||
for x in (Qt.Horizontal, Qt.Vertical):
|
||||
self.view.page().mainFrame().setScrollBarPolicy(x,
|
||||
Qt.ScrollBarAlwaysOff)
|
||||
self.report_progress = lambda x, y: x
|
||||
self.links = Links()
|
||||
|
||||
def dump(self, items, out_stream, pdf_metadata):
|
||||
opts = self.opts
|
||||
page_size = get_page_size(self.opts)
|
||||
xdpi, ydpi = self.view.logicalDpiX(), self.view.logicalDpiY()
|
||||
ml, mr = opts.margin_left, opts.margin_right
|
||||
margin_side = min(ml, mr)
|
||||
ml, mr = ml - margin_side, mr - margin_side
|
||||
self.doc = PdfDevice(out_stream, page_size=page_size, left_margin=ml,
|
||||
top_margin=0, right_margin=mr, bottom_margin=0,
|
||||
xdpi=xdpi, ydpi=ydpi, errors=self.log.error,
|
||||
debug=self.log.debug, compress=not
|
||||
opts.uncompressed_pdf)
|
||||
|
||||
self.page.setViewportSize(QSize(self.doc.width(), self.doc.height()))
|
||||
self.render_queue = items
|
||||
self.total_items = len(items)
|
||||
|
||||
mt, mb = map(self.doc.to_px, (opts.margin_top, opts.margin_bottom))
|
||||
ms = self.doc.to_px(margin_side, vertical=False)
|
||||
self.margin_top, self.margin_size, self.margin_bottom = map(
|
||||
lambda x:int(floor(x)), (mt, ms, mb))
|
||||
|
||||
self.painter = QPainter(self.doc)
|
||||
self.doc.set_metadata(title=pdf_metadata.title,
|
||||
author=pdf_metadata.author,
|
||||
tags=pdf_metadata.tags)
|
||||
self.painter.save()
|
||||
try:
|
||||
if self.cover_data is not None:
|
||||
p = QPixmap()
|
||||
p.loadFromData(self.cover_data)
|
||||
if not p.isNull():
|
||||
draw_image_page(QRect(0, 0, self.doc.width(), self.doc.height()),
|
||||
self.painter, p,
|
||||
preserve_aspect_ratio=self.opts.preserve_cover_aspect_ratio)
|
||||
self.doc.end_page()
|
||||
finally:
|
||||
self.painter.restore()
|
||||
|
||||
QTimer.singleShot(0, self.render_book)
|
||||
self.loop.exec_()
|
||||
|
||||
# TODO: Outline and links
|
||||
self.painter.end()
|
||||
|
||||
if self.doc.errors_occurred:
|
||||
raise Exception('PDF Output failed, see log for details')
|
||||
|
||||
def render_book(self):
|
||||
if self.doc.errors_occurred:
|
||||
return self.loop.exit(1)
|
||||
try:
|
||||
if not self.render_queue:
|
||||
self.loop.exit()
|
||||
else:
|
||||
self.render_next()
|
||||
except:
|
||||
self.logger.exception('Rendering failed')
|
||||
self.loop.exit(1)
|
||||
|
||||
def render_next(self):
|
||||
item = unicode(self.render_queue.pop(0))
|
||||
|
||||
self.logger.debug('Processing %s...' % item)
|
||||
self.current_item = item
|
||||
load_html(item, self.view)
|
||||
|
||||
def render_html(self, ok):
|
||||
if ok:
|
||||
try:
|
||||
self.do_paged_render()
|
||||
except:
|
||||
self.log.exception('Rendering failed')
|
||||
self.loop.exit(1)
|
||||
else:
|
||||
# The document is so corrupt that we can't render the page.
|
||||
self.logger.error('Document cannot be rendered.')
|
||||
self.loop.exit(1)
|
||||
return
|
||||
done = self.total_items - len(self.render_queue)
|
||||
self.report_progress(done/self.total_items,
|
||||
_('Rendered %s'%os.path.basename(self.current_item)))
|
||||
self.render_book()
|
||||
|
||||
@property
|
||||
def current_page_num(self):
|
||||
return self.doc.current_page_num
|
||||
|
||||
def do_paged_render(self):
|
||||
if self.paged_js is None:
|
||||
from calibre.utils.resources import compiled_coffeescript
|
||||
self.paged_js = compiled_coffeescript('ebooks.oeb.display.utils')
|
||||
self.paged_js += compiled_coffeescript('ebooks.oeb.display.indexing')
|
||||
self.paged_js += compiled_coffeescript('ebooks.oeb.display.paged')
|
||||
|
||||
self.view.page().mainFrame().addToJavaScriptWindowObject("py_bridge", self)
|
||||
evaljs = self.view.page().mainFrame().evaluateJavaScript
|
||||
evaljs(self.paged_js)
|
||||
evaljs('''
|
||||
py_bridge.__defineGetter__('value', function() {
|
||||
return JSON.parse(this._pass_json_value);
|
||||
});
|
||||
py_bridge.__defineSetter__('value', function(val) {
|
||||
this._pass_json_value = JSON.stringify(val);
|
||||
});
|
||||
|
||||
document.body.style.backgroundColor = "white";
|
||||
paged_display.set_geometry(1, %d, %d, %d);
|
||||
paged_display.layout();
|
||||
paged_display.fit_images();
|
||||
py_bridge.value = book_indexing.all_links_and_anchors();
|
||||
'''%(self.margin_top, self.margin_size, self.margin_bottom))
|
||||
|
||||
amap = self.bridge_value
|
||||
if not isinstance(amap, dict):
|
||||
amap = {'links':[], 'anchors':{}} # Some javascript error occurred
|
||||
self.links.add(self.current_item, self.current_page_num, amap['links'],
|
||||
amap['anchors'])
|
||||
|
||||
mf = self.view.page().mainFrame()
|
||||
while True:
|
||||
self.doc.init_page()
|
||||
self.painter.save()
|
||||
mf.render(self.painter)
|
||||
self.painter.restore()
|
||||
nsl = evaljs('paged_display.next_screen_location()').toInt()
|
||||
self.doc.end_page()
|
||||
if not nsl[1] or nsl[0] <= 0:
|
||||
break
|
||||
evaljs('window.scrollTo(%d, 0)'%nsl[0])
|
||||
if self.doc.errors_occurred:
|
||||
break
|
||||
|
32
src/calibre/ebooks/pdf/render/links.py
Normal file
32
src/calibre/ebooks/pdf/render/links.py
Normal file
@ -0,0 +1,32 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=UTF-8:ts=4:sw=4:sta:et:sts=4:fdm=marker:ai
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
__docformat__ = 'restructuredtext en'
|
||||
|
||||
import os
|
||||
|
||||
from calibre.ebooks.pdf.render.common import Array, Name
|
||||
|
||||
class Destination(Array):
|
||||
|
||||
def __init__(self, start_page, pos):
|
||||
super(Destination, self).__init__(
|
||||
[start_page + pos['column'], Name('FitH'), pos['y']])
|
||||
|
||||
class Links(object):
|
||||
|
||||
def __init__(self):
|
||||
self.anchors = {}
|
||||
|
||||
def add(self, base_path, start_page, links, anchors):
|
||||
path = os.path.normcase(os.path.abspath(base_path))
|
||||
self.anchors[path] = a = {}
|
||||
a[None] = Destination(start_page, {'y':0, 'column':0})
|
||||
for anchor, pos in anchors.iteritems():
|
||||
a[anchor] = Destination(start_page, pos)
|
||||
|
||||
|
66
src/calibre/ebooks/pdf/render/qt_hack.cpp
Normal file
66
src/calibre/ebooks/pdf/render/qt_hack.cpp
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
* qt_hack.cpp
|
||||
* Copyright (C) 2012 Kovid Goyal <kovid at kovidgoyal.net>
|
||||
*
|
||||
* Distributed under terms of the GPL3 license.
|
||||
*/
|
||||
|
||||
#include "qt_hack.h"
|
||||
|
||||
#include <QtEndian>
|
||||
|
||||
#include "private/qtextengine_p.h"
|
||||
#include "private/qfontengine_p.h"
|
||||
|
||||
GlyphInfo* get_glyphs(QPointF &p, const QTextItem &text_item) {
|
||||
QTextItemInt ti = static_cast<const QTextItemInt &>(text_item);
|
||||
QFontEngine *fe = ti.fontEngine;
|
||||
qreal size = ti.fontEngine->fontDef.pixelSize;
|
||||
#ifdef Q_WS_WIN
|
||||
if (ti.fontEngine->type() == QFontEngine::Win) {
|
||||
QFontEngineWin *fe = static_cast<QFontEngineWin *>(ti.fontEngine);
|
||||
size = fe->tm.tmHeight;
|
||||
}
|
||||
#endif
|
||||
QVarLengthArray<glyph_t> glyphs;
|
||||
QVarLengthArray<QFixedPoint> positions;
|
||||
QTransform m = QTransform::fromTranslate(p.x(), p.y());
|
||||
fe->getGlyphPositions(ti.glyphs, m, ti.flags, glyphs, positions);
|
||||
QVector<QPointF> points = QVector<QPointF>(positions.count());
|
||||
for (int i = 0; i < positions.count(); i++) {
|
||||
points[i].setX(positions[i].x.toReal());
|
||||
points[i].setY(positions[i].y.toReal());
|
||||
}
|
||||
|
||||
QVector<unsigned int> indices = QVector<unsigned int>(glyphs.count());
|
||||
for (int i = 0; i < glyphs.count(); i++)
|
||||
indices[i] = (unsigned int)glyphs[i];
|
||||
|
||||
const quint32 *tag = reinterpret_cast<const quint32 *>("name");
|
||||
|
||||
return new GlyphInfo(fe->getSfntTable(qToBigEndian(*tag)), size, points, indices);
|
||||
}
|
||||
|
||||
GlyphInfo::GlyphInfo(const QByteArray& name, qreal size, const QVector<QPointF> &positions, const QVector<unsigned int> &indices) :name(name), positions(positions), size(size), indices(indices) {
|
||||
}
|
||||
|
||||
QByteArray get_sfnt_table(const QTextItem &text_item, const char* tag_name) {
|
||||
QTextItemInt ti = static_cast<const QTextItemInt &>(text_item);
|
||||
const quint32 *tag = reinterpret_cast<const quint32 *>(tag_name);
|
||||
return ti.fontEngine->getSfntTable(qToBigEndian(*tag));
|
||||
}
|
||||
|
||||
QVector<unsigned int>* get_glyph_map(const QTextItem &text_item) {
|
||||
QTextItemInt ti = static_cast<const QTextItemInt &>(text_item);
|
||||
QVector<unsigned int> *ans = new QVector<unsigned int>(0x10000);
|
||||
QGlyphLayoutArray<10> glyphs;
|
||||
int nglyphs = 10;
|
||||
|
||||
for (uint uc = 0; uc < 0x10000; ++uc) {
|
||||
QChar ch(uc);
|
||||
ti.fontEngine->stringToCMap(&ch, 1, &glyphs, &nglyphs, QTextEngine::GlyphIndicesOnly);
|
||||
(*ans)[uc] = glyphs.glyphs[0];
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
34
src/calibre/ebooks/pdf/render/qt_hack.h
Normal file
34
src/calibre/ebooks/pdf/render/qt_hack.h
Normal file
@ -0,0 +1,34 @@
|
||||
/*
|
||||
* qt_hack.h
|
||||
* Copyright (C) 2012 Kovid Goyal <kovid at kovidgoyal.net>
|
||||
*
|
||||
* Distributed under terms of the GPL3 license.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <QGlyphRun>
|
||||
#include <QTextItem>
|
||||
#include <QPointF>
|
||||
|
||||
|
||||
class GlyphInfo {
|
||||
public:
|
||||
QByteArray name;
|
||||
QVector<QPointF> positions;
|
||||
qreal size;
|
||||
QVector<unsigned int> indices;
|
||||
|
||||
GlyphInfo(const QByteArray &name, qreal size, const QVector<QPointF> &positions, const QVector<unsigned int> &indices);
|
||||
|
||||
private:
|
||||
GlyphInfo(const GlyphInfo&);
|
||||
GlyphInfo &operator=(const GlyphInfo&);
|
||||
};
|
||||
|
||||
GlyphInfo* get_glyphs(QPointF &p, const QTextItem &text_item);
|
||||
|
||||
QByteArray get_sfnt_table(const QTextItem &text_item, const char* tag_name);
|
||||
|
||||
QVector<unsigned int>* get_glyph_map(const QTextItem &text_item);
|
||||
|
28
src/calibre/ebooks/pdf/render/qt_hack.sip
Normal file
28
src/calibre/ebooks/pdf/render/qt_hack.sip
Normal file
@ -0,0 +1,28 @@
|
||||
//Define the SIP wrapper to the qt_hack code
|
||||
//Author - Kovid Goyal <kovid@kovidgoyal.net>
|
||||
|
||||
%Module(name=qt_hack, version=1)
|
||||
|
||||
%Import QtCore/QtCoremod.sip
|
||||
%Import QtGui/QtGuimod.sip
|
||||
|
||||
class GlyphInfo {
|
||||
%TypeHeaderCode
|
||||
#include <qt_hack.h>
|
||||
%End
|
||||
public:
|
||||
QByteArray name;
|
||||
qreal size;
|
||||
QVector<QPointF> &positions;
|
||||
QVector<unsigned int> indices;
|
||||
GlyphInfo(const QByteArray &name, qreal size, const QVector<QPointF> &positions, const QVector<unsigned int> &indices);
|
||||
private:
|
||||
GlyphInfo(const GlyphInfo& g);
|
||||
|
||||
};
|
||||
|
||||
GlyphInfo* get_glyphs(QPointF &p, const QTextItem &text_item);
|
||||
|
||||
QByteArray get_sfnt_table(const QTextItem &text_item, const char* tag_name);
|
||||
|
||||
QVector<unsigned int>* get_glyph_map(const QTextItem &text_item);
|
@ -88,6 +88,7 @@ class Page(Stream):
|
||||
})
|
||||
self.opacities = {}
|
||||
self.fonts = {}
|
||||
self.xobjects = {}
|
||||
|
||||
def set_opacity(self, opref):
|
||||
if opref not in self.opacities:
|
||||
@ -101,6 +102,11 @@ class Page(Stream):
|
||||
self.fonts[fontref] = 'F%d'%len(self.fonts)
|
||||
return self.fonts[fontref]
|
||||
|
||||
def add_image(self, imgref):
|
||||
if imgref not in self.xobjects:
|
||||
self.xobjects[imgref] = 'Image%d'%len(self.xobjects)
|
||||
return self.xobjects[imgref]
|
||||
|
||||
def add_resources(self):
|
||||
r = Dictionary()
|
||||
if self.opacities:
|
||||
@ -113,6 +119,11 @@ class Page(Stream):
|
||||
for ref, name in self.fonts.iteritems():
|
||||
fonts[name] = ref
|
||||
r['Font'] = fonts
|
||||
if self.xobjects:
|
||||
xobjects = Dictionary()
|
||||
for ref, name in self.xobjects.iteritems():
|
||||
xobjects[name] = ref
|
||||
r['XObject'] = xobjects
|
||||
if r:
|
||||
self.page_dict['Resources'] = r
|
||||
|
||||
@ -122,7 +133,7 @@ class Page(Stream):
|
||||
self.page_dict['Contents'] = contents
|
||||
self.add_resources()
|
||||
ret = objects.add(self.page_dict)
|
||||
objects.commit(ret, stream)
|
||||
# objects.commit(ret, stream)
|
||||
return ret
|
||||
|
||||
class Path(object):
|
||||
@ -223,6 +234,35 @@ class HashingStream(object):
|
||||
if raw:
|
||||
self.last_char = raw[-1]
|
||||
|
||||
class Image(Stream):
|
||||
|
||||
def __init__(self, data, w, h, depth, mask, soft_mask, dct):
|
||||
Stream.__init__(self)
|
||||
self.width, self.height, self.depth = w, h, depth
|
||||
self.mask, self.soft_mask = mask, soft_mask
|
||||
if dct:
|
||||
self.filters.append(Name('DCTDecode'))
|
||||
else:
|
||||
self.compress = True
|
||||
self.write(data)
|
||||
|
||||
def add_extra_keys(self, d):
|
||||
d['Type'] = Name('XObject')
|
||||
d['Subtype']= Name('Image')
|
||||
d['Width'] = self.width
|
||||
d['Height'] = self.height
|
||||
if self.depth == 1:
|
||||
d['ImageMask'] = True
|
||||
d['Decode'] = Array([1, 0])
|
||||
else:
|
||||
d['BitsPerComponent'] = 8
|
||||
d['ColorSpace'] = Name('Device' + ('RGB' if self.depth == 32 else
|
||||
'Gray'))
|
||||
if self.mask is not None:
|
||||
d['Mask'] = self.mask
|
||||
if self.soft_mask is not None:
|
||||
d['SMask'] = self.soft_mask
|
||||
|
||||
class PDFStream(object):
|
||||
|
||||
PATH_OPS = {
|
||||
@ -253,6 +293,7 @@ class PDFStream(object):
|
||||
'Producer':String(creator)})
|
||||
self.stroke_opacities, self.fill_opacities = {}, {}
|
||||
self.font_manager = FontManager(self.objects, self.compress)
|
||||
self.image_cache = {}
|
||||
|
||||
@property
|
||||
def page_tree(self):
|
||||
@ -262,6 +303,14 @@ class PDFStream(object):
|
||||
def catalog(self):
|
||||
return self.objects[1]
|
||||
|
||||
def set_metadata(self, title=None, author=None, tags=None):
|
||||
if title:
|
||||
self.info['Title'] = String(title)
|
||||
if author:
|
||||
self.info['Author'] = String(author)
|
||||
if tags:
|
||||
self.info['Keywords'] = String(tags)
|
||||
|
||||
def write_line(self, byts=b''):
|
||||
byts = byts if isinstance(byts, bytes) else byts.encode('ascii')
|
||||
self.stream.write(byts + EOL)
|
||||
@ -368,10 +417,26 @@ class PDFStream(object):
|
||||
self.current_page.write('%s Tm '%' '.join(map(type(u''), transform)))
|
||||
for x, y, glyph_id in glyphs:
|
||||
self.current_page.write('%g %g Td '%(x, y))
|
||||
serialize(GlyphIndex(glyph_id, self.compress), self.current_page)
|
||||
serialize(GlyphIndex(glyph_id), self.current_page)
|
||||
self.current_page.write(' Tj ')
|
||||
self.current_page.write_line(b' ET')
|
||||
|
||||
def get_image(self, cache_key):
|
||||
return self.image_cache.get(cache_key, None)
|
||||
|
||||
def write_image(self, data, w, h, depth, dct=False, mask=None,
|
||||
soft_mask=None, cache_key=None):
|
||||
imgobj = Image(data, w, h, depth, mask, soft_mask, dct)
|
||||
self.image_cache[cache_key] = r = self.objects.add(imgobj)
|
||||
self.objects.commit(r, self.stream)
|
||||
return r
|
||||
|
||||
def draw_image(self, x, y, xscale, yscale, imgref):
|
||||
name = self.current_page.add_image(imgref)
|
||||
self.current_page.write('q %g 0 0 %g %g %g cm '%(xscale, yscale, x, y))
|
||||
serialize(Name(name), self.current_page)
|
||||
self.current_page.write_line(' Do Q')
|
||||
|
||||
def end(self):
|
||||
if self.current_page.getvalue():
|
||||
self.end_page()
|
||||
|
@ -9,18 +9,16 @@ Write content to PDF.
|
||||
'''
|
||||
|
||||
import os, shutil, json
|
||||
from future_builtins import map
|
||||
|
||||
from PyQt4.Qt import (QEventLoop, QObject, QPrinter, QSizeF, Qt, QPainter,
|
||||
QPixmap, QTimer, pyqtProperty, QString, QSize)
|
||||
from PyQt4.QtWebKit import QWebView, QWebPage, QWebSettings
|
||||
|
||||
from calibre.ptempfile import PersistentTemporaryDirectory
|
||||
from calibre.ebooks.pdf.pageoptions import (unit, paper_size, orientation)
|
||||
from calibre.ebooks.pdf.pageoptions import (unit, paper_size)
|
||||
from calibre.ebooks.pdf.outline_writer import Outline
|
||||
from calibre.ebooks.metadata import authors_to_string
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
from calibre import (__appname__, __version__, fit_image, isosx, force_unicode)
|
||||
from calibre import (__appname__, __version__, fit_image, isosx)
|
||||
from calibre.ebooks.oeb.display.webview import load_html
|
||||
|
||||
def get_custom_size(opts):
|
||||
@ -52,7 +50,7 @@ def get_pdf_printer(opts, for_comic=False, output_file_name=None): # {{{
|
||||
printer.setPaperSize(paper_size(opts.paper_size))
|
||||
else:
|
||||
if opts.output_profile.short_name == 'default' or \
|
||||
opts.output_profile.width > 9999:
|
||||
opts.output_profile.width > 9999 or opts.override_profile_size:
|
||||
if custom_size is None:
|
||||
printer.setPaperSize(paper_size(opts.paper_size))
|
||||
else:
|
||||
@ -72,7 +70,6 @@ def get_pdf_printer(opts, for_comic=False, output_file_name=None): # {{{
|
||||
else:
|
||||
printer.setPageMargins(opts.margin_left, opts.margin_top,
|
||||
opts.margin_right, opts.margin_bottom, QPrinter.Point)
|
||||
printer.setOrientation(orientation(opts.orientation))
|
||||
printer.setOutputFormat(QPrinter.PdfFormat)
|
||||
printer.setFullPage(for_comic)
|
||||
if output_file_name:
|
||||
@ -103,24 +100,6 @@ def draw_image_page(printer, painter, p, preserve_aspect_ratio=True):
|
||||
painter.drawPixmap(page_rect, p, p.rect())
|
||||
|
||||
|
||||
class PDFMetadata(object): # {{{
|
||||
def __init__(self, oeb_metadata=None):
|
||||
self.title = _(u'Unknown')
|
||||
self.author = _(u'Unknown')
|
||||
self.tags = u''
|
||||
|
||||
if oeb_metadata != None:
|
||||
if len(oeb_metadata.title) >= 1:
|
||||
self.title = oeb_metadata.title[0].value
|
||||
if len(oeb_metadata.creator) >= 1:
|
||||
self.author = authors_to_string([x.value for x in oeb_metadata.creator])
|
||||
if oeb_metadata.subject:
|
||||
self.tags = u', '.join(map(unicode, oeb_metadata.subject))
|
||||
|
||||
self.title = force_unicode(self.title)
|
||||
self.author = force_unicode(self.author)
|
||||
# }}}
|
||||
|
||||
class Page(QWebPage): # {{{
|
||||
|
||||
def __init__(self, opts, log):
|
||||
|
@ -151,7 +151,7 @@ class AddAction(InterfaceAction):
|
||||
Add an empty book item to the library. This does not import any formats
|
||||
from a book file.
|
||||
'''
|
||||
author = None
|
||||
author = series = None
|
||||
index = self.gui.library_view.currentIndex()
|
||||
if index.isValid():
|
||||
raw = index.model().db.authors(index.row())
|
||||
@ -159,16 +159,27 @@ class AddAction(InterfaceAction):
|
||||
authors = [a.strip().replace('|', ',') for a in raw.split(',')]
|
||||
if authors:
|
||||
author = authors[0]
|
||||
dlg = AddEmptyBookDialog(self.gui, self.gui.library_view.model().db, author)
|
||||
series = index.model().db.series(index.row())
|
||||
dlg = AddEmptyBookDialog(self.gui, self.gui.library_view.model().db,
|
||||
author, series)
|
||||
if dlg.exec_() == dlg.Accepted:
|
||||
num = dlg.qty_to_add
|
||||
series = dlg.selected_series
|
||||
db = self.gui.library_view.model().db
|
||||
ids = []
|
||||
for x in xrange(num):
|
||||
mi = MetaInformation(_('Unknown'), dlg.selected_authors)
|
||||
self.gui.library_view.model().db.import_book(mi, [])
|
||||
if series:
|
||||
mi.series = series
|
||||
mi.series_index = db.get_next_series_num_for(series)
|
||||
ids.append(db.import_book(mi, []))
|
||||
self.gui.library_view.model().books_added(num)
|
||||
if hasattr(self.gui, 'db_images'):
|
||||
self.gui.db_images.reset()
|
||||
self.gui.tags_view.recount()
|
||||
if ids:
|
||||
ids.reverse()
|
||||
self.gui.library_view.select_rows(ids)
|
||||
|
||||
def add_isbns(self, books, add_tags=[]):
|
||||
self.isbn_books = list(books)
|
||||
|
@ -10,12 +10,14 @@ from functools import partial
|
||||
from threading import Thread
|
||||
from contextlib import closing
|
||||
|
||||
from PyQt4.Qt import QToolButton
|
||||
from PyQt4.Qt import (QToolButton, QDialog, QGridLayout, QIcon, QLabel,
|
||||
QCheckBox, QDialogButtonBox)
|
||||
|
||||
from calibre.gui2.actions import InterfaceAction
|
||||
from calibre.gui2 import (error_dialog, Dispatcher, warning_dialog, gprefs,
|
||||
info_dialog)
|
||||
info_dialog, choose_dir)
|
||||
from calibre.gui2.dialogs.progress import ProgressDialog
|
||||
from calibre.gui2.widgets import HistoryLineEdit
|
||||
from calibre.utils.config import prefs, tweaks
|
||||
from calibre.utils.date import now
|
||||
|
||||
@ -125,6 +127,45 @@ class Worker(Thread): # {{{
|
||||
|
||||
# }}}
|
||||
|
||||
class ChooseLibrary(QDialog): # {{{
|
||||
|
||||
def __init__(self, parent):
|
||||
super(ChooseLibrary, self).__init__(parent)
|
||||
d = self
|
||||
d.l = l = QGridLayout()
|
||||
d.setLayout(l)
|
||||
d.setWindowTitle(_('Choose library'))
|
||||
la = d.la = QLabel(_('Library &path:'))
|
||||
l.addWidget(la, 0, 0)
|
||||
le = d.le = HistoryLineEdit(d)
|
||||
le.initialize('choose_library_for_copy')
|
||||
l.addWidget(le, 0, 1)
|
||||
la.setBuddy(le)
|
||||
b = d.b = QToolButton(d)
|
||||
b.setIcon(QIcon(I('document_open.png')))
|
||||
b.setToolTip(_('Browse for library'))
|
||||
b.clicked.connect(self.browse)
|
||||
l.addWidget(b, 0, 2)
|
||||
self.c = c = QCheckBox(_('&Delete after copy'))
|
||||
l.addWidget(c, 1, 0, 1, 3)
|
||||
self.bb = bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
|
||||
bb.accepted.connect(self.accept)
|
||||
bb.rejected.connect(self.reject)
|
||||
l.addWidget(bb, 2, 0, 1, 3)
|
||||
le.setMinimumWidth(350)
|
||||
self.resize(self.sizeHint())
|
||||
|
||||
def browse(self):
|
||||
d = choose_dir(self, 'choose_library_for_copy',
|
||||
_('Choose Library'))
|
||||
if d:
|
||||
self.le.setText(d)
|
||||
|
||||
@property
|
||||
def args(self):
|
||||
return (unicode(self.le.text()), self.c.isChecked())
|
||||
# }}}
|
||||
|
||||
class CopyToLibraryAction(InterfaceAction):
|
||||
|
||||
name = 'Copy To Library'
|
||||
@ -166,8 +207,20 @@ class CopyToLibraryAction(InterfaceAction):
|
||||
partial(self.copy_to_library, loc, delete_after=True))
|
||||
self.menu.addSeparator()
|
||||
|
||||
self.menu.addAction(_('Choose library by path...'), self.choose_library)
|
||||
self.qaction.setVisible(bool(locations))
|
||||
|
||||
def choose_library(self):
|
||||
d = ChooseLibrary(self.gui)
|
||||
if d.exec_() == d.Accepted:
|
||||
path, delete_after = d.args
|
||||
db = self.gui.library_view.model().db
|
||||
current = os.path.normcase(os.path.abspath(db.library_path))
|
||||
if current == os.path.normcase(os.path.abspath(path)):
|
||||
return error_dialog(self.gui, _('Cannot copy'),
|
||||
_('Cannot copy to current library.'), show=True)
|
||||
self.copy_to_library(path, delete_after)
|
||||
|
||||
def copy_to_library(self, loc, delete_after=False):
|
||||
rows = self.gui.library_view.selectionModel().selectedRows()
|
||||
if not rows or len(rows) == 0:
|
||||
|
@ -18,16 +18,17 @@ class PluginWidget(Widget, Ui_Form):
|
||||
ICON = I('mimetypes/pdf.png')
|
||||
|
||||
def __init__(self, parent, get_option, get_help, db=None, book_id=None):
|
||||
Widget.__init__(self, parent, ['paper_size', 'custom_size',
|
||||
'orientation', 'preserve_cover_aspect_ratio', 'pdf_serif_family',
|
||||
Widget.__init__(self, parent, [
|
||||
'override_profile_size', 'paper_size', 'custom_size',
|
||||
'preserve_cover_aspect_ratio', 'pdf_serif_family', 'unit',
|
||||
'pdf_sans_family', 'pdf_mono_family', 'pdf_standard_font',
|
||||
'pdf_default_font_size', 'pdf_mono_font_size'])
|
||||
self.db, self.book_id = db, book_id
|
||||
|
||||
for x in get_option('paper_size').option.choices:
|
||||
self.opt_paper_size.addItem(x)
|
||||
for x in get_option('orientation').option.choices:
|
||||
self.opt_orientation.addItem(x)
|
||||
for x in get_option('unit').option.choices:
|
||||
self.opt_unit.addItem(x)
|
||||
for x in get_option('pdf_standard_font').option.choices:
|
||||
self.opt_pdf_standard_font.addItem(x)
|
||||
|
||||
|
@ -14,7 +14,27 @@
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QFormLayout" name="formLayout">
|
||||
<item row="1" column="0">
|
||||
<property name="fieldGrowthPolicy">
|
||||
<enum>QFormLayout::ExpandingFieldsGrow</enum>
|
||||
</property>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string><b>Note:</b> The paper size settings below only take effect if you enable the "Override" checkbox below. Otherwise the size from the output profile will be used.</string>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_override_profile_size">
|
||||
<property name="text">
|
||||
<string>&Override paper size set in output profile</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>&Paper Size:</string>
|
||||
@ -24,21 +44,8 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QComboBox" name="opt_paper_size"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>&Orientation:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_orientation</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1">
|
||||
<widget class="QComboBox" name="opt_orientation"/>
|
||||
<widget class="QComboBox" name="opt_paper_size"/>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
@ -51,7 +58,24 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QLineEdit" name="opt_custom_size"/>
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QLineEdit" name="opt_custom_size"/>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_11">
|
||||
<property name="text">
|
||||
<string>&Unit:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_unit</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QComboBox" name="opt_unit"/>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="4" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_preserve_cover_aspect_ratio">
|
||||
@ -60,19 +84,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>213</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
@ -159,15 +170,18 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="2">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string><b>Note:</b> The paper size settings below only take effect if you have set the output profile to the default output profile. Otherwise the output profile will override these settings.</string>
|
||||
<item row="11" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="wordWrap">
|
||||
<bool>true</bool>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>213</height>
|
||||
</size>
|
||||
</property>
|
||||
</widget>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
|
@ -12,7 +12,7 @@ from calibre.utils.config import tweaks
|
||||
|
||||
class AddEmptyBookDialog(QDialog):
|
||||
|
||||
def __init__(self, parent, db, author):
|
||||
def __init__(self, parent, db, author, series=None):
|
||||
QDialog.__init__(self, parent)
|
||||
self.db = db
|
||||
|
||||
@ -45,6 +45,22 @@ class AddEmptyBookDialog(QDialog):
|
||||
self.clear_button.clicked.connect(self.reset_author)
|
||||
self._layout.addWidget(self.clear_button, 3, 1, 1, 1)
|
||||
|
||||
self.series_label = QLabel(_('Set the series of the new books to:'))
|
||||
self._layout.addWidget(self.series_label, 4, 0, 1, 2)
|
||||
|
||||
self.series_combo = EditWithComplete(self)
|
||||
self.authors_combo.setSizeAdjustPolicy(
|
||||
self.authors_combo.AdjustToMinimumContentsLengthWithIcon)
|
||||
self.series_combo.setEditable(True)
|
||||
self._layout.addWidget(self.series_combo, 5, 0, 1, 1)
|
||||
self.initialize_series(db, series)
|
||||
|
||||
self.sclear_button = QToolButton(self)
|
||||
self.sclear_button.setIcon(QIcon(I('trash.png')))
|
||||
self.sclear_button.setToolTip(_('Reset series'))
|
||||
self.sclear_button.clicked.connect(self.reset_series)
|
||||
self._layout.addWidget(self.sclear_button, 5, 1, 1, 1)
|
||||
|
||||
button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
button_box.accepted.connect(self.accept)
|
||||
button_box.rejected.connect(self.reject)
|
||||
@ -54,6 +70,9 @@ class AddEmptyBookDialog(QDialog):
|
||||
def reset_author(self, *args):
|
||||
self.authors_combo.setEditText(_('Unknown'))
|
||||
|
||||
def reset_series(self):
|
||||
self.series_combo.setEditText('')
|
||||
|
||||
def initialize_authors(self, db, author):
|
||||
au = author
|
||||
if not au:
|
||||
@ -65,6 +84,11 @@ class AddEmptyBookDialog(QDialog):
|
||||
self.authors_combo.set_add_separator(tweaks['authors_completer_append_separator'])
|
||||
self.authors_combo.update_items_cache(db.all_author_names())
|
||||
|
||||
def initialize_series(self, db, series):
|
||||
self.series_combo.show_initial_value(series or '')
|
||||
self.series_combo.update_items_cache(db.all_series_names())
|
||||
self.series_combo.set_separator(None)
|
||||
|
||||
@property
|
||||
def qty_to_add(self):
|
||||
return self.qty_spinbox.value()
|
||||
@ -73,6 +97,10 @@ class AddEmptyBookDialog(QDialog):
|
||||
def selected_authors(self):
|
||||
return string_to_authors(unicode(self.authors_combo.text()))
|
||||
|
||||
@property
|
||||
def selected_series(self):
|
||||
return unicode(self.series_combo.text())
|
||||
|
||||
if __name__ == '__main__':
|
||||
app = QApplication([])
|
||||
d = AddEmptyBookDialog()
|
||||
|
@ -227,7 +227,7 @@ p, li { white-space: pre-wrap; }
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<number>365</number>
|
||||
<number>36500</number>
|
||||
</property>
|
||||
<property name="value">
|
||||
<number>7</number>
|
||||
|
@ -52,10 +52,9 @@ class ColumnColor(object):
|
||||
self.mi = None
|
||||
|
||||
def __call__(self, id_, key, fmt, db, formatter, color_cache, colors):
|
||||
if id_ in color_cache:
|
||||
if key in color_cache[id_]:
|
||||
self.mi = None
|
||||
return color_cache[id_][key]
|
||||
if id_ in color_cache and key in color_cache[id_]:
|
||||
self.mi = None
|
||||
return color_cache[id_][key]
|
||||
try:
|
||||
if self.mi is None:
|
||||
self.mi = db.get_metadata(id_, index_is_id=True)
|
||||
@ -763,16 +762,15 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
self.column_color.mi = None
|
||||
|
||||
if self.color_row_fmt_cache is None:
|
||||
d = dict(self.db.prefs['column_color_rules'])
|
||||
self.color_row_fmt_cache = d.get(color_row_key, '')
|
||||
|
||||
self.color_row_fmt_cache = tuple(fmt for key, fmt in
|
||||
self.db.prefs['column_color_rules'] if key == color_row_key)
|
||||
|
||||
for k, fmt in self.db.prefs['column_color_rules']:
|
||||
if k == key:
|
||||
col = self.column_color(id_, key, fmt, self.db,
|
||||
ccol = self.column_color(id_, key, fmt, self.db,
|
||||
self.formatter, self.color_cache, self.colors)
|
||||
if col is not None:
|
||||
return col
|
||||
if ccol is not None:
|
||||
return ccol
|
||||
|
||||
if self.is_custom_column(key) and \
|
||||
self.custom_columns[key]['datatype'] == 'enumeration':
|
||||
@ -789,12 +787,11 @@ class BooksModel(QAbstractTableModel): # {{{
|
||||
except:
|
||||
pass
|
||||
|
||||
if self.color_row_fmt_cache:
|
||||
key = color_row_key
|
||||
col = self.column_color(id_, key, self.color_row_fmt_cache,
|
||||
self.db, self.formatter, self.color_cache, self.colors)
|
||||
if col is not None:
|
||||
return col
|
||||
for fmt in self.color_row_fmt_cache:
|
||||
ccol = self.column_color(id_, color_row_key, fmt, self.db,
|
||||
self.formatter, self.color_cache, self.colors)
|
||||
if ccol is not None:
|
||||
return ccol
|
||||
|
||||
self.column_color.mi = None
|
||||
return NONE
|
||||
|
@ -106,8 +106,8 @@ class ConditionEditor(QWidget): # {{{
|
||||
self.column_box.addItem('', '')
|
||||
for key in sorted(
|
||||
conditionable_columns(fm),
|
||||
key=sort_key):
|
||||
self.column_box.addItem(key, key)
|
||||
key=lambda(key): sort_key(fm[key]['name'])):
|
||||
self.column_box.addItem(fm[key]['name'], key)
|
||||
self.column_box.setCurrentIndex(0)
|
||||
|
||||
self.column_box.currentIndexChanged.connect(self.init_action_box)
|
||||
@ -314,7 +314,8 @@ class RuleEditor(QDialog): # {{{
|
||||
b.setSizeAdjustPolicy(b.AdjustToMinimumContentsLengthWithIcon)
|
||||
b.setMinimumContentsLength(15)
|
||||
|
||||
for key in sorted(displayable_columns(fm), key=sort_key):
|
||||
for key in sorted(displayable_columns(fm),
|
||||
key=lambda(k): sort_key(fm[k]['name']) if k != color_row_key else 0):
|
||||
name = all_columns_string if key == color_row_key else fm[key]['name']
|
||||
if name:
|
||||
self.column_box.addItem(name, key)
|
||||
@ -427,9 +428,11 @@ class RulesModel(QAbstractListModel): # {{{
|
||||
col, rule = self.rules[row]
|
||||
except:
|
||||
return None
|
||||
if col == color_row_key:
|
||||
col = all_columns_string
|
||||
if role == Qt.DisplayRole:
|
||||
if col == color_row_key:
|
||||
col = all_columns_string
|
||||
else:
|
||||
col = self.fm[col]['name']
|
||||
return self.rule_to_html(col, rule)
|
||||
if role == Qt.UserRole:
|
||||
return (col, rule)
|
||||
@ -486,6 +489,7 @@ class RulesModel(QAbstractListModel): # {{{
|
||||
|
||||
def condition_to_html(self, condition):
|
||||
c, a, v = condition
|
||||
c = self.fm[c]['name']
|
||||
action_name = a
|
||||
for actions in ConditionEditor.ACTION_MAP.itervalues():
|
||||
for trans, ac in actions:
|
||||
@ -569,9 +573,9 @@ class EditRules(QWidget): # {{{
|
||||
self.changed.emit()
|
||||
|
||||
def add_rule(self):
|
||||
d = RuleEditor(self.model.fm)
|
||||
d.add_blank_condition()
|
||||
self._add_rule(d)
|
||||
d = RuleEditor(self.model.fm)
|
||||
d.add_blank_condition()
|
||||
self._add_rule(d)
|
||||
|
||||
def add_advanced(self):
|
||||
td = TemplateDialog(self, '', mi=self.mi, fm=self.fm, color_field='')
|
||||
|
@ -544,7 +544,7 @@ class DocumentView(QWebView): # {{{
|
||||
self.goto_location_action.setMenu(self.goto_location_menu)
|
||||
self.grabGesture(Qt.SwipeGesture)
|
||||
|
||||
self.restore_fonts_action = QAction(_('Normal font size'), self)
|
||||
self.restore_fonts_action = QAction(_('Default font size'), self)
|
||||
self.restore_fonts_action.setCheckable(True)
|
||||
self.restore_fonts_action.triggered.connect(self.restore_font_size)
|
||||
|
||||
|
@ -697,11 +697,11 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
|
||||
self.view.shrink_fonts()
|
||||
|
||||
def magnification_changed(self, val):
|
||||
tt = _('Make font size %(which)s\nCurrent magnification: %(mag).1f')
|
||||
tt = _('%(which)s font size\nCurrent magnification: %(mag).1f')
|
||||
self.action_font_size_larger.setToolTip(
|
||||
tt %dict(which=_('larger'), mag=val))
|
||||
tt %dict(which=_('Increase'), mag=val))
|
||||
self.action_font_size_smaller.setToolTip(
|
||||
tt %dict(which=_('smaller'), mag=val))
|
||||
tt %dict(which=_('Decrease'), mag=val))
|
||||
self.action_font_size_larger.setEnabled(self.view.multiplier < 3)
|
||||
self.action_font_size_smaller.setEnabled(self.view.multiplier > 0.2)
|
||||
|
||||
|
@ -198,7 +198,7 @@
|
||||
<normaloff>:/images/font_size_larger.png</normaloff>:/images/font_size_larger.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Font size larger</string>
|
||||
<string>Increase font size</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_font_size_smaller">
|
||||
@ -207,7 +207,7 @@
|
||||
<normaloff>:/images/font_size_smaller.png</normaloff>:/images/font_size_smaller.png</iconset>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Font size smaller</string>
|
||||
<string>Decrease font size</string>
|
||||
</property>
|
||||
</action>
|
||||
<action name="action_table_of_contents">
|
||||
|
@ -11,11 +11,11 @@ from collections import OrderedDict
|
||||
|
||||
import cherrypy
|
||||
|
||||
from calibre.constants import filesystem_encoding
|
||||
from calibre import isbytestring, force_unicode, fit_image, \
|
||||
prepare_string_for_xml
|
||||
from calibre.constants import filesystem_encoding, config_dir
|
||||
from calibre import (isbytestring, force_unicode, fit_image,
|
||||
prepare_string_for_xml, sanitize_file_name2)
|
||||
from calibre.utils.filenames import ascii_filename
|
||||
from calibre.utils.config import prefs
|
||||
from calibre.utils.config import prefs, JSONConfig
|
||||
from calibre.utils.icu import sort_key
|
||||
from calibre.utils.magick import Image
|
||||
from calibre.library.comments import comments_to_html
|
||||
@ -242,6 +242,8 @@ class BrowseServer(object):
|
||||
connect('browse_category_icon', base_href+'/icon/{name}',
|
||||
self.browse_icon)
|
||||
|
||||
self.icon_map = JSONConfig('gui').get('tags_browser_category_icons', {})
|
||||
|
||||
# Templates {{{
|
||||
def browse_template(self, sort, category=True, initial_search=''):
|
||||
|
||||
@ -321,10 +323,18 @@ class BrowseServer(object):
|
||||
if not hasattr(self, '__browse_icon_cache__'):
|
||||
self.__browse_icon_cache__ = {}
|
||||
if name not in self.__browse_icon_cache__:
|
||||
try:
|
||||
data = I(name, data=True)
|
||||
except:
|
||||
raise cherrypy.HTTPError(404, 'no icon named: %r'%name)
|
||||
if name.startswith('_'):
|
||||
name = sanitize_file_name2(name[1:])
|
||||
try:
|
||||
with open(os.path.join(config_dir, 'tb_icons', name), 'rb') as f:
|
||||
data = f.read()
|
||||
except:
|
||||
raise cherrypy.HTTPError(404, 'no icon named: %r'%name)
|
||||
else:
|
||||
try:
|
||||
data = I(name, data=True)
|
||||
except:
|
||||
raise cherrypy.HTTPError(404, 'no icon named: %r'%name)
|
||||
img = Image()
|
||||
img.load(data)
|
||||
width, height = img.size
|
||||
@ -359,7 +369,9 @@ class BrowseServer(object):
|
||||
if meta['is_custom'] and category not in displayed_custom_fields:
|
||||
continue
|
||||
# get the icon files
|
||||
if category in category_icon_map:
|
||||
if category in self.icon_map:
|
||||
icon = '_'+quote(self.icon_map[category])
|
||||
elif category in category_icon_map:
|
||||
icon = category_icon_map[category]
|
||||
elif meta['is_custom']:
|
||||
icon = category_icon_map['custom:']
|
||||
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user