This commit is contained in:
GRiker 2012-11-03 05:20:56 -07:00
commit 4e49cb078a
324 changed files with 90934 additions and 59564 deletions

View File

@ -28,6 +28,12 @@ License: LGPL-2.1+
The full text of the LGPL is distributed as in
/usr/share/common-licenses/LGPL-2.1 on Debian systems.
Files: src/calibre/utils/fonts/woff/*
Copyright: Jonathan Kew?
License: LGPL-2.1
The full text of the LGPL is distributed as in
/usr/share/common-licenses/LGPL-2.1 on Debian systems.
Files: src/calibre/ebooks/hyphenate.py
Copyright: Copyright (C) 1990, 2004, 2005 Gerard D.C. Kuiken.
License: other
@ -41,6 +47,12 @@ License: Apache 2.0
The full text of the Apache 2.0 license is available at:
http://www.apache.org/licenses/LICENSE-2.0
Files: src/sfntly/*
Copyright: Google Inc.
License: Apache 2.0
The full text of the Apache 2.0 license is available at:
http://www.apache.org/licenses/LICENSE-2.0
Files: resources/viewer/mathjax/*
Copyright: Unknown
License: Apache 2.0

View File

@ -19,6 +19,103 @@
# new recipes:
# - title:
- version: 0.9.5
date: 2012-11-02
new features:
- title: "Font embedding: Add support for the CSS 3 Fonts module, which means you can embed font families that have more that the usual four faces, with the full set of font-stretch and font-weight variations. Of course, whether the fonts actually show up on a reader will depend on the readers' support for CSS 3."
- title: "Sharing by email: Allow specifying an 'alias' or friendly name by which to identify each email recipient."
tickets: [1069076]
- title: "Embedding fonts: Allow adding ttf/otf font files to calibre directly to be used for embedding. That way the fonts do not have to be installed system wide. You can add a font to calibre via the 'Add fonts' button in the font chooser dialog for embedding fonts."
- title: "E-book viewer: Add the ability to rotate images to the popup image viewer."
tickets: [1073513]
- title: "Generate cover: Speedup searching the system for a font that can render special characters"
- title: "A new custom font scanner to locate all fonts on the system. Faster and less crash prone that fontconfig/freetype"
- title: "Font family chooser: Show the faces available for a family when clicking on the family"
bug fixes:
- title: "Get Books: Fix eHarlequin and Kobo stores."
tickets: [1072702]
- title: "Kobo driver: Fix a bug that could cause the on device book matching to fail in certain circumstances."
tickets: [1072437]
- title: "Kobo driver: When using a SD card do not delete shelves that contain on books on the card (there might be books in the shelf in the main memory)."
tickets: [1073792]
- title: "Workaround for bug in the windows API CreateHardLink function that breaks using calibre libraries on some networked filesystems."
- title: "Template editor: Use dummy metadata instead of blank/unknown values"
- title: "Windows: abort setting of title/author if any of the books' files are in use. Results in less surprising behavior than before, when the title/author would be changed, but the on disk location would not."
improved recipes:
- Financial Times UK
- Science AAAS
- The Atlantic
new recipes:
- title: "Pravda in english, italian and portuguese"
author: Darko Miletic
- title: "Delco Times"
author: Krittika Goyal
- version: 0.9.4
date: 2012-10-26
new features:
- title: "Conversion: Add an option to embed a font family into the book."
description: "The embedded font is used as the base font for all text that does not specify its own font family in the input document. Works only with output formats that support font embedding, principally EPUB/AZW3. Option is found under Look & Feel in the conversion dialog. You can ensure that the font is used for all text, regardless of the input document's styles by filtering out font family styles via the Filter Style Information option in the Conversion dialog."
type: major
- title: "When changing the title/author of a book, use hard links instead of copying the books' files, for a large speedup. Only works on filesystems that support hardlinks."
- title: "Linux installer: Resume interrupted downloads and verify the SHA-512 signature of the downloaded file before installing it."
bug fixes:
- title: "Windows: Check if any of the files of a book are in use before changing the title/author, this prevents the creation of duplicate files if one of the files is open in another program"
- title: "Kobo driver: Fix the ondevice status for some books getting lost."
tickets: [1069403]
- title: "Catalogs: Fix regression that broke use of prefix rules."
tickets: [1070086]
- title: "Tag Browser: Fix sorting incorrect for accented letters"
tickets: [1069835]
- title: "Make the bundled Liberation fonts available on all platforms for embedding"
- title: "Use mimetype for fonts from the EPUB 3 specification"
- title: "Get Books: Handle website change that broke the SONY Store plugin"
- title: "Generate cover: If the default font cannot render characters in the metadata (for example for east asian languages) try to automatically find a font on the system that is capable of rendering the characters"
- title: "Fix regression that broke certain types of CSS selectors."
tickets: [1068937]
- title: "Use font-weight:bold instead of font-weight:bolder for the <b> and <strong> tags as ADE cant handle bolder when embedded fonts are used"
improved recipes:
- New York Post
- PC World
- TIME Magazine
- Associated Press
new recipes:
- title: Yazihane
author: A Erdogan
- version: 0.9.3
date: 2012-10-19

2862
imgsrc/font.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 119 KiB

View File

@ -72,13 +72,21 @@ After installing Bazaar, you can get the |app| source code with the command::
bzr branch lp:calibre
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::
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
The calibre repository is huge so the branch operation above takes along time (about an hour). If you want to get the code faster, the sourcecode for the latest release is always available as an
`archive <http://status.calibre-ebook.com/dist/src>`_.
|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).
If you want to get the code faster, the sourcecode for the latest release is
always available as an `archive <http://status.calibre-ebook.com/dist/src>`_.
You can also use bzr to just download the source code, without the history,
using::
bzr branch --stacked lp:calibre
Submitting your changes to be included
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@ -109,7 +117,7 @@ Whenever you commit changes to your branch with the command::
bzr commit -m "Comment describing your change"
Kovid can merge it directly from your branch into the main |app| source tree. You should also keep an eye on the |app|
`development forum <http://www.mobileread.com/forums/forumdisplay.php?f=240>`. Before making major changes, you should
`development forum <http://www.mobileread.com/forums/forumdisplay.php?f=240>`_. Before making major changes, you should
discuss them in the forum or contact Kovid directly (his email address is all over the source code).
Windows development environment

View File

@ -69,8 +69,8 @@ If you have a hand edited TOC in the input document, you can use the TOC detecti
Finally, I encourage you to ditch the content TOC and only have a metadata TOC in your ebooks. Metadata TOCs will give the people reading your ebooks a much superior navigation experience (except on the Kindle, where they are essentially the same as a content TOC).
The covers for my MOBI files have stopped showing up in Kindle for PC/Kindle for Android/etc.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The covers for my MOBI files have stopped showing up in Kindle for PC/Kindle for Android/iPad etc.
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
This is caused by a bug in the Amazon software. You can work around it by going
to Preferences->Output Options->MOBI output and setting the "Enable sharing
@ -284,8 +284,8 @@ Use the 'Connect to iTunes' method in the 'Getting started' instructions in `Cal
This method only works on Windows XP and higher, and OS X 10.5 and higher. Linux is not supported (iTunes is not available in linux) and OS X 10.4 is not supported.
How do I use |app| with my Android phone/tablet?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
How do I use |app| with my Android phone/tablet or Kindle Fire HD?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
There are two ways that you can connect your Android device to calibre. Using a USB cable -- or wirelessly, over the air.
The first step to using an Android device is installing an ebook reading
@ -435,7 +435,7 @@ any |app| developers will ever feel motivated enough to support it. There is how
that allows you to create collections on your Kindle from the |app| metadata. It is available
`from here <http://www.mobileread.com/forums/showthread.php?t=118635>`_.
.. note:: Amazon have removed the ability to manipulate collections completely in their newer models, like the Kindle Touch and Kindle Fire, making even the above plugin useless. If you really want the ability to manage collections on your Kindle via a USB connection, we encourage you to complain to Amazon about it, or get a reader where this is supported, like the SONY Readers.
.. note:: Amazon have removed the ability to manipulate collections completely in their newer models, like the Kindle Touch and Kindle Fire, making even the above plugin useless. If you really want the ability to manage collections on your Kindle via a USB connection, we encourage you to complain to Amazon about it, or get a reader where this is supported, like the SONY or Kobo Readers.
I am getting an error when I try to use |app| with my Kobo Touch?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
@ -557,6 +557,27 @@ There can be two reasons why |app| is showing a empty list of books:
* Your metadata.db file was deleted/corrupted. In this case, you can ask |app| to rebuild the metadata.db from its backups. Right click the |app| icon in the |app| toolbar (it will say 0 books underneath it) and select Library maintenance->Restore database. |app| will automatically rebuild metadata.db.
I am getting errors with my calibre library on a networked drive/NAS?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
**Do not put your calibre library on a networked drive**.
A filesystem is a complex beast. Most network filesystems lack various
filesystem features that |app| uses. Some dont support file locking, some dont
support hardlinking, some are just flaky. Additionally, |app| is a single user
application, if you accidentally run two copies of |app| on the same networked
library, bad things will happen. Finally, different OSes impose different
limitations on filesystems, so if you share your networked drive across OSes,
once again, bad things *will happen*.
Consider using the |app| Content Server to make your books available on other
computers. Run |app| on a single computer and access it via the Content Server
or a Remote Desktop solution.
If you must share the actual library, use a file syncing tool like
DropBox or rsync or Microsoft SkyDrive instead of a networked drive. Even with
these tools there is danger of data corruption/loss, so only do this if you are
willing to live with that risk.
Content From The Web
---------------------
@ -638,6 +659,9 @@ There are three possible things I know of, that can cause this:
* You are using a Wacom branded mouse. There is an incompatibility between Wacom mice and the graphics toolkit |app| uses. Try using a non-Wacom mouse.
* If you use RoboForm, it is known to cause |app| to crash. Add |app| to
the blacklist of programs inside RoboForm to fix this.
* Sometimes if some software has installed lots of new files in your fonts folder, |app| can crash until it finishes indexing them. Just start |app|, then leave it alone for about 20 minutes, without clicking on anything. After that you should be able to use |app| as normal.
@ -674,7 +698,20 @@ If you still cannot get the installer to work and you are on windows, you can us
My antivirus program claims |app| is a virus/trojan?
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Your antivirus program is wrong. Antivirus programs use heuristics, patterns of code that "looks suspicuous" to detect viruses. It's rather like racial profiling. |app| is a completely open source product. You can actually browse the source code yourself (or hire someone to do it for you) to verify that it is not a virus. Please report the false identification to whatever company you buy your antivirus software from. If the antivirus program is preventing you from downloading/installing |app|, disable it temporarily, install |app| and then re-enable it.
The first thing to check is that you are downloading |app| from the official
website: `<http://calibre-ebook.com/download>`_. |app| is a very popular program
and unscrupulous people try to setup websites offering it for download to fool
the unwary.
If you have the official download and your antivirus program is still claiming
|app| is a virus, then, your antivirus program is wrong. Antivirus programs use
heuristics, patterns of code that "look suspicious" to detect viruses. It's
rather like racial profiling. |app| is a completely open source product. You
can actually browse the source code yourself (or hire someone to do it for you)
to verify that it is not a virus. Please report the false identification to
whatever company you buy your antivirus software from. If the antivirus program
is preventing you from downloading/installing |app|, disable it temporarily,
install |app| and then re-enable it.
How do I backup |app|?
~~~~~~~~~~~~~~~~~~~~~~~~~~~

Binary file not shown.

Before

Width:  |  Height:  |  Size: 85 KiB

BIN
manual/images/sg_pref.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

View File

@ -65,7 +65,7 @@ You create the custom column in the usual way, using Preferences -> Add your own
Then after restarting |app|, you must tell |app| that the column is to be treated as a hierarchy. Go to Preferences -> Look and Feel -> Tag Browser and enter the lookup name "#genre" into the "Categories with hierarchical items" box. Press Apply, and you are done with setting up.
.. image:: images/sg_pref.jpg
.. image:: images/sg_pref.png
:align: center
At the point there are no genres in the column. We are left with the last step: how to apply a genre to a book. A genre does not exist in |app| until it appears on at least one book. To learn how to apply a genre for the first time, we must go into some detail about what a genre looks like in the metadata for a book.

View File

@ -10,6 +10,8 @@ class AssociatedPress(BasicNewsRecipe):
use_embedded_content = False
language = 'en'
no_stylesheets = True
auto_cleanup = True
# auto_cleanup_keep = '//td[@class="ap-smallphoto-td-image"]'
max_articles_per_feed = 15
@ -20,13 +22,13 @@ class AssociatedPress(BasicNewsRecipe):
]
keep_only_tags = [ dict(name='div', attrs={'class':['body']}),
dict(name='div', attrs={'class':['entry-content']}),
]
remove_tags = [dict(name='table', attrs={'class':['ap-video-table','ap-htmlfragment-table','ap-htmltable-table']}),
dict(name='span', attrs={'class':['apCaption','tabletitle']}),
dict(name='td', attrs={'bgcolor':['#333333']}),
]
#keep_only_tags = [ dict(name='table', attrs={'class':['ap-story-table hnews hentry item']}),
##dict(name='div', attrs={'class':['entry-content']}),
#]
#remove_tags = [dict(name='td', attrs={'class':['ap-mediabox-td']}),
#dict(name='table', attrs={'class':['ap-htmltable-table', 'ap-htmltable-table', 'ap-mediabox-table']}),
##dict(name='td', attrs={'bgcolor':['#333333']}),
#]
extra_css = '''
.headline{font-family:Verdana,Arial,Helvetica,sans-serif;font-weight:bold;}
.bline{color:#003366;}

View File

@ -38,8 +38,10 @@ class TheAtlantic(BasicNewsRecipe):
self.timefmt = ' [%s]'%ds
cover = soup.find('img', src=True, attrs={'class':'cover'})
if cover is not None:
self.cover_url = cover['src'].replace(' ', '%20')
self.cover_url = re.sub('\s','%20',re.sub('jpg.*','jpg',cover['src']))
self.log(self.cover_url)
feeds = []
seen_titles = set([])
@ -47,18 +49,16 @@ class TheAtlantic(BasicNewsRecipe):
section_title = self.tag_to_string(section.find('h2'))
self.log('Found section:', section_title)
articles = []
for post in section.findAll('div', attrs={'class':lambda x : x and
'post' in x}):
h = post.find(['h3', 'h4'])
title = self.tag_to_string(h)
for post in section.findAll('h3', attrs={'class':'headline'}):
a = post.find('a', href=True)
title = self.tag_to_string(a)
if title in seen_titles:
continue
seen_titles.add(title)
a = post.find('a', href=True)
url = a['href']
if url.startswith('/'):
url = 'http://www.theatlantic.com'+url
p = post.find('p', attrs={'class':'dek'})
p = post.parent.find('p', attrs={'class':'dek'})
desc = None
self.log('\tFound article:', title, 'at', url)
if p is not None:
@ -69,19 +69,29 @@ class TheAtlantic(BasicNewsRecipe):
if articles:
feeds.append((section_title, articles))
poems = []
self.log('Found section: Poems')
pd = soup.find('h2', text='Poetry').parent.parent
for poem in pd.findAll('h4'):
title = self.tag_to_string(poem)
url = poem.find('a')['href']
rightContent=soup.find('div', attrs = {'class':'rightContent'})
for module in rightContent.findAll('div', attrs={'class':'module'}):
section_title = self.tag_to_string(module.find('h2'))
articles = []
for post in module.findAll('div', attrs={'class':'post'}):
a = post.find('a', href=True)
title = self.tag_to_string(a)
if title in seen_titles:
continue
seen_titles.add(title)
url = a['href']
if url.startswith('/'):
url = 'http://www.theatlantic.com'+url
p = post.parent.find('p', attrs={'class':'dek'})
desc = None
self.log('\tFound article:', title, 'at', url)
poems.append({'title':title, 'url':url, 'description':'',
'date':''})
if poems:
feeds.append(('Poems', poems))
if p is not None:
desc = self.tag_to_string(p)
self.log('\t\t', desc)
articles.append({'title':title, 'url':url, 'description':desc, 'date':''})
if articles:
feeds.append((section_title, articles))
return feeds
@ -100,4 +110,3 @@ class TheAtlantic(BasicNewsRecipe):
table.replaceWith(div)
return soup

View File

@ -15,7 +15,8 @@ class BusinessStandard(BasicNewsRecipe):
no_stylesheets = True
delay = 1
use_embedded_content = False
encoding = 'cp1252'
auto_cleanup = True
encoding = 'utf-8'
publisher = 'Boston'
category = 'news, boston, usa, world'
language = 'en'
@ -30,23 +31,23 @@ class BusinessStandard(BasicNewsRecipe):
,'publisher' : publisher
}
keep_only_tags = [dict(attrs={'id':['INDblogEntry','blogEntry','articleHeader','articleGraphs','galleryShell']})]
remove_tags = [
dict(name=['object','link','script','iframe'])
,dict(attrs={'id':['blogheadTools','bdc_emailWidget','tools','relatedContent']})
]
#keep_only_tags = [dict(attrs={'id':['INDblogEntry','blogEntry','articleHeader','articleGraphs','galleryShell']})]
#remove_tags = [
#dict(name=['object','link','script','iframe'])
#,dict(attrs={'id':['blogheadTools','bdc_emailWidget','tools','relatedContent']})
#]
feeds = [
(u'Top Stories' , u'http://feeds.boston.com/boston/topstories' )
,(u'Patriots news', u'http://feeds.boston.com/boston/sports/football/patriots')
,(u'Patriots news', u'http://feeds.boston.com/boston/sports/football/patriots/patriots_rss')
,(u'National news', u'http://feeds.boston.com/boston/news/nation' )
,(u'World news' , u'http://feeds.boston.com/boston/news/world' )
]
def print_version(self, url):
return url + '?page=full'
#def print_version(self, url):
#return url + '?page=full'
def get_article_url(self, article):
rawarticle = article.get('guid', None)
return rawarticle.rpartition('?')[0]
#def get_article_url(self, article):
#rawarticle = article.get('guid', None)
#return rawarticle.rpartition('?')[0]

View File

@ -7,7 +7,7 @@ class AdvancedUserRecipe1306061239(BasicNewsRecipe):
description = 'News as provided by The Daily Mirror -UK'
__author__ = 'Dave Asbury'
# last updated 8/6/12
# last updated 19/10/12
language = 'en_GB'
#cover_url = 'http://yookeo.com/screens/m/i/mirror.co.uk.jpg'
@ -15,10 +15,12 @@ class AdvancedUserRecipe1306061239(BasicNewsRecipe):
oldest_article = 1
max_articles_per_feed = 12
max_articles_per_feed = 1
remove_empty_feeds = True
remove_javascript = True
no_stylesheets = True
ignore_duplicate_articles = {'title'}
# auto_cleanup = True
#conversion_options = { 'linearize_tables' : True }
@ -61,10 +63,11 @@ class AdvancedUserRecipe1306061239(BasicNewsRecipe):
# example of commented out feed not needed ,(u'Travel','http://www.mirror.co.uk/advice/travel/rss.xml')
]
extra_css = '''
h1{ font-size:medium;}
body{ text-align: justify; font-family:Arial,Helvetica,sans-serif; font-size:11px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:normal;}
img { display:block}
'''#
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
h2{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;}
p{font-family:Arial,Helvetica,sans-serif;font-size:small;}
body{font-family:Helvetica,Arial,sans-serif;font-size:small;}
'''
def get_cover_url(self):
soup = self.index_to_soup('http://www.politicshome.com/uk/latest_frontpage.html')
@ -75,8 +78,10 @@ class AdvancedUserRecipe1306061239(BasicNewsRecipe):
#cov2 now contains url of the page containing pic
soup = self.index_to_soup(cov2)
cov = soup.find(attrs={'id' : 'large'})
cov2 = str(cov)
cov2=cov2[27:-18]
cov=str(cov)
cov2 = re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', cov)
cov2 = str(cov2)
cov2=cov2[2:len(cov2)-2]
#cov2 now is pic url, now go back to original function
br = browser()
br.set_handle_redirect(False)

View File

@ -0,0 +1,26 @@
from calibre.web.feeds.news import BasicNewsRecipe
class HindustanTimes(BasicNewsRecipe):
title = u'Delcoe Times'
language = 'en'
__author__ = 'Krittika Goyal'
oldest_article = 1 #days
max_articles_per_feed = 25
#encoding = 'cp1252'
use_embedded_content = False
no_stylesheets = True
auto_cleanup = True
feeds = [
('News',
'http://www.delcotimes.com/?rss=news'),
('Sports',
'http://www.delcotimes.com/?rss=sports'),
('Business',
'http://business-news.thestreet.com/the-delaware-county-daily-times/rss/109393'),
('Entertainment',
'http://www.delcotimes.com/?rss=entertainment'),
]

View File

@ -55,7 +55,6 @@ class FinancialTimes(BasicNewsRecipe):
,dict(name='div' , attrs={'class':'standfirst'})
,dict(name='div' , attrs={'id' :'storyContent'})
,dict(name='div' , attrs={'class':['ft-story-body','index-detail']})
,dict(name='div' , attrs={'class':['ft-story-body','index-detail']})
,dict(name='h2' , attrs={'class':'entry-title'} )
,dict(name='span', attrs={'class':lambda x: x and 'posted-on' in x.split()} )
,dict(name='span', attrs={'class':'author_byline'} )
@ -91,7 +90,10 @@ class FinancialTimes(BasicNewsRecipe):
url = rawlink
if not rawlink.startswith('http://'):
url = self.PREFIX + rawlink
try:
urlverified = self.browser.open_novisit(url).geturl() # resolve redirect.
except:
continue
title = self.tag_to_string(item)
date = strftime(self.timefmt)
articles.append({
@ -175,3 +177,6 @@ class FinancialTimes(BasicNewsRecipe):
tfile.close()
self.temp_files.append(tfile)
return tfile.name
def cleanup(self):
self.browser.open('https://registration.ft.com/registration/login/logout?location=')

BIN
recipes/icons/pravda_en.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 B

BIN
recipes/icons/pravda_it.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 B

BIN
recipes/icons/pravda_ru.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 538 B

View File

@ -17,6 +17,7 @@ class NYPost(BasicNewsRecipe):
no_stylesheets = True
encoding = 'utf8'
use_embedded_content = False
auto_cleanup = True
language = 'en'
masthead_url = 'http://www.nypost.com/rw/SysConfig/WebPortal/nypost/images/nyp_logo_230x32.gif'
extra_css = ' body{font-family: Arial,Helvetica,sans-serif } img{margin-bottom: 0.4em} '
@ -28,7 +29,7 @@ class NYPost(BasicNewsRecipe):
, 'language' : language
}
keep_only_tags=[dict(name='div', attrs={'id':'story'})]
#keep_only_tags=[dict(name='div', attrs={'id':'story'})]
feeds = [(u'Articles', u'http://www.nypost.com/rss/all_section.xml')]

View File

@ -1,5 +1,4 @@
from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import BeautifulSoup
class NewYorkTimesBookReview(BasicNewsRecipe):
title = u'New York Times Book Review'
@ -7,50 +6,16 @@ class NewYorkTimesBookReview(BasicNewsRecipe):
__author__ = 'Krittika Goyal'
oldest_article = 8 #days
max_articles_per_feed = 1000
recursions = 2
#recursions = 2
#encoding = 'latin1'
use_embedded_content = False
no_stylesheets = True
auto_cleanup = True
remove_stylesheets = True
#remove_tags_before = dict(name='h1', attrs={'class':'heading'})
remove_tags_after = dict(name='div', attrs={'id':'authorId'})
remove_tags = [
dict(name='iframe'),
dict(name=['div', 'a'], attrs={'class':['enlargeThis', 'jumpLink']}),
dict(name='div', attrs={'id':['sidebarArticles', 'toolsRight']}),
#dict(name='ul', attrs={'class':'article-tools'}),
#dict(name='ul', attrs={'class':'articleTools'}),
]
match_regexps = [
r'http://www.nytimes.com/.+pagewanted=[2-9]+'
]
feeds = [
('New York Times Sunday Book Review',
'http://feeds.nytimes.com/nyt/rss/SundayBookReview'),
]
def preprocess_html(self, soup):
story = soup.find(name='div', attrs={'id':'article'})
#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)
#for x in soup.findAll(name='p', text=lambda x:x and '--&gt;' in x):
#p = x.findParent('p')
#if p is not None:
#p.extract()
return soup
def postprocess_html(self, soup, first):
for div in soup.findAll(id='pageLinks'):
div.extract()
if not first:
h1 = soup.find('h1')
if h1 is not None:
h1.extract()
t = soup.find(attrs={'class':'timestamp'})
if t is not None:
t.extract()
return soup

View File

@ -35,6 +35,7 @@ class pcWorld(BasicNewsRecipe):
remove_javascript = True
no_stylesheets = True
auto_cleanup = True
def get_obfuscated_article(self, url):
br = self.get_browser()
@ -48,16 +49,16 @@ class pcWorld(BasicNewsRecipe):
self.temp_files[-1].close()
return self.temp_files[-1].name
keep_only_tags = [
dict(name='div', attrs={'class':'article'})
]
remove_tags = [
dict(name='div', attrs={'class':['toolBar','mac_tags','toolBar btmTools','recommend longRecommend','recommend shortRecommend','textAds']}),
dict(name='div', attrs={'id':['sidebar','comments','mac_tags']}),
dict(name='ul', attrs={'class':['tools', 'tools clearfix']}),
dict(name='li', attrs={'class':'sub'}),
dict(name='p', attrs={'id':'userDesire'})
]
#keep_only_tags = [
#dict(name='div', attrs={'class':'article'})
#]
#remove_tags = [
#dict(name='div', attrs={'class':['toolBar','mac_tags','toolBar btmTools','recommend longRecommend','recommend shortRecommend','textAds']}),
#dict(name='div', attrs={'id':['sidebar','comments','mac_tags']}),
#dict(name='ul', attrs={'class':['tools', 'tools clearfix']}),
#dict(name='li', attrs={'class':'sub'}),
#dict(name='p', attrs={'id':'userDesire'})
#]
feeds = [
(u'PCWorld Headlines', u'http://feeds.pcworld.com/pcworld/latestnews'),
(u'How-To', u'http://feeds.pcworld.com/pcworld/update/howto'),

53
recipes/pravda_en.recipe Normal file
View File

@ -0,0 +1,53 @@
__license__ = 'GPL v3'
__copyright__ = '2012, Darko Miletic <darko.miletic at gmail.com>'
'''
english.pravda.ru
'''
from calibre.web.feeds.news import BasicNewsRecipe
class Pravda_eng(BasicNewsRecipe):
title = 'Pravda in English'
__author__ = 'Darko Miletic'
description = 'News from Russia and rest of the world'
publisher = 'PRAVDA.Ru'
category = 'news, politics, Russia'
oldest_article = 2
max_articles_per_feed = 200
no_stylesheets = True
encoding = 'utf8'
use_embedded_content = False
language = 'en_RU'
remove_empty_feeds = True
publication_type = 'newspaper'
masthead_url = 'http://english.pravda.ru/pix/logo.gif'
extra_css = """
body{font-family: Arial,sans-serif }
img{margin-bottom: 0.4em; display:block}
"""
conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
remove_attributes=['lang', 'style']
keep_only_tags = [dict(name='div', attrs={'id':'article'})]
feeds = [
(u'World' , u'http://english.pravda.ru/world/export-articles.xml' )
,(u'Russia' , u'http://english.pravda.ru/russia/export-articles.xml' )
,(u'Society' , u'http://english.pravda.ru/society/export-articles.xml' )
,(u'Incidents', u'http://english.pravda.ru/hotspots/export-articles.xml' )
,(u'Opinion' , u'http://english.pravda.ru/opinion/export-articles.xml' )
,(u'Science' , u'http://english.pravda.ru/science/export-articles.xml' )
,(u'Business' , u'http://english.pravda.ru/business/export-articles.xml' )
,(u'Economics', u'http://english.pravda.ru/russia/economics/export-articles.xml')
,(u'Politics' , u'http://english.pravda.ru/russia/politics/export-articles.xml' )
]
def print_version(self, url):
return url + '?mode=print'

52
recipes/pravda_it.recipe Normal file
View File

@ -0,0 +1,52 @@
__license__ = 'GPL v3'
__copyright__ = '2012, Darko Miletic <darko.miletic at gmail.com>'
'''
italia.pravda.ru
'''
from calibre.web.feeds.news import BasicNewsRecipe
class Pravda_ita(BasicNewsRecipe):
title = 'Pravda in Italiano'
__author__ = 'Darko Miletic'
description = 'News from Russia and rest of the world'
publisher = 'PRAVDA.Ru'
category = 'news, politics, Russia'
oldest_article = 2
max_articles_per_feed = 200
no_stylesheets = True
encoding = 'utf8'
use_embedded_content = False
language = 'it'
remove_empty_feeds = True
publication_type = 'newspaper'
masthead_url = 'http://italia.pravda.ru/pix/logo.gif'
extra_css = """
body{font-family: Arial,sans-serif }
img{margin-bottom: 0.4em; display:block}
"""
conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
remove_attributes=['lang', 'style']
keep_only_tags = [dict(name='div', attrs={'id':'article'})]
feeds = [
(u'Dal mondo' , u'http://italia.pravda.ru/world/export-articles.xml' )
,(u'Russia' , u'http://italia.pravda.ru/russia/export-articles.xml' )
,(u'Societa' , u'http://italia.pravda.ru/society/export-articles.xml' )
,(u'Avvenimenti', u'http://italia.pravda.ru/hotspots/export-articles.xml' )
,(u'Opinioni' , u'http://italia.pravda.ru/opinion/export-articles.xml' )
,(u'Scienza' , u'http://italia.pravda.ru/science/export-articles.xml' )
,(u'Economia' , u'http://italia.pravda.ru/russia/economics/export-articles.xml')
,(u'Politica' , u'http://italia.pravda.ru/russia/politics/export-articles.xml' )
]
def print_version(self, url):
return url + '?mode=print'

51
recipes/pravda_por.recipe Normal file
View File

@ -0,0 +1,51 @@
__license__ = 'GPL v3'
__copyright__ = '2012, Darko Miletic <darko.miletic at gmail.com>'
'''
port.pravda.ru
'''
from calibre.web.feeds.news import BasicNewsRecipe
class Pravda_port(BasicNewsRecipe):
title = u'Pravda em português'
__author__ = 'Darko Miletic'
description = 'News from Russia and rest of the world'
publisher = 'PRAVDA.Ru'
category = 'news, politics, Russia'
oldest_article = 2
max_articles_per_feed = 200
no_stylesheets = True
encoding = 'utf8'
use_embedded_content = False
language = 'pt'
remove_empty_feeds = True
publication_type = 'newspaper'
masthead_url = 'http://port.pravda.ru/pix/logo.gif'
extra_css = """
body{font-family: Arial,sans-serif }
img{margin-bottom: 0.4em; display:block}
"""
conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
remove_attributes=['lang', 'style']
keep_only_tags = [dict(name='div', attrs={'id':'article'})]
feeds = [
(u'Mundo' , u'http://port.pravda.ru/mundo/export-articles.xml' )
,(u'Russia' , u'http://port.pravda.ru/russa/export-articles.xml' )
,(u'Sociedade' , u'http://port.pravda.ru/sociedade/export-articles.xml' )
,(u'Cultura' , u'http://port.pravda.ru/sociedade/cultura/export-articles.xml')
,(u'Ciencia' , u'http://port.pravda.ru/science/export-articles.xml' )
,(u'Desporto' , u'http://port.pravda.ru/desporto/export-articles.xml' )
,(u'CPLP' , u'http://port.pravda.ru/cplp/export-articles.xml' )
]
def print_version(self, url):
return url + '?mode=print'

50
recipes/pravda_ru.recipe Normal file
View File

@ -0,0 +1,50 @@
__license__ = 'GPL v3'
__copyright__ = '2012, Darko Miletic <darko.miletic at gmail.com>'
'''
www.pravda.ru
'''
from calibre.web.feeds.news import BasicNewsRecipe
class Pravda_ru(BasicNewsRecipe):
title = u'Правда'
__author__ = 'Darko Miletic'
description = u'Правда.Ру: Аналитика и новости'
publisher = 'PRAVDA.Ru'
category = 'news, politics, Russia'
oldest_article = 2
max_articles_per_feed = 200
no_stylesheets = True
encoding = 'utf8'
use_embedded_content = False
language = 'ru'
remove_empty_feeds = True
publication_type = 'newspaper'
masthead_url = 'http://www.pravda.ru/pix/logo.gif'
extra_css = """
body{font-family: Arial,sans-serif }
img{margin-bottom: 0.4em; display:block}
"""
conversion_options = {
'comment' : description
, 'tags' : category
, 'publisher' : publisher
, 'language' : language
}
remove_attributes=['lang', 'style']
keep_only_tags = [dict(name='div', attrs={'id':'article'})]
feeds = [
(u'Мир' , u'http://www.pravda.ru/world/export.xml' )
,(u'Религия' , u'http://www.pravda.ru/faith/export.xml' )
,(u'Общество' , u'http://www.pravda.ru/society/export.xml' )
,(u'Происшествия', u'http://www.pravda.ru/accidents/export.xml')
,(u'Наука' , u'http://www.pravda.ru/science/export.xml' )
,(u'Экономика' , u'http://www.pravda.ru/economics/export.xml')
,(u'Политика' , u'http://www.pravda.ru/politics/export.xml' )
]
def print_version(self, url):
return url + '?mode=print'

View File

@ -27,7 +27,7 @@ class ScienceAAS(BasicNewsRecipe):
br = BasicNewsRecipe.get_browser()
if self.username is not None and self.password is not None:
br.open(self.LOGIN)
br.select_form(nr=1)
br.select_form(nr=0)
br['username'] = self.username
br['code' ] = self.password
br.submit()

View File

@ -1,4 +1,4 @@
import random
import re, random
from calibre import browser
from calibre.web.feeds.recipes import BasicNewsRecipe
@ -8,7 +8,7 @@ class AdvancedUserRecipe1325006965(BasicNewsRecipe):
title = u'The Sun UK'
description = 'Articles from The Sun tabloid UK'
__author__ = 'Dave Asbury'
# last updated 12/10/12 added starsons remove article code
# last updated 19/10/12 better cover fetch
language = 'en_GB'
oldest_article = 1
max_articles_per_feed = 15
@ -19,7 +19,7 @@ class AdvancedUserRecipe1325006965(BasicNewsRecipe):
remove_javascript = True
no_stylesheets = True
ignore_duplicate_articles = {'title'}
ignore_duplicate_articles = {'title','url'}
extra_css = '''
@ -72,9 +72,10 @@ class AdvancedUserRecipe1325006965(BasicNewsRecipe):
#cov2 now contains url of the page containing pic
soup = self.index_to_soup(cov2)
cov = soup.find(attrs={'id' : 'large'})
cov2 = str(cov)
cov2=cov2[27:-18]
#cov2 now is pic url, now go back to original function
cov=str(cov)
cov2 = re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', cov)
cov2 = str(cov2)
cov2=cov2[2:len(cov2)-2]
br = browser()
br.set_handle_redirect(False)
try:

View File

@ -23,16 +23,15 @@ class Time(BasicNewsRecipe):
keep_only_tags = [
{
'class':['tout1', 'entry-content', 'external-gallery-img', 'image-meta']
'class':['primary-col', 'tout1']
},
]
remove_tags = [
{'class':['thumbnail', 'button']},
{'class':['button', 'entry-sharing group', 'wp-paginate',
'moving-markup', 'entry-comments']},
]
recursions = 10
match_regexps = [r'/[0-9,]+-(2|3|4|5|6|7|8|9)(,\d+){0,1}.html',r'http://www.time.com/time/specials/packages/article/.*']
extra_css = '.entry-date { padding-left: 2ex }'
preprocess_regexps = [(re.compile(
r'<meta .+/>'), lambda m:'')]
@ -45,7 +44,7 @@ class Time(BasicNewsRecipe):
br.select_form(predicate=lambda f: 'action' in f.attrs and f.attrs['action'] == 'https://auth.time.com/login.php')
br['username'] = self.username
br['password'] = self.password
br['magcode'] = ['TD']
# br['magcode'] = ['TD']
br.find_control('turl').readonly = False
br['turl'] = 'http://www.time.com/time/magazine'
br.find_control('rurl').readonly = False
@ -104,7 +103,14 @@ class Time(BasicNewsRecipe):
method='text').strip()
if not title: continue
url = a[0].get('href')
url = re.sub('/magazine/article/0,9171','/subscriber/printout/0,8816', url)
if url.startswith('/'):
url = 'http://www.time.com'+url
if '/article/0,' in url:
soup = self.index_to_soup(url)
a = soup.find('a', href=lambda x:x and '/printout/' in x)
url = a['href'].replace('/printout', '/subscriber/printout')
else:
url += 'print/' if url.endswith('/') else '/print/'
if url.startswith('/'):
url = 'http://www.time.com'+url
desc = ''
@ -112,10 +118,18 @@ class Time(BasicNewsRecipe):
if p:
desc = html.tostring(p[0], encoding=unicode,
method='text')
self.log('\t', title, ':\n\t\t', desc)
self.log('\t', title, ':\n\t\t', url)
yield {
'title' : title,
'url' : url,
'date' : '',
'description' : desc
}
def preprocess_html(self, soup):
for fig in soup.findAll('figure'):
img = fig.find('img')
if img is not None:
fig.replaceWith(img)
return soup

19
recipes/yazihane.recipe Normal file
View File

@ -0,0 +1,19 @@
from calibre.web.feeds.news import BasicNewsRecipe
import re
class AdvancedUserRecipe1350731826(BasicNewsRecipe):
title = u'Yazihane'
oldest_article = 7
max_articles_per_feed = 100
__author__ = 'A Erdogan'
description = 'Sports Blog'
publisher = 'yazihaneden.com'
category = 'sports, basketball, nba, cycling, euroleague'
no_stylesheets = True
use_embedded_content = False
masthead_url = 'http://www.yazihaneden.com/wp-content/uploads/Untitled-1.png'
language = 'tr'
keep_only_tags = [ dict(name='div', attrs={'id':re.compile('(^|| )post-($|| )', re.DOTALL)})]
remove_tags_after = dict(name='div', attrs={'class':'post-footer clear'})
feeds = [(u'Yazihane', u'http://www.yazihaneden.com/feed/')]

View File

@ -1,5 +1,4 @@
from calibre.web.feeds.news import BasicNewsRecipe
from calibre.ebooks.BeautifulSoup import BeautifulSoup, Tag
class YemenTimesRecipe(BasicNewsRecipe):
__license__ = 'GPL v3'
@ -13,7 +12,7 @@ class YemenTimesRecipe(BasicNewsRecipe):
category = u'News, Opinion, Yemen'
description = u'Award winning weekly from Yemen, promoting press freedom, professional journalism and the defense of human rights.'
oldest_article = 7
oldest_article = 10
max_articles_per_feed = 100
use_embedded_content = False
encoding = 'utf-8'
@ -21,27 +20,13 @@ class YemenTimesRecipe(BasicNewsRecipe):
remove_empty_feeds = True
no_stylesheets = True
remove_javascript = True
auto_cleanup = True
keep_only_tags = []
keep_only_tags.append(dict(name = 'div', attrs = {'id': 'ctl00_ContentPlaceHolder1_MAINNEWS0_Panel1',
'class': 'DMAIN2'}))
remove_attributes = ['style']
INDEX = 'http://www.yementimes.com/'
feeds = []
feeds.append((u'Our Viewpoint', u'http://www.yementimes.com/DEFAULTSUB.ASPX?pnc=6&pnm=OUR%20VIEWPOINT'))
feeds.append((u'Local News', u'http://www.yementimes.com/DEFAULTSUB.ASPX?pnc=3&pnm=Local%20news'))
feeds.append((u'Their News', u'http://www.yementimes.com/DEFAULTSUB.ASPX?pnc=80&pnm=Their%20News'))
feeds.append((u'Report', u'http://www.yementimes.com/DEFAULTSUB.ASPX?pnc=8&pnm=report'))
feeds.append((u'Health', u'http://www.yementimes.com/DEFAULTSUB.ASPX?pnc=51&pnm=health'))
feeds.append((u'Interview', u'http://www.yementimes.com/DEFAULTSUB.ASPX?pnc=77&pnm=interview'))
feeds.append((u'Opinion', u'http://www.yementimes.com/DEFAULTSUB.ASPX?pnc=7&pnm=opinion'))
feeds.append((u'Business', u'http://www.yementimes.com/DEFAULTSUB.ASPX?pnc=5&pnm=business'))
feeds.append((u'Op-Ed', u'http://www.yementimes.com/DEFAULTSUB.ASPX?pnc=81&pnm=Op-Ed'))
feeds.append((u'Culture', u'http://www.yementimes.com/DEFAULTSUB.ASPX?pnc=75&pnm=Culture'))
feeds.append((u'Readers View', u'http://www.yementimes.com/DEFAULTSUB.ASPX?pnc=4&pnm=Readers%20View'))
feeds.append((u'Variety', u'http://www.yementimes.com/DEFAULTSUB.ASPX?pnc=9&pnm=Variety'))
feeds.append((u'Education', u'http://www.yementimes.com/DEFAULTSUB.ASPX?pnc=57&pnm=Education'))
feeds = [
('News',
'http://www.yementimes.com/?tpl=1341'),
]
extra_css = '''
body {font-family:verdana, arial, helvetica, geneva, sans-serif;}
@ -53,73 +38,4 @@ class YemenTimesRecipe(BasicNewsRecipe):
conversion_options = {'comments': description, 'tags': category, 'language': 'en',
'publisher': publisher, 'linearize_tables': True}
def get_browser(self):
br = BasicNewsRecipe.get_browser()
br.set_handle_gzip(True)
return br
def parse_index(self):
answer = []
for feed_title, feed in self.feeds:
soup = self.index_to_soup(feed)
newsbox = soup.find('div', 'newsbox')
main = newsbox.findNextSibling('table')
articles = []
for li in main.findAll('li'):
title = self.tag_to_string(li.a)
url = self.INDEX + li.a['href']
articles.append({'title': title, 'date': None, 'url': url, 'description': '<br/>&nbsp;'})
answer.append((feed_title, articles))
return answer
def preprocess_html(self, soup):
freshSoup = self.getFreshSoup(soup)
headline = soup.find('div', attrs = {'id': 'DVMTIT'})
if headline:
div = headline.findNext('div', attrs = {'id': 'DVTOP'})
img = None
if div:
img = div.find('img')
headline.name = 'h1'
freshSoup.body.append(headline)
if img is not None:
freshSoup.body.append(img)
byline = soup.find('div', attrs = {'id': 'DVTIT'})
if byline:
date_el = byline.find('span')
if date_el:
pub_date = self.tag_to_string(date_el)
date = Tag(soup, 'div', attrs = [('class', 'yemen_date')])
date.append(pub_date)
date_el.extract()
raw = '<br/>'.join(['%s' % (part) for part in byline.findAll(text = True)])
author = BeautifulSoup('<div class="yemen_byline">' + raw + '</div>')
if date is not None:
freshSoup.body.append(date)
freshSoup.body.append(author)
story = soup.find('div', attrs = {'id': 'DVDET'})
if story:
for table in story.findAll('table'):
if table.find('img'):
table['class'] = 'yemen_caption'
freshSoup.body.append(story)
return freshSoup
def getFreshSoup(self, oldSoup):
freshSoup = BeautifulSoup('<html><head><title></title></head><body></body></html>')
if oldSoup.head.title:
freshSoup.head.title.append(self.tag_to_string(oldSoup.head.title))
return freshSoup

BIN
resources/images/font.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

View File

@ -787,12 +787,10 @@ application/x-font-framemaker
application/x-font-ghostscript gsf
application/x-font-libgrx
application/x-font-linux-psf psf
application/x-font-otf otf
application/x-font-pcf pcf
application/x-font-snf snf
application/x-font-speedo
application/x-font-sunos-news
application/x-font-ttf ttc ttf
application/x-font-type1 afm pfa pfb pfm
application/x-font-vfont
application/x-freemind mm
@ -1368,8 +1366,6 @@ text/fb2+xml fb2
text/x-sony-bbeb+xml lrs
application/x-sony-bbeb lrf lrx
application/adobe-page-template+xml xpgt
application/x-font-opentype otf
application/x-font-truetype ttf
application/x-mobipocket-ebook mobi prc azw
application/x-topaz-ebook tpz azw1
application/x-mobipocket-subscription pobi
@ -1381,3 +1377,7 @@ application/x-cb7 cb7
application/x-koboreader-ebook kobo
image/wmf wmf
application/ereader pdb
# See http://idpf.org/epub/30/spec/epub30-publications.html#sec-core-media-types
application/vnd.ms-opentype otf
application/font-woff woff
application/x-font-truetype ttf

View File

@ -236,7 +236,8 @@ th {
/* inlines */
b, strong {
font-weight: bolder;
/* ADE doesn't support bolder with embedded fonts */
font-weight: bold;
}
i, cite, em, var, dfn {

View File

@ -8,7 +8,10 @@ let g:syntastic_cpp_include_dirs = [
\'/usr/include/qt4/QtCore',
\'/usr/include/qt4/QtGui',
\'/usr/include/qt4',
\'/usr/include/freetype2',
\'/usr/include/fontconfig',
\'src/qtcurve/common', 'src/qtcurve',
\'src/sfntly/src', 'src/sfntly/src/sample',
\'/usr/include/ImageMagick',
\]
let g:syntastic_c_include_dirs = g:syntastic_cpp_include_dirs

View File

@ -84,16 +84,18 @@ qt_inc = pyqt.qt_inc_dir
qt_lib = pyqt.qt_lib_dir
ft_lib_dirs = []
ft_libs = []
ft_inc_dirs = []
jpg_libs = []
jpg_lib_dirs = []
fc_inc = '/usr/include/fontconfig'
fc_lib = '/usr/lib'
podofo_inc = '/usr/include/podofo'
podofo_lib = '/usr/lib'
chmlib_inc_dirs = chmlib_lib_dirs = []
sqlite_inc_dirs = []
icu_inc_dirs = []
icu_lib_dirs = []
zlib_inc_dirs = []
zlib_lib_dirs = []
zlib_libs = ['z']
if iswindows:
prefix = r'C:\cygwin\home\kovid\sw'
@ -103,8 +105,6 @@ if iswindows:
'source', 'i18n')]
icu_lib_dirs = [os.path.join(ICU, 'source', 'lib')]
sqlite_inc_dirs = [sw_inc_dir]
fc_inc = os.path.join(sw_inc_dir, 'fontconfig')
fc_lib = sw_lib_dir
chmlib_inc_dirs = consolidate('CHMLIB_INC_DIR', os.path.join(prefix,
'build', 'chmlib-0.40', 'src'))
chmlib_lib_dirs = consolidate('CHMLIB_LIB_DIR', os.path.join(prefix,
@ -116,6 +116,10 @@ if iswindows:
jpg_libs = ['jpeg']
ft_lib_dirs = [sw_lib_dir]
ft_libs = ['freetype']
ft_inc_dirs = [sw_inc_dir]
zlib_inc_dirs = [sw_inc_dir]
zlib_lib_dirs = [sw_lib_dir]
zlib_libs = ['zlib']
magick_inc_dirs = [os.path.join(prefix, 'build', 'ImageMagick-6.7.6')]
magick_lib_dirs = [os.path.join(magick_inc_dirs[0], 'VisualMagick', 'lib')]
@ -123,8 +127,6 @@ if iswindows:
podofo_inc = os.path.join(sw_inc_dir, 'podofo')
podofo_lib = sw_lib_dir
elif isosx:
fc_inc = '/sw/include/fontconfig'
fc_lib = '/sw/lib'
podofo_inc = '/sw/podofo'
podofo_lib = '/sw/lib'
magick_inc_dirs = consolidate('MAGICK_INC',
@ -135,6 +137,8 @@ elif isosx:
png_inc_dirs = consolidate('PNG_INC_DIR', '/sw/include')
png_lib_dirs = consolidate('PNG_LIB_DIR', '/sw/lib')
png_libs = ['png12']
ft_libs = ['freetype']
ft_inc_dirs = ['/sw/include/freetype2']
else:
# Include directories
png_inc_dirs = pkgconfig_include_dirs('libpng', 'PNG_INC_DIR',
@ -150,15 +154,12 @@ else:
if not magick_libs:
magick_libs = ['MagickWand', 'MagickCore']
png_libs = ['png']
ft_inc_dirs = pkgconfig_include_dirs('freetype2', 'FT_INC_DIR',
'/usr/include/freetype2')
ft_lib_dirs = pkgconfig_lib_dirs('freetype2', 'FT_LIB_DIR', '/usr/lib')
ft_libs = pkgconfig_libs('freetype2', '', '')
fc_inc = os.environ.get('FC_INC_DIR', fc_inc)
fc_lib = os.environ.get('FC_LIB_DIR', fc_lib)
fc_error = None if os.path.exists(os.path.join(fc_inc, 'fontconfig.h')) else \
('fontconfig header files not found on your system. '
'Try setting the FC_INC_DIR and FC_LIB_DIR environment '
'variables.')
magick_error = None
if not magick_inc_dirs or not os.path.exists(os.path.join(magick_inc_dirs[0],
'wand')):

View File

@ -13,11 +13,13 @@ from multiprocessing import cpu_count
from PyQt4.pyqtconfig import QtGuiModuleMakefile
from setup import Command, islinux, isbsd, isosx, SRC, iswindows
from setup.build_environment import (fc_inc, fc_lib, chmlib_inc_dirs, fc_error,
from setup.build_environment import (chmlib_inc_dirs,
podofo_inc, podofo_lib, podofo_error, pyqt, OSX_SDK, NMAKE, QMAKE,
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)
icu_lib_dirs, win_ddk_lib_dirs, ft_libs, ft_lib_dirs, ft_inc_dirs,
zlib_libs, zlib_lib_dirs, zlib_inc_dirs)
from setup.sfntly import SfntlyBuilderMixin
MT
isunix = islinux or isosx or isbsd
@ -47,13 +49,12 @@ class Extension(object):
self.optional = kwargs.get('optional', False)
self.needs_ddk = kwargs.get('needs_ddk', False)
def preflight(self, obj_dir, compiler, linker, builder, cflags, ldflags):
pass
reflow_sources = glob.glob(os.path.join(SRC, 'calibre', 'ebooks', 'pdf', '*.cpp'))
reflow_headers = glob.glob(os.path.join(SRC, 'calibre', 'ebooks', 'pdf', '*.h'))
pdfreflow_libs = []
if iswindows:
pdfreflow_libs = ['advapi32', 'User32', 'Gdi32', 'zlib']
icu_libs = ['icudata', 'icui18n', 'icuuc', 'icuio']
icu_cflags = []
if iswindows:
@ -62,9 +63,26 @@ if isosx:
icu_libs = ['icucore']
icu_cflags = ['-DU_DISABLE_RENAMING'] # Needed to use system libicucore.dylib
class SfntlyExtension(Extension, SfntlyBuilderMixin):
def __init__(self, *args, **kwargs):
Extension.__init__(self, *args, **kwargs)
SfntlyBuilderMixin.__init__(self)
def preflight(self, *args, **kwargs):
self(*args, **kwargs)
extensions = [
SfntlyExtension('sfntly',
['calibre/utils/fonts/sfntly.cpp'],
headers= ['calibre/utils/fonts/sfntly.h'],
libraries=icu_libs,
lib_dirs=icu_lib_dirs,
inc_dirs=icu_inc_dirs,
cflags=icu_cflags
),
Extension('speedup',
['calibre/utils/speedup.c'],
),
@ -119,12 +137,23 @@ extensions = [
'calibre/utils/lzx/mspack.h'],
inc_dirs=['calibre/utils/lzx']),
Extension('fontconfig',
['calibre/utils/fonts/fontconfig.c'],
inc_dirs = [fc_inc],
libraries=['fontconfig'],
lib_dirs=[fc_lib],
error=fc_error),
Extension('freetype',
['calibre/utils/fonts/freetype.cpp'],
inc_dirs = ft_inc_dirs,
libraries=ft_libs,
lib_dirs=ft_lib_dirs),
Extension('woff',
['calibre/utils/fonts/woff/main.c',
'calibre/utils/fonts/woff/woff.c'],
headers=[
'calibre/utils/fonts/woff/woff.h',
'calibre/utils/fonts/woff/woff-private.h'],
libraries=zlib_libs,
lib_dirs=zlib_lib_dirs,
inc_dirs=zlib_inc_dirs,
),
Extension('msdes',
['calibre/utils/msdes/msdesmodule.c',
@ -228,6 +257,7 @@ if isunix:
cc = os.environ.get('CC', 'gcc')
cxx = os.environ.get('CXX', 'g++')
cflags = os.environ.get('OVERRIDE_CFLAGS',
# '-Wall -DNDEBUG -ggdb -fno-strict-aliasing -pipe')
'-O3 -Wall -DNDEBUG -fno-strict-aliasing -pipe')
cflags = shlex.split(cflags) + ['-fPIC']
ldflags = os.environ.get('OVERRIDE_LDFLAGS', '-Wall')
@ -290,9 +320,6 @@ class Build(Command):
CFLAGS - Extra compiler flags
LDFLAGS - Extra linker flags
FC_INC_DIR - fontconfig header files
FC_LIB_DIR - fontconfig library
POPPLER_INC_DIR - poppler header files
POPPLER_LIB_DIR - poppler-qt4 library
@ -358,8 +385,9 @@ class Build(Command):
compiler = cxx if ext.needs_cxx else cc
linker = msvc.linker if iswindows else compiler
objects = []
einc = self.inc_dirs_to_cflags(ext.inc_dirs)
obj_dir = self.j(self.obj_dir, ext.name)
ext.preflight(obj_dir, compiler, linker, self, cflags, ldflags)
einc = self.inc_dirs_to_cflags(ext.inc_dirs)
if ext.needs_ddk:
ddk_flags = ['-I'+x for x in win_ddk]
cflags.extend(ddk_flags)
@ -380,7 +408,7 @@ class Build(Command):
dest = self.dest(ext)
elib = self.lib_dirs_to_ldflags(ext.lib_dirs)
xlib = self.libraries_to_ldflags(ext.libraries)
if self.newer(dest, objects):
if self.newer(dest, objects+ext.extra_objs):
print 'Linking', ext.name
cmd = [linker]
if iswindows:

View File

@ -15,8 +15,8 @@ from setup import __version__ as VERSION, __appname__ as APPNAME, basenames, \
LICENSE = open('LICENSE', 'rb').read()
MAGICK_HOME='@executable_path/../Frameworks/ImageMagick'
ENV = dict(
FC_CONFIG_DIR='@executable_path/../Resources/fonts',
FC_CONFIG_FILE='@executable_path/../Resources/fonts/fonts.conf',
FONTCONFIG_PATH='@executable_path/../Resources/fonts',
FONTCONFIG_FILE='@executable_path/../Resources/fonts/fonts.conf',
MAGICK_CONFIGURE_PATH=MAGICK_HOME+'/config',
MAGICK_CODER_MODULE_PATH=MAGICK_HOME+'/modules-Q16/coders',
MAGICK_CODER_FILTER_PATH=MAGICK_HOME+'/modules-Q16/filter',
@ -379,7 +379,7 @@ class Py2App(object):
@flush
def add_poppler(self):
info('\nAdding poppler')
for x in ('libpoppler.27.dylib',):
for x in ('libpoppler.28.dylib',):
self.install_dylib(os.path.join(SW, 'lib', x))
for x in ('pdftohtml', 'pdftoppm', 'pdfinfo'):
self.install_dylib(os.path.join(SW, 'bin', x), False)
@ -411,7 +411,6 @@ class Py2App(object):
raw = open(fc, 'rb').read()
raw = raw.replace('<dir>/usr/share/fonts</dir>', '''\
<dir>/Library/Fonts</dir>
<dir>/Network/Library/Fonts</dir>
<dir>/System/Library/Fonts</dir>
<dir>/usr/X11R6/lib/X11/fonts</dir>
<dir>/usr/share/fonts</dir>

View File

@ -281,8 +281,6 @@ class Win32Freeze(Command, WixMixIn):
for x in ('zlib1.dll', 'libxml2.dll'):
shutil.copy2(self.j(bindir, x+'.manifest'), self.dll_dir)
shutil.copytree(os.path.join(SW, 'etc', 'fonts'),
os.path.join(self.base, 'fontconfig'))
# Copy ImageMagick
for pat in ('*.dll', '*.xml'):
for f in glob.glob(self.j(IMAGEMAGICK, pat)):

View File

@ -276,27 +276,6 @@ cp build/kdewin32-msvc-0.3.9/build/bin/Release/*.exp lib/
cp -r build/kdewin32-msvc-0.3.9/include/msvc/ include/
cp build/kdewin32-msvc-0.3.9/include/*.h include/
fontconfig
---------------
Get it from http://www.winkde.org/pub/kde/ports/win32/repository/win32libs/
mkdir build
Remove subdirectory test from the bottom of CMakeLists.txt
run cmake
Set build type to release and project config to dll
Right click on the fontconfig project and select properties. Add sw/include/msvc to the include paths
Build only fontconfig
cp build/fontconfig-msvc-2.4.2-3/build/src/Release/*.dll bin
cp build/fontconfig-msvc-2.4.2-3/build/src/Release/*.lib lib
cp build/fontconfig-msvc-2.4.2-3/build/src/Release/*.exp lib
cp -r build/fontconfig-msvc-2.4.2-3/fontconfig/ include/
Also install the etc files from the font-config-bin archive from kde win32libs
It contains correct fonts.conf etc.
poppler
-------------

View File

@ -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-07-23 10:54+0000\n"
"Last-Translator: jmontane <Unknown>\n"
"PO-Revision-Date: 2012-10-28 09:28+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-07-24 04:52+0000\n"
"X-Generator: Launchpad (build 15668)\n"
"X-Launchpad-Export-Date: 2012-10-29 04:59+0000\n"
"X-Generator: Launchpad (build 16194)\n"
"Language: ca\n"
#. name for aaa
@ -4744,7 +4744,7 @@ msgstr "Chachi"
#. name for cbj
msgid "Ede Cabe"
msgstr "Ede Cabe"
msgstr "Ede;Cabe"
#. name for cbk
msgid "Chavacano"
@ -9936,7 +9936,7 @@ msgstr "Ibani"
#. name for ica
msgid "Ede Ica"
msgstr "Ede Ica"
msgstr "Ede;Ica"
#. name for ich
msgid "Etkywan"
@ -9964,7 +9964,7 @@ msgstr "Idon"
#. name for idd
msgid "Ede Idaca"
msgstr "Ede Idaca"
msgstr "Ede;Idaca"
#. name for ide
msgid "Idere"
@ -9972,7 +9972,7 @@ msgstr "Idere"
#. name for idi
msgid "Idi"
msgstr ""
msgstr "Idi"
#. name for ido
msgid "Ido"
@ -10032,35 +10032,35 @@ msgstr "Ebira"
#. name for ige
msgid "Igede"
msgstr ""
msgstr "Igede"
#. name for igg
msgid "Igana"
msgstr ""
msgstr "Igana"
#. name for igl
msgid "Igala"
msgstr ""
msgstr "Igala"
#. name for igm
msgid "Kanggape"
msgstr ""
msgstr "Igom"
#. name for ign
msgid "Ignaciano"
msgstr ""
msgstr "Ignaciano"
#. name for igo
msgid "Isebe"
msgstr ""
msgstr "Isebe"
#. name for igs
msgid "Interglossa"
msgstr ""
msgstr "Interglossa"
#. name for igw
msgid "Igwe"
msgstr ""
msgstr "Igwe"
#. name for ihb
msgid "Iha Based Pidgin"
@ -10068,175 +10068,175 @@ msgstr "Iha; parla mixta"
#. name for ihi
msgid "Ihievbe"
msgstr ""
msgstr "Ihievbe"
#. name for ihp
msgid "Iha"
msgstr ""
msgstr "Iha"
#. name for iii
msgid "Yi; Sichuan"
msgstr ""
msgstr "Yi; Sichuan"
#. name for ijc
msgid "Izon"
msgstr ""
msgstr "Izon"
#. name for ije
msgid "Biseni"
msgstr ""
msgstr "Biseni"
#. name for ijj
msgid "Ede Ije"
msgstr ""
msgstr "Ede;Ije"
#. name for ijn
msgid "Kalabari"
msgstr ""
msgstr "Kalabari"
#. name for ijs
msgid "Ijo; Southeast"
msgstr ""
msgstr "Izon;Ijo"
#. name for ike
msgid "Inuktitut; Eastern Canadian"
msgstr ""
msgstr "Inuktitut; Canadà oriental"
#. name for iki
msgid "Iko"
msgstr ""
msgstr "Iko"
#. name for ikk
msgid "Ika"
msgstr ""
msgstr "Ika"
#. name for ikl
msgid "Ikulu"
msgstr ""
msgstr "Ikulu"
#. name for iko
msgid "Olulumo-Ikom"
msgstr ""
msgstr "Olulumo-Ikom"
#. name for ikp
msgid "Ikpeshi"
msgstr ""
msgstr "Ikpeshi"
#. name for ikt
msgid "Inuktitut; Western Canadian"
msgstr ""
msgstr "Inuktitut; Canadà occidental"
#. name for iku
msgid "Inuktitut"
msgstr "inuktitut"
msgstr "Inuktitut"
#. name for ikv
msgid "Iku-Gora-Ankwa"
msgstr ""
msgstr "Iku"
#. name for ikw
msgid "Ikwere"
msgstr ""
msgstr "Ikwere"
#. name for ikx
msgid "Ik"
msgstr ""
msgstr "Ik"
#. name for ikz
msgid "Ikizu"
msgstr ""
msgstr "Ikizu"
#. name for ila
msgid "Ile Ape"
msgstr ""
msgstr "Ile Ape"
#. name for ilb
msgid "Ila"
msgstr ""
msgstr "Ila"
#. name for ile
msgid "Interlingue"
msgstr ""
msgstr "Interlingue"
#. name for ilg
msgid "Garig-Ilgar"
msgstr ""
msgstr "Garig-Ilgar"
#. name for ili
msgid "Ili Turki"
msgstr ""
msgstr "Ili turc"
#. name for ilk
msgid "Ilongot"
msgstr ""
msgstr "Ilong"
#. name for ill
msgid "Iranun"
msgstr ""
msgstr "Iranun"
#. name for ilo
msgid "Iloko"
msgstr ""
msgstr "Ilocà"
#. name for ils
msgid "International Sign"
msgstr ""
msgstr "Signes internacional"
#. name for ilu
msgid "Ili'uun"
msgstr ""
msgstr "Iliun"
#. name for ilv
msgid "Ilue"
msgstr ""
msgstr "Ilue"
#. name for ilw
msgid "Talur"
msgstr ""
msgstr "Talur"
#. name for ima
msgid "Malasar; Mala"
msgstr ""
msgstr "Malasar; Mala"
#. name for ime
msgid "Imeraguen"
msgstr ""
msgstr "Imeraguen"
#. name for imi
msgid "Anamgura"
msgstr ""
msgstr "Anamgura"
#. name for iml
msgid "Miluk"
msgstr ""
msgstr "Miluk"
#. name for imn
msgid "Imonda"
msgstr ""
msgstr "Imonda"
#. name for imo
msgid "Imbongu"
msgstr ""
msgstr "Imbongu"
#. name for imr
msgid "Imroing"
msgstr ""
msgstr "Imroing"
#. name for ims
msgid "Marsian"
msgstr ""
msgstr "Marsià"
#. name for imy
msgid "Milyan"
msgstr ""
msgstr "Mili"
#. name for ina
msgid "Interlingua (International Auxiliary Language Association)"
msgstr ""
msgstr "Interlingua"
#. name for inb
msgid "Inga"
msgstr ""
msgstr "Inga"
#. name for ind
msgid "Indonesian"
@ -10244,15 +10244,15 @@ msgstr "Indonesi"
#. name for ing
msgid "Degexit'an"
msgstr ""
msgstr "Degexitan"
#. name for inh
msgid "Ingush"
msgstr ""
msgstr "Ingúix"
#. name for inj
msgid "Inga; Jungle"
msgstr ""
msgstr "Inga; Jungla"
#. name for inl
msgid "Indonesian Sign Language"
@ -10260,19 +10260,19 @@ msgstr "Llenguatge de signes indonesi"
#. name for inm
msgid "Minaean"
msgstr ""
msgstr "Minaeà"
#. name for inn
msgid "Isinai"
msgstr ""
msgstr "Isinai"
#. name for ino
msgid "Inoke-Yate"
msgstr ""
msgstr "Inoke-Yate"
#. name for inp
msgid "Iñapari"
msgstr ""
msgstr "Iñapari"
#. name for ins
msgid "Indian Sign Language"
@ -10280,27 +10280,27 @@ msgstr "Llenguatge de signes indi"
#. name for int
msgid "Intha"
msgstr ""
msgstr "Intha"
#. name for inz
msgid "Ineseño"
msgstr ""
msgstr "Ineseño"
#. name for ior
msgid "Inor"
msgstr ""
msgstr "Inor"
#. name for iou
msgid "Tuma-Irumu"
msgstr ""
msgstr "Tuma-Irumu"
#. name for iow
msgid "Iowa-Oto"
msgstr ""
msgstr "Iowa-Oto"
#. name for ipi
msgid "Ipili"
msgstr ""
msgstr "Ipili"
#. name for ipk
msgid "Inupiaq"
@ -10308,59 +10308,59 @@ msgstr "inupiak"
#. name for ipo
msgid "Ipiko"
msgstr ""
msgstr "Ipiko"
#. name for iqu
msgid "Iquito"
msgstr ""
msgstr "Iquito"
#. name for ire
msgid "Iresim"
msgstr ""
msgstr "Iresim"
#. name for irh
msgid "Irarutu"
msgstr ""
msgstr "Irarutu"
#. name for iri
msgid "Irigwe"
msgstr ""
msgstr "Irigwe"
#. name for irk
msgid "Iraqw"
msgstr ""
msgstr "Iraqw"
#. name for irn
msgid "Irántxe"
msgstr ""
msgstr "Iranxe"
#. name for irr
msgid "Ir"
msgstr ""
msgstr "Ir"
#. name for iru
msgid "Irula"
msgstr ""
msgstr "Irula"
#. name for irx
msgid "Kamberau"
msgstr ""
msgstr "Kamberau"
#. name for iry
msgid "Iraya"
msgstr ""
msgstr "Iraya"
#. name for isa
msgid "Isabi"
msgstr ""
msgstr "Isabi"
#. name for isc
msgid "Isconahua"
msgstr ""
msgstr "Isconahua"
#. name for isd
msgid "Isnag"
msgstr ""
msgstr "Isneg"
#. name for ise
msgid "Italian Sign Language"
@ -10372,15 +10372,15 @@ msgstr "Llenguatge de signes irlandès"
#. name for ish
msgid "Esan"
msgstr ""
msgstr "Esan"
#. name for isi
msgid "Nkem-Nkum"
msgstr ""
msgstr "Nkem-Nkum"
#. name for isk
msgid "Ishkashimi"
msgstr ""
msgstr "Ishkashimi"
#. name for isl
msgid "Icelandic"
@ -10388,15 +10388,15 @@ msgstr "Islandès"
#. name for ism
msgid "Masimasi"
msgstr ""
msgstr "Masimasi"
#. name for isn
msgid "Isanzu"
msgstr ""
msgstr "Isanzu"
#. name for iso
msgid "Isoko"
msgstr ""
msgstr "Isoco"
#. name for isr
msgid "Israeli Sign Language"
@ -10404,11 +10404,11 @@ msgstr "Llenguatge de signes israelià"
#. name for ist
msgid "Istriot"
msgstr ""
msgstr "Istri"
#. name for isu
msgid "Isu (Menchum Division)"
msgstr ""
msgstr "Isu (Divisió de Menchum)"
#. name for ita
msgid "Italian"
@ -10416,15 +10416,15 @@ msgstr "Italià"
#. name for itb
msgid "Itneg; Binongan"
msgstr ""
msgstr "Itneg; Binongan"
#. name for ite
msgid "Itene"
msgstr ""
msgstr "Itene"
#. name for iti
msgid "Itneg; Inlaod"
msgstr ""
msgstr "Itneg; Inlaod"
#. name for itk
msgid "Judeo-Italian"
@ -10432,147 +10432,147 @@ msgstr "Judeo-italià"
#. name for itl
msgid "Itelmen"
msgstr ""
msgstr "Itelmen"
#. name for itm
msgid "Itu Mbon Uzo"
msgstr ""
msgstr "Itu Mbon Uzo"
#. name for ito
msgid "Itonama"
msgstr ""
msgstr "Itonama"
#. name for itr
msgid "Iteri"
msgstr ""
msgstr "Iteri"
#. name for its
msgid "Isekiri"
msgstr ""
msgstr "Isekiri"
#. name for itt
msgid "Itneg; Maeng"
msgstr ""
msgstr "Itneg; Maeng"
#. name for itv
msgid "Itawit"
msgstr ""
msgstr "Itawit"
#. name for itw
msgid "Ito"
msgstr ""
msgstr "Ito"
#. name for itx
msgid "Itik"
msgstr ""
msgstr "Itik"
#. name for ity
msgid "Itneg; Moyadan"
msgstr ""
msgstr "Itneg; Moyadan"
#. name for itz
msgid "Itzá"
msgstr ""
msgstr "Itzà"
#. name for ium
msgid "Mien; Iu"
msgstr ""
msgstr "Mien; Iu"
#. name for ivb
msgid "Ibatan"
msgstr ""
msgstr "Ibatan"
#. name for ivv
msgid "Ivatan"
msgstr ""
msgstr "Ivatan"
#. name for iwk
msgid "I-Wak"
msgstr ""
msgstr "Iwaak"
#. name for iwm
msgid "Iwam"
msgstr ""
msgstr "Iwam"
#. name for iwo
msgid "Iwur"
msgstr ""
msgstr "Iwur"
#. name for iws
msgid "Iwam; Sepik"
msgstr ""
msgstr "Iwam; Sepik"
#. name for ixc
msgid "Ixcatec"
msgstr ""
msgstr "Ixcatec"
#. name for ixl
msgid "Ixil"
msgstr ""
msgstr "Ixil"
#. name for iya
msgid "Iyayu"
msgstr ""
msgstr "Iyayu"
#. name for iyo
msgid "Mesaka"
msgstr ""
msgstr "Mesaka"
#. name for iyx
msgid "Yaka (Congo)"
msgstr ""
msgstr "Yaka (Congo)"
#. name for izh
msgid "Ingrian"
msgstr ""
msgstr "Ingri"
#. name for izi
msgid "Izi-Ezaa-Ikwo-Mgbo"
msgstr ""
msgstr "Izi-Ezaa-Ikwo-Mgbo"
#. name for izr
msgid "Izere"
msgstr ""
msgstr "Izere"
#. name for jaa
msgid "Jamamadí"
msgstr ""
msgstr "Jamamadí"
#. name for jab
msgid "Hyam"
msgstr ""
msgstr "Ham"
#. name for jac
msgid "Popti'"
msgstr ""
msgstr "Jacaltec"
#. name for jad
msgid "Jahanka"
msgstr ""
msgstr "Jahanka"
#. name for jae
msgid "Yabem"
msgstr ""
msgstr "Yabem"
#. name for jaf
msgid "Jara"
msgstr ""
msgstr "Jara"
#. name for jah
msgid "Jah Hut"
msgstr ""
msgstr "Jah Hut"
#. name for jaj
msgid "Zazao"
msgstr ""
msgstr "Zazao"
#. name for jak
msgid "Jakun"
msgstr ""
msgstr "Jakun"
#. name for jal
msgid "Yalahatan"
msgstr ""
msgstr "Jalahatan"
#. name for jam
msgid "Creole English; Jamaican"
@ -10580,31 +10580,31 @@ msgstr "Anglès crioll; Jamaica"
#. name for jao
msgid "Yanyuwa"
msgstr ""
msgstr "Yanyula"
#. name for jaq
msgid "Yaqay"
msgstr ""
msgstr "Yaqay"
#. name for jar
msgid "Jarawa (Nigeria)"
msgstr ""
msgstr "Jarawa (Nigèria)"
#. name for jas
msgid "Javanese; New Caledonian"
msgstr ""
msgstr "Javanès; Nova Caledònia"
#. name for jat
msgid "Jakati"
msgstr ""
msgstr "Jakati"
#. name for jau
msgid "Yaur"
msgstr ""
msgstr "Yaur"
#. name for jav
msgid "Javanese"
msgstr ""
msgstr "Javanès"
#. name for jax
msgid "Malay; Jambi"
@ -10612,11 +10612,11 @@ msgstr "Malai; Jambi"
#. name for jay
msgid "Yan-nhangu"
msgstr ""
msgstr "Jarnango"
#. name for jaz
msgid "Jawe"
msgstr ""
msgstr "Jawe"
#. name for jbe
msgid "Judeo-Berber"
@ -10624,27 +10624,27 @@ msgstr "Judeo-berber"
#. name for jbj
msgid "Arandai"
msgstr ""
msgstr "Arandai"
#. name for jbn
msgid "Nafusi"
msgstr ""
msgstr "Djerbi"
#. name for jbo
msgid "Lojban"
msgstr ""
msgstr "Lojban"
#. name for jbr
msgid "Jofotek-Bromnya"
msgstr ""
msgstr "Jofotek-Bromnya"
#. name for jbt
msgid "Jabutí"
msgstr ""
msgstr "Jaboti"
#. name for jbu
msgid "Jukun Takum"
msgstr ""
msgstr "Jukun Takum"
#. name for jcs
msgid "Jamaican Country Sign Language"
@ -10652,31 +10652,31 @@ msgstr "Llenguatge de signes del país jamaicà"
#. name for jct
msgid "Krymchak"
msgstr ""
msgstr "Judeocrimeà"
#. name for jda
msgid "Jad"
msgstr ""
msgstr "Jad"
#. name for jdg
msgid "Jadgali"
msgstr ""
msgstr "Jadgali"
#. name for jdt
msgid "Judeo-Tat"
msgstr ""
msgstr "Judeotat"
#. name for jeb
msgid "Jebero"
msgstr ""
msgstr "Jebero"
#. name for jee
msgid "Jerung"
msgstr ""
msgstr "Jerung"
#. name for jeg
msgid "Jeng"
msgstr ""
msgstr "Jeng"
#. name for jeh
msgid "Jeh"
@ -11012,7 +11012,7 @@ msgstr ""
#. name for jvn
msgid "Javanese; Caribbean"
msgstr ""
msgstr "Javanès; Carib"
#. name for jwi
msgid "Jwira-Pepesa"
@ -18960,7 +18960,7 @@ msgstr ""
#. name for nqk
msgid "Ede Nago; Kura"
msgstr ""
msgstr "Ede;Nago Kura"
#. name for nqm
msgid "Ndom"
@ -25172,7 +25172,7 @@ msgstr ""
#. name for tis
msgid "Itneg; Masadiit"
msgstr ""
msgstr "Itneg; Masadiit"
#. name for tit
msgid "Tinigua"
@ -29604,7 +29604,7 @@ msgstr ""
#. name for yix
msgid "Yi; Axi"
msgstr ""
msgstr "Yi; Axi"
#. name for yiy
msgid "Yir Yoront"
@ -29688,7 +29688,7 @@ msgstr ""
#. name for ylo
msgid "Yi; Naluo"
msgstr ""
msgstr "Yi; Naluo"
#. name for ylr
msgid "Yalarnnga"
@ -29764,7 +29764,7 @@ msgstr ""
#. name for ymr
msgid "Malasar"
msgstr ""
msgstr "Malasar"
#. name for yms
msgid "Mysian"
@ -30104,7 +30104,7 @@ msgstr ""
#. name for ywq
msgid "Yi; Wuding-Luquan"
msgstr ""
msgstr "Yi; Wuding-luqua"
#. name for ywr
msgid "Yawuru"

View File

@ -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-04-18 13:08+0000\n"
"Last-Translator: Asier Iturralde Sarasola <Unknown>\n"
"PO-Revision-Date: 2012-10-29 14:16+0000\n"
"Last-Translator: gorkaazk <gorkaazkarate@euskalerria.org>\n"
"Language-Team: Euskara <itzulpena@comtropos.com>\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Launchpad-Export-Date: 2012-04-19 04:36+0000\n"
"X-Generator: Launchpad (build 15108)\n"
"X-Launchpad-Export-Date: 2012-10-30 04:44+0000\n"
"X-Generator: Launchpad (build 16206)\n"
"Language: eu\n"
#. name for aaa
@ -73,7 +73,7 @@ msgstr "Anambé"
#. name for aao
msgid "Arabic; Algerian Saharan"
msgstr ""
msgstr "Arabiera, Aljeriako Saharakoa"
#. name for aap
msgid "Arára; Pará"
@ -181,31 +181,32 @@ msgstr "Abazera"
#. name for abr
msgid "Abron"
msgstr ""
msgstr "Abron; (bono hizkuntza, Ghana)"
#. name for abs
msgid "Malay; Ambonese"
msgstr ""
msgstr "Malaysiera; (\"ambonese\" hizkuntza)"
#. name for abt
msgid "Ambulas"
msgstr ""
msgstr "Ambulas hizkuntza"
#. name for abu
msgid "Abure"
msgstr ""
"Abure hizkuntza (edo abonwa; edo akaplass) (Niger, Kongo, Boli-kosta)"
#. name for abv
msgid "Arabic; Baharna"
msgstr ""
msgstr "Arabiera; baharna"
#. name for abw
msgid "Pal"
msgstr ""
msgstr "Pal hizkuntza (Papua)"
#. name for abx
msgid "Inabaknon"
msgstr ""
msgstr "Inabaknon hizkuntza (edo abaknon, Filipina uharteak)"
#. name for aby
msgid "Aneme Wake"

View File

@ -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-02-01 20:12+0000\n"
"PO-Revision-Date: 2012-10-24 18:16+0000\n"
"Last-Translator: drMerry <Unknown>\n"
"Language-Team: Dutch <vertaling@vrijschrift.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-02-02 05:57+0000\n"
"X-Generator: Launchpad (build 14738)\n"
"X-Launchpad-Export-Date: 2012-10-25 05:35+0000\n"
"X-Generator: Launchpad (build 16179)\n"
"Language: nl\n"
#. name for aaa
@ -332,7 +332,7 @@ msgstr "Andegerebinha"
#. name for adh
msgid "Adhola"
msgstr ""
msgstr "Adhola"
#. name for adi
msgid "Adi"
@ -30436,11 +30436,11 @@ msgstr ""
#. name for zma
msgid "Manda (Australia)"
msgstr ""
msgstr "Manda (Australië)"
#. name for zmb
msgid "Zimba"
msgstr ""
msgstr "Zimba"
#. name for zmc
msgid "Margany"

View File

@ -13,14 +13,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-06-14 09:06+0000\n"
"Last-Translator: Eugene Marshal <Unknown>\n"
"PO-Revision-Date: 2012-10-20 00:57+0000\n"
"Last-Translator: Ida Leter <iatheia@yandex.ru>\n"
"Language-Team: Russian <debian-l10n-russian@lists.debian.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-06-15 04:42+0000\n"
"X-Generator: Launchpad (build 15414)\n"
"X-Launchpad-Export-Date: 2012-10-21 04:41+0000\n"
"X-Generator: Launchpad (build 16165)\n"
"Language: ru\n"
#. name for aaa
@ -41,7 +41,7 @@ msgstr "Амал"
#. name for aae
msgid "Albanian; Arbëreshë"
msgstr ""
msgstr "Албанский; диалект Арбереши"
#. name for aaf
msgid "Aranadan"
@ -53,7 +53,7 @@ msgstr "Амбрак"
#. name for aah
msgid "Arapesh; Abu'"
msgstr ""
msgstr "Арапешей; Абу'"
#. name for aai
msgid "Arifama-Miniafia"
@ -77,15 +77,15 @@ msgstr "Анамбе"
#. name for aao
msgid "Arabic; Algerian Saharan"
msgstr ""
msgstr "Арабский; Алжирская Сахара"
#. name for aap
msgid "Arára; Pará"
msgstr ""
msgstr "Арара; Пара"
#. name for aaq
msgid "Abnaki; Eastern"
msgstr ""
msgstr "Абенаки; Восточный"
#. name for aar
msgid "Afar"
@ -97,7 +97,7 @@ msgstr "Асакс"
#. name for aat
msgid "Albanian; Arvanitika"
msgstr ""
msgstr "Албанский; Арнаутский диалект"
#. name for aau
msgid "Abau"
@ -125,7 +125,7 @@ msgstr "Банкон"
#. name for abc
msgid "Ayta; Ambala"
msgstr ""
msgstr "Айта; Амбала"
#. name for abd
msgid "Manide"
@ -133,7 +133,7 @@ msgstr "Мэнайд"
#. name for abe
msgid "Abnaki; Western"
msgstr ""
msgstr "Абенаки; Западный"
#. name for abf
msgid "Abai Sungai"
@ -145,7 +145,7 @@ msgstr "Абага"
#. name for abh
msgid "Arabic; Tajiki"
msgstr ""
msgstr "Арабский; Таджи"
#. name for abi
msgid "Abidji"
@ -177,7 +177,7 @@ msgstr "Абон"
#. name for abp
msgid "Ayta; Abellen"
msgstr ""
msgstr "Айта; Абенлен"
#. name for abq
msgid "Abaza"

93
setup/sfntly.py Normal file
View File

@ -0,0 +1,93 @@
#!/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 shlex, os
from glob import glob
from setup import iswindows
class Group(object):
def __init__(self, name, base, build_base, cflags):
self.name = name
self.cflags = cflags
self.headers = frozenset(glob(os.path.join(base, '*.h')))
self.src_files = glob(os.path.join(base, '*.cc'))
self.bdir = os.path.abspath(os.path.join(build_base, name))
if not os.path.exists(self.bdir):
os.makedirs(self.bdir)
self.objects = [os.path.join(self.bdir,
os.path.basename(x).rpartition('.')[0] + ('.obj' if iswindows else
'.o')) for x in self.src_files]
def __call__(self, compiler, linker, builder, all_headers):
for src, obj in zip(self.src_files, self.objects):
if builder.newer(obj, [src] + list(all_headers)):
sinc = ['/Tp'+src] if iswindows else ['-c', src]
oinc = ['/Fo'+obj] if iswindows else ['-o', obj]
cmd = [compiler] + self.cflags + sinc + oinc
builder.info(' '.join(cmd))
builder.check_call(cmd)
class SfntlyBuilderMixin(object):
def __init__(self):
self.sfntly_cflags = [
'-DSFNTLY_NO_EXCEPTION',
'-DSFNTLY_EXPERIMENTAL',
]
if iswindows:
self.sfntly_cflags += [
'-D_UNICODE', '-DUNICODE',
] + shlex.split('/W4 /WX /Gm- /Gy /GR-')
self.cflags += ['-DWIN32']
else:
# Possibly add -fno-inline (slower, but more robust)
self.sfntly_cflags += [
'-Werror',
'-fno-exceptions',
]
if len(self.libraries) > 1:
self.libraries = ['icuuc']
if not iswindows:
self.libraries += ['pthread']
def __call__(self, obj_dir, compiler, linker, builder, cflags, ldflags):
self.sfntly_build_dir = os.path.join(obj_dir, 'sfntly')
if '/Ox' in cflags:
cflags.remove('/Ox')
if '-O3' in cflags:
cflags.remove('-O3')
if '/W3' in cflags:
cflags.remove('/W3')
if '-ggdb' not in cflags:
cflags.insert(0, '/O2' if iswindows else '-O2')
groups = []
all_headers = set()
all_objects = []
src_dir = self.absolutize([os.path.join('sfntly', 'src')])[0]
inc_dirs = [src_dir]
self.inc_dirs += inc_dirs
inc_flags = builder.inc_dirs_to_cflags(self.inc_dirs)
for loc in ('', 'port', 'data', 'math', 'table', 'table/bitmap',
'table/core', 'table/truetype'):
path = os.path.join(src_dir, 'sfntly', *loc.split('/'))
gr = Group(loc, path, self.sfntly_build_dir, cflags+
inc_flags+self.sfntly_cflags+self.cflags)
groups.append(gr)
all_headers |= gr.headers
all_objects.extend(gr.objects)
for group in groups:
group(compiler, linker, builder, all_headers)
self.extra_objs = all_objects

View File

@ -329,6 +329,7 @@ def get_parsed_proxy(typ='http', debug=True):
ans['port'] = int(ans['port'])
except:
if debug:
import traceback
traceback.print_exc()
else:
if debug:
@ -689,29 +690,6 @@ def remove_bracketed_text(src,
buf.append(char)
return u''.join(buf)
if isosx:
import glob, shutil
fdir = os.path.expanduser('~/.fonts')
try:
if not os.path.exists(fdir):
os.makedirs(fdir)
if not os.path.exists(os.path.join(fdir, 'LiberationSans_Regular.ttf')):
base = P('fonts/liberation/*.ttf')
for f in glob.glob(base):
shutil.copy2(f, fdir)
except:
import traceback
traceback.print_exc()
def load_builtin_fonts():
import glob
from PyQt4.Qt import QFontDatabase
base = P('fonts/liberation/*.ttf')
for f in glob.glob(base):
QFontDatabase.addApplicationFont(f)
return 'Liberation Serif', 'Liberation Sans', 'Liberation Mono'
def ipython(user_ns=None):
from calibre.utils.ipython import ipython
ipython(user_ns=user_ns)

View File

@ -4,7 +4,7 @@ __license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal kovid@kovidgoyal.net'
__docformat__ = 'restructuredtext en'
__appname__ = u'calibre'
numeric_version = (0, 9, 3)
numeric_version = (0, 9, 5)
__version__ = u'.'.join(map(unicode, numeric_version))
__author__ = u"Kovid Goyal <kovid@kovidgoyal.net>"
@ -36,6 +36,7 @@ isunix = isosx or islinux
isportable = os.environ.get('CALIBRE_PORTABLE_BUILD', None) is not None
ispy3 = sys.version_info.major > 2
isxp = iswindows and sys.getwindowsversion().major < 6
isworker = os.environ.has_key('CALIBRE_WORKER') or os.environ.has_key('CALIBRE_SIMPLE_WORKER')
try:
preferred_encoding = locale.getpreferredencoding()
@ -83,12 +84,14 @@ class Plugins(collections.Mapping):
'magick',
'podofo',
'cPalmdoc',
'fontconfig',
'progress_indicator',
'chmlib',
'chm_extra',
'icu',
'speedup',
'freetype',
'woff',
'sfntly',
]
if iswindows:
plugins.extend(['winutil', 'wpd', 'winfonts'])

View File

@ -1394,6 +1394,16 @@ class StoreEKnigiStore(StoreBase):
formats = ['EPUB', 'PDF', 'HTML']
affiliate = True
class StoreEmpikStore(StoreBase):
name = 'Empik'
author = u'Tomasz Długosz'
description = u'Empik to marka o unikalnym dziedzictwie i legendarne miejsce, dawne “okno na świat”. Jest obecna w polskim krajobrazie kulturalnym od 60 lat (wcześniej jako Kluby Międzynarodowej Prasy i Książki).'
actual_plugin = 'calibre.gui2.store.stores.empik_plugin:EmpikStore'
headquarters = 'PL'
formats = ['EPUB', 'MOBI', 'PDF']
affiliate = True
class StoreEscapeMagazineStore(StoreBase):
name = 'EscapeMagazine'
author = u'Tomasz Długosz'
@ -1661,6 +1671,7 @@ plugins += [
StoreEbooksGratuitsStore,
StoreEHarlequinStore,
StoreEKnigiStore,
StoreEmpikStore,
StoreEscapeMagazineStore,
StoreFeedbooksStore,
StoreFoylesUKStore,

View File

@ -19,6 +19,8 @@ Run an embedded python interpreter.
''')
parser.add_option('-c', '--command', help='Run python code.', default=None)
parser.add_option('-e', '--exec-file', default=None, help='Run the python code in file.')
parser.add_option('-f', '--subset-font', default=False,
action='store_true', help='Subset the specified font')
parser.add_option('-d', '--debug-device-driver', default=False, action='store_true',
help='Debug the specified device driver.')
parser.add_option('-g', '--gui', default=False, action='store_true',
@ -209,6 +211,11 @@ def main(args=sys.argv):
execfile(ef, g)
return
if len(args) > 1 and args[1] in ('-f', '--subset-font'):
from calibre.utils.fonts.subset import main
main(['subset-font']+args[2:])
return
opts, args = option_parser().parse_args(args)
if opts.gui:
from calibre.gui2.main import main

View File

@ -212,7 +212,7 @@ class ANDROID(USBMS):
'VIZIO', 'GOOGLE', 'FREESCAL', 'KOBO_INC', 'LENOVO', 'ROCKCHIP',
'POCKET', 'ONDA_MID', 'ZENITHIN', 'INGENIC', 'PMID701C', 'PD',
'PMP5097C', 'MASS', 'NOVO7', 'ZEKI', 'COBY', 'SXZ', 'USB_2.0',
'COBY_MID', 'VS', 'AINOL', 'TOPWISE']
'COBY_MID', 'VS', 'AINOL', 'TOPWISE', 'PAD703']
WINDOWS_MAIN_MEM = ['ANDROID_PHONE', 'A855', 'A853', 'INC.NEXUS_ONE',
'__UMS_COMPOSITE', '_MB200', 'MASS_STORAGE', '_-_CARD', 'SGH-I897',
'GT-I9000', 'FILE-STOR_GADGET', 'SGH-T959_CARD', 'SGH-T959', 'SAMSUNG_ANDROID',

View File

@ -20,7 +20,7 @@ class Book(Book_):
def __init__(self, prefix, lpath, title=None, authors=None, mime=None, date=None, ContentType=None,
thumbnail_name=None, size=0, other=None):
# debug_print('Book::__init__ - title=', title)
show_debug = title is not None and title.lower().find("magic kingdom") >= 0
show_debug = title is not None and title.lower().find("xxxxx") >= 0
if show_debug:
debug_print("Book::__init__ - title=", title, 'authors=', authors)
debug_print("Book::__init__ - other=", other)
@ -32,7 +32,7 @@ class Book(Book_):
if authors is not None and len(authors) > 0:
self.authors_from_string(authors)
if self.author_sort is None or self.author_sort == "Unknown":
self.author_sort = author_to_author_sort(self.authors)
self.author_sort = author_to_author_sort(authors)
self.mime = mime
@ -58,7 +58,8 @@ class Book(Book_):
self.datetime = time.gmtime()
self.contentID = None
self.current_collections = []
self.current_shelves = []
self.kobo_collections = []
if thumbnail_name is not None:
self.thumbnail = ImageWrapper(thumbnail_name)
@ -99,6 +100,10 @@ class KTCollectionsBookList(CollectionsBookList):
lpath = getattr(book, 'lpath', None)
if lpath is None:
continue
# If the book is not in the current library, we don't want to use the metadtaa for the collections
if book.application_id is None:
# debug_print("KTCollectionsBookList:get_collections - Book not in current library")
continue
# Decide how we will build the collections. The default: leave the
# book in all existing collections. Do not add any new ones.
attrs = ['device_collections']
@ -115,7 +120,8 @@ class KTCollectionsBookList(CollectionsBookList):
elif prefs['manage_device_metadata'] == 'on_connect':
# For existing books, modify the collections only if the user
# specified 'on_connect'
attrs += collection_attributes
attrs = collection_attributes
book.device_collections = []
if show_debug:
debug_print("KTCollectionsBookList:get_collections - attrs=", attrs)

View File

@ -33,7 +33,7 @@ class KOBO(USBMS):
gui_name = 'Kobo Reader'
description = _('Communicate with the Kobo Reader')
author = 'Timothy Legge and David Forrester'
version = (2, 0, 2)
version = (2, 0, 3)
dbversion = 0
fwversion = 0
@ -653,6 +653,7 @@ class KOBO(USBMS):
@classmethod
def book_from_path(cls, prefix, lpath, title, authors, mime, date, ContentType, ImageID):
# debug_print("KOBO:book_from_path - title=%s"%title)
from calibre.ebooks.metadata import MetaInformation
if cls.settings().read_metadata or cls.MUST_READ_METADATA:
@ -850,7 +851,7 @@ class KOBO(USBMS):
return collections
def sync_booklists(self, booklists, end_session=True):
# debug_print('KOBO: started sync_booklists')
debug_print('KOBO: started sync_booklists')
paths = self.get_device_paths()
blists = {}
@ -872,7 +873,7 @@ class KOBO(USBMS):
self.update_device_database_collections(blist, collections, oncard)
USBMS.sync_booklists(self, booklists, end_session=end_session)
#debug_print('KOBO: finished sync_booklists')
debug_print('KOBO: finished sync_booklists')
def rebuild_collections(self, booklist, oncard):
collections_attributes = []
@ -1199,7 +1200,7 @@ class KOBOTOUCH(KOBO):
author = 'David Forrester'
description = 'Communicate with the Kobo Touch, Glo and Mini firmware. Based on the existing Kobo driver by %s.' % (KOBO.author)
supported_dbversion = 62
supported_dbversion = 70
min_supported_dbversion = 53
booklist_class = KTCollectionsBookList
@ -1285,13 +1286,21 @@ class KOBOTOUCH(KOBO):
# Image file name endings. Made up of: image size, min_dbversion, max_dbversion,
COVER_FILE_ENDINGS = {
' - N3_LIBRARY_FULL.parsed':[(355,530),0, 99,],
# ' - N3_LIBRARY_FULL.parsed':[(600,800),0, 99,],
' - N3_LIBRARY_GRID.parsed':[(149,233),0, 99,],
' - N3_LIBRARY_LIST.parsed':[(60,90),0, 99,],
' - N3_LIBRARY_SHELF.parsed': [(40,60),0, 52,],
' - N3_FULL.parsed':[(600,800),0, 52,],
' - N3_LIBRARY_FULL.parsed':[(355,473),0, 99,], # Used for Details screen
' - N3_LIBRARY_GRID.parsed':[(149,198),0, 99,], # Used for library lists
' - N3_LIBRARY_LIST.parsed':[(60,90),0, 53,],
# ' - N3_LIBRARY_SHELF.parsed': [(40,60),0, 52,],
' - N3_FULL.parsed':[(600,800),0, 99,], # Used for screensaver, home screen
}
#Following are the sizes used with pre2.1.4 firmware
# COVER_FILE_ENDINGS = {
# ' - N3_LIBRARY_FULL.parsed':[(355,530),0, 99,], # Used for Details screen
## ' - N3_LIBRARY_FULL.parsed':[(600,800),0, 99,],
# ' - N3_LIBRARY_GRID.parsed':[(149,233),0, 99,], # Used for library lists
# ' - N3_LIBRARY_LIST.parsed':[(60,90),0, 53,],
# ' - N3_LIBRARY_SHELF.parsed': [(40,60),0, 52,],
# ' - N3_FULL.parsed':[(600,800),0, 99,], # Used for screensaver if "Full screen" is checked.
# }
def initialize(self):
super(KOBOTOUCH, self).initialize()
@ -1400,10 +1409,15 @@ class KOBOTOUCH(KOBO):
debug_print("KoboTouch:update_booklist - have a deleted book")
# Label Previews
if accessibility == 6:
if isdownloaded == 'false':
playlist_map[lpath].append('Recommendation')
else:
playlist_map[lpath].append('Preview')
elif accessibility == 4:
playlist_map[lpath].append('Recommendation')
kobo_collections = playlist_map[lpath][:]
if len(bookshelves) > 0:
playlist_map[lpath].extend(bookshelves)
@ -1420,12 +1434,15 @@ class KOBOTOUCH(KOBO):
debug_print('KoboTouch:update_booklist - bl[idx].device_collections=', bl[idx].device_collections)
debug_print('KoboTouch:update_booklist - playlist_map=', playlist_map)
debug_print('KoboTouch:update_booklist - bookshelves=', bookshelves)
debug_print('KoboTouch:update_booklist - kobo_collections=', kobo_collections)
debug_print('KoboTouch:update_booklist - series="%s"' % bl[idx].series)
debug_print('KoboTouch:update_booklist - the book=', bl[idx])
debug_print('KoboTouch:update_booklist - the authors=', bl[idx].authors)
debug_print('KoboTouch:update_booklist - application_id=', bl[idx].application_id)
bl_cache[lpath] = None
if bl[idx].title_sort is not None:
bl[idx].title = bl[idx].title_sort
# removed to allow recognizing of ePub with an UUID inside
# if bl[idx].title_sort is not None:
# bl[idx].title = bl[idx].title_sort
if ImageID is not None:
imagename = self.imagefilename_from_imageID(ImageID)
if imagename is not None:
@ -1438,11 +1455,6 @@ class KOBOTOUCH(KOBO):
else:
debug_print(" Strange: The file: ", prefix, lpath, " does not exist!")
debug_print("KoboTouch:update_booklist - book size=", bl[idx].size)
# if lpath in playlist_map and \
# playlist_map[lpath] not in bl[idx].device_collections:
# bl[idx].device_collections = playlist_map.get(lpath,[])
# changed = True
if show_debug:
debug_print("KoboTouch:update_booklist - ContentID='%s'"%ContentID)
@ -1450,7 +1462,8 @@ class KOBOTOUCH(KOBO):
if lpath in playlist_map:
bl[idx].device_collections = playlist_map.get(lpath,[])
bl[idx].current_collections = bl[idx].device_collections
bl[idx].current_shelves = bookshelves
bl[idx].kobo_collections = kobo_collections
changed = True
if show_debug:
@ -1482,12 +1495,16 @@ class KOBOTOUCH(KOBO):
debug_print('KoboTouch:update_booklist - class:', book.__class__)
# debug_print(' resolution:', book.__class__.__mro__)
debug_print(" contentid:'%s'"%book.contentID)
debug_print(book)
debug_print(" title:'%s'"%book.title)
debug_print(" the book:", book)
debug_print(" author_sort:'%s'"%book.author_sort)
debug_print(" bookshelves:", bookshelves)
debug_print(" kobo_collections:", kobo_collections)
# print 'Update booklist'
book.device_collections = playlist_map.get(lpath,[])# if lpath in playlist_map else []
book.current_collections = bl[idx].device_collections
book.current_shelves = bookshelves
book.kobo_collections = kobo_collections
book.contentID = ContentID
# debug_print('KoboTouch:update_booklist - title=', title, 'book.device_collections', book.device_collections)
@ -1526,16 +1543,16 @@ class KOBOTOUCH(KOBO):
# return bytestrings if the content cannot the decoded as unicode
connection.text_factory = lambda x: unicode(x, "utf-8", "ignore")
self.bookshelvelist = self.get_bookshelflist(connection)
cursor = connection.cursor()
cursor.execute('select version from dbversion')
result = cursor.fetchone()
self.dbversion = result[0]
debug_print("Database Version=%d"%self.dbversion)
self.bookshelvelist = self.get_bookshelflist(connection)
debug_print("KoboTouch:books - shelf list:", self.bookshelvelist)
opts = self.settings()
if self.dbversion >= 33:
query= ('select Title, Attribution, DateCreated, ContentID, MimeType, ContentType, ' \
@ -1623,14 +1640,16 @@ class KOBOTOUCH(KOBO):
#print "count found in cache: %d, count of files in metadata: %d, need_sync: %s" % \
# (len(bl_cache), len(bl), need_sync)
if need_sync: #self.count_found_in_bl != len(bl) or need_sync:
# Bypassing the KOBO sync_booklists as that does things we don't need to do
# Also forcing sync to see if this solves issues with updating shelves and matching books.
if need_sync or True: #self.count_found_in_bl != len(bl) or need_sync:
debug_print("KoboTouch:books - about to sync_booklists")
if oncard == 'cardb':
self.sync_booklists((None, None, bl))
USBMS.sync_booklists(self, (None, None, bl))
elif oncard == 'carda':
self.sync_booklists((None, bl, None))
USBMS.sync_booklists(self, (None, bl, None))
else:
self.sync_booklists((bl, None, None))
USBMS.sync_booklists(self, (bl, None, None))
self.report_progress(1.0, _('Getting list of books on device...'))
debug_print("KoboTouch:books - end - oncard='%s'"%oncard)
@ -1941,7 +1960,6 @@ class KOBOTOUCH(KOBO):
debug_print("Booklists=", booklists)
if self.dbversion < 53:
self.reset_readstatus(connection, oncard)
self.remove_from_bookshelves(connection, oncard)
if self.dbversion >= 14:
debug_print("No Collections - reseting FavouritesIndex")
self.reset_favouritesindex(connection, oncard)
@ -1954,6 +1972,7 @@ class KOBOTOUCH(KOBO):
if book.application_id is not None:
# debug_print("KoboTouch:update_device_database_collections - about to remove a book from shelves book.title=%s" % book.title)
self.remove_book_from_device_bookshelves(connection, book)
book.device_collections.extend(book.kobo_collections)
if not prefs['manage_device_metadata'] == 'manual' and delete_empty_shelves:
debug_print("KoboTouch:update_device_database_collections - about to clear empty bookshelves")
self.delete_empty_bookshelves(connection)
@ -2089,12 +2108,13 @@ class KOBOTOUCH(KOBO):
def remove_book_from_device_bookshelves(self, connection, book):
show_debug = self.is_debugging_title(book.title)# or True
remove_shelf_list = set(book.current_collections) - set(book.device_collections) - set(["Im_Reading", "Read", "Closed"])
remove_shelf_list = set(book.current_shelves) - set(book.device_collections)
if show_debug:
debug_print('KoboTouch:remove_book_from_device_bookshelves - book.application_id="%s"'%book.application_id)
debug_print('KoboTouch:remove_book_from_device_bookshelves - book.contentID="%s"'%book.contentID)
debug_print('KoboTouch:remove_book_from_device_bookshelves - book.device_collections=', book.device_collections)
debug_print('KoboTouch:remove_book_from_device_bookshelves - book.current_shelves=', book.current_shelves)
debug_print('KoboTouch:remove_book_from_device_bookshelves - remove_shelf_list=', remove_shelf_list)
if len(remove_shelf_list) == 0:
@ -2155,7 +2175,7 @@ class KOBOTOUCH(KOBO):
if not self.supports_bookshelves():
return bookshelves
query = 'SELECT Name FROM Shelf'
query = 'SELECT Name FROM Shelf WHERE _IsDeleted = "false"'
cursor = connection.cursor()
cursor.execute(query)
@ -2204,11 +2224,8 @@ class KOBOTOUCH(KOBO):
debug_print('KoboTouch:check_for_bookshelf bookshelf_name="%s"'%bookshelf_name)
test_query = 'SELECT InternalName, Name, _IsDeleted FROM Shelf WHERE Name = ?'
test_values = (bookshelf_name, )
addquery = 'INSERT INTO "main"."Shelf"'\
' ("CreationDate","InternalName","LastModified","Name","_IsDeleted","_IsVisible","_IsSynced")'\
' VALUES (?, ?, ?, ?, ?, ?, ?)'
add_values = (
time.strftime(self.TIMESTAMP_STRING, time.gmtime()),
addquery = 'INSERT INTO "main"."Shelf"'
add_values = (time.strftime(self.TIMESTAMP_STRING, time.gmtime()),
bookshelf_name,
time.strftime(self.TIMESTAMP_STRING, time.gmtime()),
bookshelf_name,
@ -2216,6 +2233,17 @@ class KOBOTOUCH(KOBO):
"true",
"false",
)
if self.dbversion < 64:
addquery += ' ("CreationDate","InternalName","LastModified","Name","_IsDeleted","_IsVisible","_IsSynced")'\
' VALUES (?, ?, ?, ?, ?, ?, ?)'
else:
addquery += ' ("CreationDate", "InternalName","LastModified","Name","_IsDeleted","_IsVisible","_IsSynced", "Id")'\
' VALUES (?, ?, ?, ?, ?, ?, ?, ?)'
add_values = add_values +(bookshelf_name,)
if show_debug:
debug_print('KoboTouch:check_for_bookshelf addquery=', addquery)
debug_print('KoboTouch:check_for_bookshelf add_values=', add_values)
updatequery = 'UPDATE Shelf SET _IsDeleted = "false" WHERE Name = ?'
cursor = connection.cursor()
@ -2232,6 +2260,7 @@ class KOBOTOUCH(KOBO):
connection.commit()
cursor.close()
# Update the bookshelf list.
self.bookshelvelist = self.get_bookshelflist(connection)
# debug_print("KoboTouch:set_bookshelf - end")

View File

@ -114,7 +114,7 @@ class MTP_DEVICE(BASE):
except:
prints('Failed to load existing driveinfo.calibre file, with error:')
traceback.print_exc()
dinfo = None
dinfo = {}
if dinfo.get('device_store_uuid', None) is None:
dinfo['device_store_uuid'] = unicode(uuid.uuid4())
if dinfo.get('device_name', None) is None:

View File

@ -178,18 +178,41 @@ def normalize(x):
def calibre_cover(title, author_string, series_string=None,
output_format='jpg', title_size=46, author_size=36, logo_path=None):
from calibre.utils.config_base import tweaks
title = normalize(title)
author_string = normalize(author_string)
series_string = normalize(series_string)
from calibre.utils.magick.draw import create_cover_page, TextLine
lines = [TextLine(title, title_size), TextLine(author_string, author_size)]
text = title + author_string + (series_string or u'')
font_path = tweaks['generate_cover_title_font']
if font_path is None:
font_path = P('fonts/liberation/LiberationSerif-Bold.ttf')
from calibre.utils.fonts.utils import get_font_for_text
font = open(font_path, 'rb').read()
c = get_font_for_text(text, font)
cleanup = False
if c is not None and c != font:
from calibre.ptempfile import PersistentTemporaryFile
pt = PersistentTemporaryFile('.ttf')
pt.write(c)
pt.close()
font_path = pt.name
cleanup = True
lines = [TextLine(title, title_size, font_path=font_path),
TextLine(author_string, author_size, font_path=font_path)]
if series_string:
lines.append(TextLine(series_string, author_size))
lines.append(TextLine(series_string, author_size, font_path=font_path))
if logo_path is None:
logo_path = I('library.png')
try:
return create_cover_page(lines, logo_path, output_format='jpg',
texture_opacity=0.3, texture_data=I('cover_texture.png',
data=True))
finally:
if cleanup:
os.remove(font_path)
UNIT_RE = re.compile(r'^(-*[0-9]*[.]?[0-9]*)\s*(%|em|ex|en|px|mm|cm|in|pt|pc)$')
@ -231,7 +254,6 @@ def unit_convert(value, base, font, dpi):
def generate_masthead(title, output_path=None, width=600, height=60):
from calibre.ebooks.conversion.config import load_defaults
from calibre.utils.fonts import fontconfig
from calibre.utils.config import tweaks
fp = tweaks['generate_cover_title_font']
if not fp:
@ -241,11 +263,10 @@ def generate_masthead(title, output_path=None, width=600, height=60):
masthead_font_family = recs.get('masthead_font', 'Default')
if masthead_font_family != 'Default':
masthead_font = fontconfig.files_for_family(masthead_font_family)
# Assume 'normal' always in dict, else use default
# {'normal': (path_to_font, friendly name)}
if 'normal' in masthead_font:
font_path = masthead_font['normal'][0]
from calibre.utils.fonts.scanner import font_scanner
faces = font_scanner.fonts_for_family(masthead_font_family)
if faces:
font_path = faces[0]['path']
if not font_path or not os.access(font_path, os.R_OK):
font_path = default_font

View File

@ -132,7 +132,7 @@ def add_pipeline_options(parser, plumber):
_('Options to control the look and feel of the output'),
[
'base_font_size', 'disable_font_rescaling',
'font_size_mapping',
'font_size_mapping', 'embed_font_family',
'line_height', 'minimum_line_height',
'linearize_tables',
'extra_css', 'filter_css',

View File

@ -193,6 +193,17 @@ OptionRecommendation(name='line_height',
)
),
OptionRecommendation(name='embed_font_family',
recommended_value=None, level=OptionRecommendation.LOW,
help=_(
'Embed the specified font family into the book. This specifies '
'the "base" font used for the book. If the input document '
'specifies its own fonts, they may override this base font. '
'You can use the filter style information option to remove fonts from the '
'input document. Note that font embedding only works '
'with some output formats, principally EPUB and AZW3.')
),
OptionRecommendation(name='linearize_tables',
recommended_value=False, level=OptionRecommendation.LOW,
help=_('Some badly designed documents use tables to control the '

View File

@ -34,24 +34,24 @@ class PRS500_PROFILE(object):
name = 'prs500'
def find_custom_fonts(options, logger):
from calibre.utils.fonts import fontconfig
files_for_family = fontconfig.files_for_family
from calibre.utils.fonts.scanner import font_scanner
fonts = {'serif' : None, 'sans' : None, 'mono' : None}
def family(cmd):
return cmd.split(',')[-1].strip()
if options.serif_family:
f = family(options.serif_family)
fonts['serif'] = files_for_family(f)
fonts['serif'] = font_scanner.legacy_fonts_for_family(f)
print (111111, fonts['serif'])
if not fonts['serif']:
logger.warn('Unable to find serif family %s'%f)
if options.sans_family:
f = family(options.sans_family)
fonts['sans'] = files_for_family(f)
fonts['sans'] = font_scanner.legacy_fonts_for_family(f)
if not fonts['sans']:
logger.warn('Unable to find sans family %s'%f)
if options.mono_family:
f = family(options.mono_family)
fonts['mono'] = files_for_family(f)
fonts['mono'] = font_scanner.legacy_fonts_for_family(f)
if not fonts['mono']:
logger.warn('Unable to find mono family %s'%f)
return fonts

View File

@ -379,6 +379,10 @@ def set_metadata(stream, mi, apply_null=False, update_timestamp=False):
stream.seek(0)
stream.truncate()
# Apparently there exists FB2 reading software that chokes on the use of
# single quotes in xml declaration. Sigh. See
# http://www.mobileread.com/forums/showthread.php?p=2273184#post2273184
stream.write(b'<?xml version="1.0" encoding="UTF-8"?>\n')
stream.write(etree.tostring(root, method='xml', encoding='utf-8',
xml_declaration=True))
xml_declaration=False))

View File

@ -23,6 +23,7 @@ TEMPLATE = '''
a {{ text-decoration: none }}
a:hover {{ color: red }}
{extra_css}
{embed_css}
</style>
</head>
<body id="calibre_generated_inline_toc">
@ -64,8 +65,16 @@ class TOCAdder(object):
self.log('\tGenerating in-line ToC')
embed_css = ''
s = getattr(oeb, 'store_embed_font_rules', None)
if getattr(s, 'body_font_family', None):
css = [x.cssText for x in s.rules] + [
'body { font-family: %s }'%s.body_font_family]
embed_css = '\n\n'.join(css)
root = etree.fromstring(TEMPLATE.format(xhtmlns=XHTML_NS,
title=self.title, extra_css=(opts.extra_css or '')))
title=self.title, embed_css=embed_css,
extra_css=(opts.extra_css or '')))
parent = XPath('//h:ul')(root)[0]
parent.text = '\n\t'
for child in self.oeb.toc:

View File

@ -258,7 +258,7 @@ OPF_MIME = types_map['.opf']
PAGE_MAP_MIME = 'application/oebps-page-map+xml'
OEB_DOC_MIME = 'text/x-oeb1-document'
OEB_CSS_MIME = 'text/x-oeb1-css'
OPENTYPE_MIME = 'application/x-font-opentype'
OPENTYPE_MIME = types_map['.otf']
GIF_MIME = types_map['.gif']
JPEG_MIME = types_map['.jpeg']
PNG_MIME = types_map['.png']

View File

@ -22,7 +22,6 @@ from calibre.utils.logging import default_log
from calibre import (guess_type, prepare_string_for_xml,
xml_replace_entities)
from calibre.ebooks.oeb.transforms.cover import CoverManager
from calibre.ebooks.oeb.iterator.spine import (SpineItem, create_indexing_data)
from calibre.ebooks.oeb.iterator.bookmarks import BookmarksMixin
@ -76,7 +75,8 @@ class EbookIterator(BookmarksMixin):
return i
def __enter__(self, processed=False, only_input_plugin=False,
run_char_count=True, read_anchor_map=True):
run_char_count=True, read_anchor_map=True,
extract_embedded_fonts_for_qt=False):
''' Convert an ebook file into an exploded OEB book suitable for
display in viewers/preprocessing etc. '''
@ -174,6 +174,16 @@ class EbookIterator(BookmarksMixin):
self.read_bookmarks()
if extract_embedded_fonts_for_qt:
from calibre.ebooks.oeb.iterator.extract_fonts import extract_fonts
try:
extract_fonts(self.opf, self.log)
except:
ol = self.log.filter_level
self.log.filter_level = self.log.DEBUG
self.log.exception('Failed to extract fonts')
self.log.filter_level = ol
return self
def __exit__(self, *args):

View File

@ -0,0 +1,110 @@
#!/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 re, os, logging
from functools import partial
from future_builtins import map
class FamilyMap(dict):
def __init__(self, log):
dict.__init__(self)
self.replace_map = {}
self.added_fonts = set()
self.log = log
def __call__(self, basedir, match):
self.read_font_fule(basedir, match.group())
return b''
def finalize(self):
if self.replace_map:
self.pat = re.compile(br'(font-family.*?)(' +
b'|'.join([re.escape(x) for x in
self.replace_map.iterkeys()])+b')', re.I)
def replace_font_families(self, raw):
if self.replace_map:
def sub(m):
k = m.group(2).lower()
for q, val in self.replace_map.iteritems():
if q.lower() == k.lower():
return m.group().replace(m.group(2), val)
return m.group()
return self.pat.sub(sub, raw)
def read_font_fule(self, basedir, css):
from PyQt4.Qt import QFontDatabase
import cssutils
cssutils.log.setLevel(logging.ERROR)
try:
sheet = cssutils.parseString(css, validate=False)
except:
return
for rule in sheet.cssRules:
try:
s = rule.style
src = s.getProperty('src').propertyValue[0].uri
font_family = s.getProperty('font-family').propertyValue[0].value
except:
continue
if not src or not font_family:
continue
font_file = os.path.normcase(os.path.abspath(os.path.join(basedir,
src)))
if font_file not in self.added_fonts:
self.added_fonts.add(font_file)
if os.path.exists(font_file):
with open(font_file, 'rb') as f:
idx = QFontDatabase.addApplicationFontFromData(f.read())
if idx > -1:
family = map(unicode,
QFontDatabase.applicationFontFamilies(idx)).next()
self.log('Extracted embedded font:', family, 'from',
os.path.basename(font_file))
if (family and family != font_family and
family not in self.replace_map):
self.log('Replacing font family value:',
font_family, 'with', family)
self.replace_map[font_family.encode('utf-8')] = \
family.encode('utf-8')
def extract_fonts(opf, log):
'''
Extract embedded fonts from the ebook and add them explicitly to the Qt
font database to workaround https://bugs.webkit.org/show_bug.cgi?id=29433
Only works if the font-face and font-family rules are all contained in the
CSS files (Also processing the HTML files would be too much of a
performance hit, to do robustly).
'''
css_files = {}
font_family_map = FamilyMap(log)
pat = re.compile(br'^\s*@font-face\s*{[^}]+}', re.M)
for item in opf.manifest:
if item.mime_type and item.mime_type.lower() in {
'text/css', 'text/x-oeb1-css', 'text/x-oeb-css'}:
try:
with open(item.path, 'rb') as f:
raw = f.read()
except EnvironmentError:
continue
css_files[item.path] = pat.sub(partial(font_family_map,
os.path.dirname(item.path)), raw)
font_family_map.finalize()
if font_family_map.added_fonts:
for path, raw in css_files.iteritems():
with open(path, 'wb') as f:
nraw = font_family_map.replace_font_families(raw) or raw
f.write(nraw)

View File

@ -126,6 +126,17 @@ class CaseInsensitiveAttributesTranslator(HTMLTranslator):
ci_css_to_xpath = CaseInsensitiveAttributesTranslator().css_to_xpath
NULL_NAMESPACE_REGEX = re.compile(ur'''(name\(\) = ['"])h:''')
def fix_namespace(raw):
'''
cssselect uses name() = 'h:p' to select tags for some CSS selectors (e.g.
h|p+h|p).
However, since for us the XHTML namespace is the default namespace (with no
prefix), name() is the same as local-name(). So this is a hack to
workaround the problem.
'''
return NULL_NAMESPACE_REGEX.sub(ur'\1', raw)
class CSSSelector(object):
def __init__(self, css, log=None, namespaces=XPNSMAP):
@ -136,7 +147,7 @@ class CSSSelector(object):
def build_selector(self, css, log, func=css_to_xpath):
try:
return etree.XPath(func(css), namespaces=self.namespaces)
return etree.XPath(fix_namespace(func(css)), namespaces=self.namespaces)
except:
if log is not None:
log.exception('Failed to parse CSS selector: %r'%css)

View File

@ -14,9 +14,11 @@ from lxml import etree
import cssutils
from cssutils.css import Property
from calibre import guess_type
from calibre.ebooks.oeb.base import (XHTML, XHTML_NS, CSS_MIME, OEB_STYLES,
namespace, barename, XPath)
from calibre.ebooks.oeb.stylizer import Stylizer
from calibre.utils.filenames import ascii_filename
COLLAPSE = re.compile(r'[ \t\r\n\v]+')
STRIPNUM = re.compile(r'[-0-9]+$')
@ -101,6 +103,22 @@ def FontMapper(sbase=None, dbase=None, dkey=None):
else:
return NullMapper()
class EmbedFontsCSSRules(object):
def __init__(self, body_font_family, rules):
self.body_font_family, self.rules = body_font_family, rules
self.href = None
def __call__(self, oeb):
if not self.body_font_family: return None
if not self.href:
iid, href = oeb.manifest.generate(u'page_styles', u'page_styles.css')
rules = [x.cssText for x in self.rules]
rules = u'\n\n'.join(rules)
sheet = cssutils.parseString(rules, validate=False)
self.href = oeb.manifest.add(iid, href, guess_type(href)[0],
data=sheet).href
return self.href
class CSSFlattener(object):
def __init__(self, fbase=None, fkey=None, lineh=None, unfloat=False,
@ -144,11 +162,61 @@ class CSSFlattener(object):
cssutils.replaceUrls(item.data, item.abshref,
ignoreImportRules=True)
self.body_font_family, self.embed_font_rules = self.get_embed_font_info(
self.opts.embed_font_family)
# Store for use in output plugins/transforms that generate content,
# like the AZW3 output inline ToC.
self.oeb.store_embed_font_rules = EmbedFontsCSSRules(self.body_font_family,
self.embed_font_rules)
self.stylize_spine()
self.sbase = self.baseline_spine() if self.fbase else None
self.fmap = FontMapper(self.sbase, self.fbase, self.fkey)
self.flatten_spine()
def get_embed_font_info(self, family, failure_critical=True):
efi = []
body_font_family = None
if not family:
return body_font_family, efi
from calibre.utils.fonts.scanner import font_scanner
from calibre.utils.fonts.utils import panose_to_css_generic_family
faces = font_scanner.fonts_for_family(family)
if not faces:
msg = (u'No embeddable fonts found for family: %r'%self.opts.embed_font_family)
if failure_critical:
raise ValueError(msg)
self.oeb.log.warn(msg)
return body_font_family, efi
for i, font in enumerate(faces):
ext = 'otf' if font['is_otf'] else 'ttf'
fid, href = self.oeb.manifest.generate(id=u'font',
href=u'%s.%s'%(ascii_filename(font['full_name']).replace(u' ', u'-'), ext))
item = self.oeb.manifest.add(fid, href,
guess_type('dummy.'+ext)[0],
data=font_scanner.get_font_data(font))
item.unload_data_from_memory()
cfont = {
u'font-family':u'"%s"'%font['font-family'],
u'panose-1': u' '.join(map(unicode, font['panose'])),
u'src': u'url(%s)'%item.href,
}
if i == 0:
generic_family = panose_to_css_generic_family(font['panose'])
body_font_family = u"'%s',%s"%(font['font-family'], generic_family)
self.oeb.log(u'Embedding font: %s'%font['font-family'])
for k in (u'font-weight', u'font-style', u'font-stretch'):
if font[k] != u'normal':
cfont[k] = font[k]
rule = '@font-face { %s }'%('; '.join(u'%s:%s'%(k, v) for k, v in
cfont.iteritems()))
rule = cssutils.parseString(rule)
efi.append(rule)
return body_font_family, efi
def stylize_spine(self):
self.stylizers = {}
profile = self.context.source
@ -170,6 +238,8 @@ class CSSFlattener(object):
bs.extend(['page-break-before: always'])
if self.context.change_justification != 'original':
bs.append('text-align: '+ self.context.change_justification)
if self.body_font_family:
bs.append(u'font-family: '+self.body_font_family)
body.set('style', '; '.join(bs))
stylizer = Stylizer(html, item.href, self.oeb, self.context, profile,
user_css=self.context.extra_css,
@ -450,7 +520,8 @@ class CSSFlattener(object):
items.sort()
css = ';\n'.join("%s: %s" % (key, val) for key, val in items)
css = ('@page {\n%s\n}\n'%css) if items else ''
rules = [r.cssText for r in stylizer.font_face_rules]
rules = [r.cssText for r in stylizer.font_face_rules +
self.embed_font_rules]
raw = '\n\n'.join(rules)
css += '\n\n' + raw
global_css[css].append(item)

View File

@ -73,6 +73,7 @@ class Split(object):
def find_page_breaks(self, item):
if self.page_break_selectors is None:
from calibre.ebooks.oeb.stylizer import fix_namespace
css_to_xpath = HTMLTranslator().css_to_xpath
self.page_break_selectors = set([])
stylesheets = [x.data for x in self.oeb.manifest if x.media_type in
@ -84,7 +85,7 @@ class Split(object):
'page-break-after'), 'cssText', '').strip().lower()
try:
if before and before not in {'avoid', 'auto', 'inherit'}:
self.page_break_selectors.add((XPath(css_to_xpath(rule.selectorText)),
self.page_break_selectors.add((XPath(fix_namespace(css_to_xpath(rule.selectorText))),
True))
if self.remove_css_pagebreaks:
rule.style.removeProperty('page-break-before')
@ -92,7 +93,7 @@ class Split(object):
pass
try:
if after and after not in {'avoid', 'auto', 'inherit'}:
self.page_break_selectors.add((XPath(css_to_xpath(rule.selectorText)),
self.page_break_selectors.add((XPath(fix_namespace(css_to_xpath(rule.selectorText))),
False))
if self.remove_css_pagebreaks:
rule.style.removeProperty('page-break-after')

View File

@ -1,14 +1,15 @@
__license__ = 'GPL v3'
__copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
""" The GUI """
import os, sys, Queue, threading
import os, sys, Queue, threading, glob
from threading import RLock
from urllib import unquote
from PyQt4.Qt import (QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt,
QByteArray, QTranslator, QCoreApplication, QThread,
QEvent, QTimer, pyqtSignal, QDateTime, QDesktopServices,
QFileDialog, QFileIconProvider, QSettings, QColor,
QIcon, QApplication, QDialog, QUrl, QFont, QPalette)
QIcon, QApplication, QDialog, QUrl, QFont, QPalette,
QFontDatabase)
ORG_NAME = 'KovidsBrain'
APP_UID = 'libprs500'
@ -791,6 +792,29 @@ class Application(QApplication):
self.redirect_notify = True
return ret
def load_builtin_fonts(self, scan_for_fonts=False):
global _rating_font
if scan_for_fonts:
from calibre.utils.fonts.scanner import font_scanner
# Start scanning the users computer for fonts
font_scanner
# Load the builtin fonts and any fonts added to calibre by the user to
# Qt
for ff in glob.glob(P('fonts/liberation/*.?tf')) + \
[P('fonts/calibreSymbols.otf')] + \
glob.glob(os.path.join(config_dir, 'fonts', '*.?tf')):
if ff.rpartition('.')[-1].lower() in {'ttf', 'otf'}:
with open(ff, 'rb') as s:
# Windows requires font files to be executable for them to be
# loaded successfully, so we use the in memory loader
fid = QFontDatabase.addApplicationFontFromData(s.read())
if fid > -1:
fam = QFontDatabase.applicationFontFamilies(fid)
fam = set(map(unicode, fam))
if u'calibre Symbols' in fam:
_rating_font = u'calibre Symbols'
def load_calibre_style(self):
# On OS X QtCurve resets the palette, so we preserve it explicitly
orig_pal = QPalette(self.palette())
@ -963,22 +987,9 @@ def is_gui_thread():
global gui_thread
return gui_thread is QThread.currentThread()
_rating_font = None
_rating_font = 'Arial Unicode MS' if iswindows else 'sans-serif'
def rating_font():
global _rating_font
if _rating_font is None:
from PyQt4.Qt import QFontDatabase
_rating_font = 'Arial Unicode MS' if iswindows else 'sans-serif'
fontid = QFontDatabase.addApplicationFont(
#P('fonts/liberation/LiberationSerif-Regular.ttf')
P('fonts/calibreSymbols.otf')
)
if fontid > -1:
try:
_rating_font = unicode(list(
QFontDatabase.applicationFontFamilies(fontid))[0])
except:
pass
return _rating_font
def find_forms(srcdir):

View File

@ -120,17 +120,19 @@ class ShareConnMenu(QMenu): # {{{
for account in keys:
formats, auto, default = opts.accounts[account]
subject = opts.subjects.get(account, '')
alias = opts.aliases.get(account, '')
dest = 'mail:'+account+';'+formats+';'+subject
action1 = DeviceAction(dest, False, False, I('mail.png'),
account)
alias or account)
action2 = DeviceAction(dest, True, False, I('mail.png'),
account + ' ' + _('(delete from library)'))
(alias or account) + ' ' + _('(delete from library)'))
self.email_to_menu.addAction(action1)
self.email_to_and_delete_menu.addAction(action2)
map(self.memory.append, (action1, action2))
if default:
ac = DeviceAction(dest, False, False,
I('mail.png'), _('Email to') + ' ' +account)
I('mail.png'), _('Email to') + ' ' +(alias or
account))
self.addAction(ac)
self.email_actions.append(ac)
ac.a_s.connect(sync_menu.action_triggered)

View File

@ -191,6 +191,8 @@ class Widget(QWidget):
elif isinstance(g, (XPathEdit, RegexEdit)):
g.edit.editTextChanged.connect(f)
g.edit.currentIndexChanged.connect(f)
elif isinstance(g, FontFamilyChooser):
g.family_changed.connect(f)
else:
raise Exception('Can\'t connect %s'%type(g))

View File

@ -32,6 +32,7 @@ class LookAndFeelWidget(Widget, Ui_Form):
Widget.__init__(self, parent,
['change_justification', 'extra_css', 'base_font_size',
'font_size_mapping', 'line_height', 'minimum_line_height',
'embed_font_family',
'smarten_punctuation', 'unsmarten_punctuation',
'disable_font_rescaling', 'insert_blank_line',
'remove_paragraph_spacing',

View File

@ -7,27 +7,53 @@
<x>0</x>
<y>0</y>
<width>655</width>
<height>522</height>
<height>619</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QCheckBox" name="opt_disable_font_rescaling">
<property name="text">
<string>&amp;Disable font size rescaling</string>
<item row="3" column="4">
<widget class="QDoubleSpinBox" name="opt_line_height">
<property name="suffix">
<string> pt</string>
</property>
<property name="decimals">
<number>1</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_18">
<item row="3" column="3">
<widget class="QLabel" name="label">
<property name="text">
<string>Base &amp;font size:</string>
<string>Line &amp;height:</string>
</property>
<property name="buddy">
<cstring>opt_base_font_size</cstring>
<cstring>opt_line_height</cstring>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Minimum &amp;line height:</string>
</property>
<property name="buddy">
<cstring>opt_minimum_line_height</cstring>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QDoubleSpinBox" name="opt_minimum_line_height">
<property name="suffix">
<string> %</string>
</property>
<property name="decimals">
<number>1</number>
</property>
<property name="maximum">
<double>900.000000000000000</double>
</property>
</widget>
</item>
@ -97,49 +123,6 @@
</item>
</layout>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_6">
<property name="text">
<string>Minimum &amp;line height:</string>
</property>
<property name="buddy">
<cstring>opt_minimum_line_height</cstring>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QDoubleSpinBox" name="opt_minimum_line_height">
<property name="suffix">
<string> %</string>
</property>
<property name="decimals">
<number>1</number>
</property>
<property name="maximum">
<double>900.000000000000000</double>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Line &amp;height:</string>
</property>
<property name="buddy">
<cstring>opt_line_height</cstring>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QDoubleSpinBox" name="opt_line_height">
<property name="suffix">
<string> pt</string>
</property>
<property name="decimals">
<number>1</number>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
@ -157,14 +140,14 @@
</property>
</widget>
</item>
<item row="6" column="0" colspan="2">
<item row="7" column="0" colspan="2">
<widget class="QCheckBox" name="opt_remove_paragraph_spacing">
<property name="text">
<string>Remove &amp;spacing between paragraphs</string>
</property>
</widget>
</item>
<item row="6" column="3">
<item row="7" column="3">
<widget class="QLabel" name="label_4">
<property name="text">
<string>&amp;Indent size:</string>
@ -177,7 +160,7 @@
</property>
</widget>
</item>
<item row="6" column="4">
<item row="7" column="4">
<widget class="QDoubleSpinBox" name="opt_remove_paragraph_spacing_indent_size">
<property name="toolTip">
<string>&lt;p&gt;When calibre removes inter paragraph spacing, it automatically sets a paragraph indent, to ensure that paragraphs can be easily distinguished. This option controls the width of that indent.</string>
@ -199,85 +182,7 @@
</property>
</widget>
</item>
<item row="7" column="0" colspan="2">
<widget class="QCheckBox" name="opt_insert_blank_line">
<property name="text">
<string>Insert &amp;blank line between paragraphs</string>
</property>
</widget>
</item>
<item row="7" column="3">
<widget class="QLabel" name="label_7">
<property name="text">
<string>&amp;Line size:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>opt_insert_blank_line_size</cstring>
</property>
</widget>
</item>
<item row="7" column="4">
<widget class="QDoubleSpinBox" name="opt_insert_blank_line_size">
<property name="suffix">
<string> em</string>
</property>
<property name="decimals">
<number>1</number>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Text &amp;justification:</string>
</property>
<property name="buddy">
<cstring>opt_change_justification</cstring>
</property>
</widget>
</item>
<item row="8" column="2" colspan="3">
<widget class="QComboBox" name="opt_change_justification"/>
</item>
<item row="9" column="0">
<widget class="QCheckBox" name="opt_smarten_punctuation">
<property name="text">
<string>Smarten &amp;punctuation</string>
</property>
</widget>
</item>
<item row="9" column="1" colspan="4">
<widget class="QCheckBox" name="opt_asciiize">
<property name="text">
<string>&amp;Transliterate unicode characters to ASCII</string>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QCheckBox" name="opt_unsmarten_punctuation">
<property name="text">
<string>&amp;UnSmarten punctuation</string>
</property>
</widget>
</item>
<item row="10" column="1" colspan="2">
<widget class="QCheckBox" name="opt_keep_ligatures">
<property name="text">
<string>Keep &amp;ligatures</string>
</property>
</widget>
</item>
<item row="10" column="3">
<widget class="QCheckBox" name="opt_linearize_tables">
<property name="text">
<string>&amp;Linearize tables</string>
</property>
</widget>
</item>
<item row="11" column="0" colspan="5">
<item row="12" column="0" colspan="5">
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
@ -378,10 +283,131 @@
</item>
</layout>
</item>
<item row="3" column="0">
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
</layout>
</widget>
</widget>
</item>
<item row="8" column="0" colspan="2">
<widget class="QCheckBox" name="opt_insert_blank_line">
<property name="text">
<string>Insert &amp;blank line between paragraphs</string>
</property>
</widget>
</item>
<item row="8" column="4">
<widget class="QDoubleSpinBox" name="opt_insert_blank_line_size">
<property name="suffix">
<string> em</string>
</property>
<property name="decimals">
<number>1</number>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Text &amp;justification:</string>
</property>
<property name="buddy">
<cstring>opt_change_justification</cstring>
</property>
</widget>
</item>
<item row="9" column="2" colspan="3">
<widget class="QComboBox" name="opt_change_justification"/>
</item>
<item row="10" column="0">
<widget class="QCheckBox" name="opt_smarten_punctuation">
<property name="text">
<string>Smarten &amp;punctuation</string>
</property>
</widget>
</item>
<item row="10" column="1" colspan="4">
<widget class="QCheckBox" name="opt_asciiize">
<property name="text">
<string>&amp;Transliterate unicode characters to ASCII</string>
</property>
</widget>
</item>
<item row="11" column="0">
<widget class="QCheckBox" name="opt_unsmarten_punctuation">
<property name="text">
<string>&amp;UnSmarten punctuation</string>
</property>
</widget>
</item>
<item row="11" column="1" colspan="2">
<widget class="QCheckBox" name="opt_keep_ligatures">
<property name="text">
<string>Keep &amp;ligatures</string>
</property>
</widget>
</item>
<item row="11" column="3">
<widget class="QCheckBox" name="opt_linearize_tables">
<property name="text">
<string>&amp;Linearize tables</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_18">
<property name="text">
<string>Base &amp;font size:</string>
</property>
<property name="buddy">
<cstring>opt_base_font_size</cstring>
</property>
</widget>
</item>
<item row="8" column="3">
<widget class="QLabel" name="label_7">
<property name="text">
<string>&amp;Line size:</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>opt_insert_blank_line_size</cstring>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>&amp;Embed font family:</string>
</property>
<property name="buddy">
<cstring>opt_embed_font_family</cstring>
</property>
</widget>
</item>
<item row="0" column="0" colspan="5">
<widget class="QCheckBox" name="opt_disable_font_rescaling">
<property name="text">
<string>&amp;Disable font size rescaling</string>
</property>
</widget>
</item>
<item row="6" column="1" colspan="2">
<widget class="FontFamilyChooser" name="opt_embed_font_family"/>
</item>
</layout>
</widget>
<customwidgets>
@ -390,6 +416,11 @@
<extends>QComboBox</extends>
<header>widgets.h</header>
</customwidget>
<customwidget>
<class>FontFamilyChooser</class>
<extends>QWidget</extends>
<header>calibre/gui2/font_family_chooser.h</header>
</customwidget>
</customwidgets>
<resources>
<include location="../../../../resources/images.qrc"/>

View File

@ -205,7 +205,7 @@
<customwidgets>
<customwidget>
<class>FontFamilyChooser</class>
<extends>QComboBox</extends>
<extends>QWidget</extends>
<header>calibre/gui2/font_family_chooser.h</header>
</customwidget>
</customwidgets>

View File

@ -29,38 +29,8 @@ class PluginWidget(Widget, Ui_Form):
)
self.db, self.book_id = db, book_id
'''
from calibre.utils.fonts import fontconfig
global font_family_model
if font_family_model is None:
font_family_model = FontFamilyModel()
try:
font_family_model.families = fontconfig.find_font_families(allowed_extensions=['ttf'])
except:
import traceback
font_family_model.families = []
print 'WARNING: Could not load fonts'
traceback.print_exc()
font_family_model.families.sort()
font_family_model.families[:0] = [_('Default')]
self.font_family_model = font_family_model
self.opt_masthead_font.setModel(self.font_family_model)
'''
self.opt_mobi_file_type.addItems(['old', 'both', 'new'])
self.initialize_options(get_option, get_help, db, book_id)
'''
def set_value_handler(self, g, val):
if unicode(g.objectName()) in 'opt_masthead_font':
idx = -1
if val:
idx = g.findText(val, Qt.MatchFixedString)
if idx < 0:
idx = 0
g.setCurrentIndex(idx)
return True
return False
'''

View File

@ -211,6 +211,9 @@ class RegexEdit(QWidget, Ui_Edit):
self.button.clicked.connect(self.builder)
def builder(self):
if self.db is None:
self.doc_cache = _('Click the Open button below to open a '
'ebook to use for testing.')
bld = RegexBuilder(self.db, self.book_id, self.edit.text(), self.doc_cache, self)
if bld.cancelled:
return

View File

@ -504,7 +504,11 @@ from the value in the box</string>
<item>
<widget class="QCheckBox" name="restore_original">
<property name="toolTip">
<string>When doing a same format to same format conversion, for e.g., EPUB to EPUB, calibre saves the original EPUB as ORIGINAL_EPUB. This option tells calibre to restore the EPUB from ORIGINAL_EPUB. Useful if you did a bulk conversion of a large number of books and something went wrong.</string>
<string>When doing a same format to same format conversion,
for e.g., EPUB to EPUB, calibre saves the original EPUB
as ORIGINAL_EPUB. This option tells calibre to restore
the EPUB from ORIGINAL_EPUB. Useful if you did a bulk
conversion of a large number of books and something went wrong.</string>
</property>
<property name="text">
<string>Restore pre conversion &amp;originals, if available</string>

View File

@ -219,7 +219,13 @@ class TemplateDialog(QDialog, Ui_TemplateDialog):
if mi:
self.mi = mi
else:
self.mi = Metadata(None, None)
self.mi = Metadata(_('Title'), [_('Author')])
self.mi.author_sort = _('Author Sort')
self.mi.series = _('Series')
self.mi.series_index = 3
self.mi.rating = 4.0
self.mi.tags = [_('Tag 1'), _('Tag 2')]
self.mi.languages = ['eng']
# Remove help icon on title bar
icon = self.windowIcon()

View File

@ -7,11 +7,16 @@ __license__ = 'GPL v3'
__copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from PyQt4.Qt import (QFontInfo, QFontMetrics, Qt, QFont, QFontDatabase, QPen,
QStyledItemDelegate, QSize, QStyle, QComboBox, QStringListModel,
QDialog, QVBoxLayout, QApplication, QFontComboBox)
import os, shutil
from calibre.utils.icu import sort_key
from PyQt4.Qt import (QFontInfo, QFontMetrics, Qt, QFont, QFontDatabase, QPen,
QStyledItemDelegate, QSize, QStyle, QStringListModel, pyqtSignal,
QDialog, QVBoxLayout, QApplication, QFontComboBox, QPushButton,
QToolButton, QGridLayout, QListView, QWidget, QDialogButtonBox, QIcon,
QHBoxLayout, QLabel, QModelIndex, QLineEdit)
from calibre.constants import config_dir
from calibre.gui2 import choose_files, error_dialog, info_dialog
def writing_system_for_font(font):
has_latin = True
@ -55,6 +60,12 @@ def writing_system_for_font(font):
class FontFamilyDelegate(QStyledItemDelegate):
def sizeHint(self, option, index):
try:
return self.do_size_hint(option, index)
except:
return QSize(300, 50)
def do_size_hint(self, option, index):
text = index.data(Qt.DisplayRole).toString()
font = QFont(option.font)
font.setPointSize(QFontInfo(font).pointSize() * 1.5)
@ -62,6 +73,15 @@ class FontFamilyDelegate(QStyledItemDelegate):
return QSize(m.width(text), m.height())
def paint(self, painter, option, index):
QStyledItemDelegate.paint(self, painter, option, QModelIndex())
painter.save()
try:
self.do_paint(painter, option, index)
except:
pass
painter.restore()
def do_paint(self, painter, option, index):
text = unicode(index.data(Qt.DisplayRole).toString())
font = QFont(option.font)
font.setPointSize(QFontInfo(font).pointSize() * 1.5)
@ -75,10 +95,6 @@ class FontFamilyDelegate(QStyledItemDelegate):
r = option.rect
if option.state & QStyle.State_Selected:
painter.save()
painter.setBrush(option.palette.highlight())
painter.setPen(Qt.NoPen)
painter.drawRect(option.rect)
painter.setPen(QPen(option.palette.highlightedText(), 0))
if (option.direction == Qt.RightToLeft):
@ -86,7 +102,6 @@ class FontFamilyDelegate(QStyledItemDelegate):
else:
r.setLeft(r.left() + 4)
old = painter.font()
painter.setFont(font)
painter.drawText(r, Qt.AlignVCenter|Qt.AlignLeading|Qt.TextSingleLine, text)
@ -100,69 +115,248 @@ class FontFamilyDelegate(QStyledItemDelegate):
r.setLeft(r.left() + w)
painter.drawText(r, Qt.AlignVCenter|Qt.AlignLeading|Qt.TextSingleLine, sample)
painter.setFont(old)
if (option.state & QStyle.State_Selected):
painter.restore()
class FontFamilyChooser(QComboBox):
class Typefaces(QLabel):
def __init__(self, parent=None):
QComboBox.__init__(self, parent)
from calibre.utils.fonts import fontconfig
QLabel.__init__(self, parent)
self.setMinimumWidth(400)
self.base_msg = '<h3>'+_('Choose a font family')+'</h3>'
self.setText(self.base_msg)
self.setWordWrap(True)
def show_family(self, family, faces):
if not family:
self.setText(self.base_msg)
return
msg = '''
<h3>%s</h3>
<dl style="font-size: smaller">
{0}
</dl>
'''%(_('Available faces for %s')%family)
entries = []
for font in faces:
sf = (font['wws_subfamily_name'] or font['preferred_subfamily_name']
or font['subfamily_name'])
entries.append('''
<dt><b>{sf}</b></dt>
<dd>font-stretch: <i>{width}</i> font-weight: <i>{weight}</i> font-style:
<i>{style}</i></dd>
'''.format(sf=sf, width=font['font-stretch'],
weight=font['font-weight'], style=font['font-style']))
msg = msg.format('\n\n'.join(entries))
self.setText(msg)
class FontsView(QListView):
changed = pyqtSignal()
def __init__(self, parent):
QListView.__init__(self, parent)
self.setSelectionMode(self.SingleSelection)
self.setAlternatingRowColors(True)
self.d = FontFamilyDelegate(self)
self.setItemDelegate(self.d)
def currentChanged(self, current, previous):
self.changed.emit()
QListView.currentChanged(self, current, previous)
class FontFamilyDialog(QDialog):
def __init__(self, current_family, parent=None):
QDialog.__init__(self, parent)
self.setWindowTitle(_('Choose font family'))
self.setWindowIcon(QIcon(I('font.png')))
from calibre.utils.fonts.scanner import font_scanner
self.font_scanner = font_scanner
self.m = QStringListModel(self)
self.build_font_list()
self.l = l = QGridLayout()
self.setLayout(l)
self.view = FontsView(self)
self.view.setModel(self.m)
self.view.setCurrentIndex(self.m.index(0))
if current_family:
for i, val in enumerate(self.families):
if icu_lower(val) == icu_lower(current_family):
self.view.setCurrentIndex(self.m.index(i))
break
self.view.doubleClicked.connect(self.accept, type=Qt.QueuedConnection)
self.view.changed.connect(self.current_changed,
type=Qt.QueuedConnection)
self.faces = Typefaces(self)
self.bb = QDialogButtonBox(QDialogButtonBox.Ok|QDialogButtonBox.Cancel)
self.bb.accepted.connect(self.accept)
self.bb.rejected.connect(self.reject)
self.add_fonts_button = afb = self.bb.addButton(_('Add &fonts'),
self.bb.ActionRole)
afb.setIcon(QIcon(I('plus.png')))
afb.clicked.connect(self.add_fonts)
self.ml = QLabel(_('Choose a font family from the list below:'))
self.search = QLineEdit(self)
self.search.setPlaceholderText(_('Search'))
self.search.returnPressed.connect(self.find)
self.nb = QToolButton(self)
self.nb.setIcon(QIcon(I('arrow-down.png')))
self.nb.setToolTip(_('Find Next'))
self.pb = QToolButton(self)
self.pb.setIcon(QIcon(I('arrow-up.png')))
self.pb.setToolTip(_('Find Previous'))
self.nb.clicked.connect(self.find_next)
self.pb.clicked.connect(self.find_previous)
l.addWidget(self.ml, 0, 0, 1, 4)
l.addWidget(self.search, 1, 0, 1, 1)
l.addWidget(self.nb, 1, 1, 1, 1)
l.addWidget(self.pb, 1, 2, 1, 1)
l.addWidget(self.view, 2, 0, 1, 3)
l.addWidget(self.faces, 1, 3, 2, 1)
l.addWidget(self.bb, 3, 0, 1, 4)
l.setAlignment(self.faces, Qt.AlignTop)
self.resize(800, 600)
def set_current(self, i):
self.view.setCurrentIndex(self.m.index(i))
def keyPressEvent(self, e):
if e.key() == Qt.Key_Return:
return
return QDialog.keyPressEvent(self, e)
def find(self, backwards=False):
i = self.view.currentIndex().row()
if i < 0: i = 0
q = icu_lower(unicode(self.search.text())).strip()
if not q: return
r = (xrange(i-1, -1, -1) if backwards else xrange(i+1,
len(self.families)))
for j in r:
f = self.families[j]
if q in icu_lower(f):
self.set_current(j)
return
def find_next(self):
self.find()
def find_previous(self):
self.find(backwards=True)
def build_font_list(self):
try:
self.families = fontconfig.find_font_families()
self.families = list(self.font_scanner.find_font_families())
except:
self.families = []
print ('WARNING: Could not load fonts')
import traceback
traceback.print_exc()
# Restrict to Qt families as we need the font to be available in
# QFontDatabase
qt_families = set([unicode(x) for x in QFontDatabase().families()])
self.families = list(qt_families.intersection(set(self.families)))
self.families.sort(key=sort_key)
self.families.insert(0, _('None'))
self.m.setStringList(self.families)
self.m = QStringListModel(self.families)
self.setModel(self.m)
self.d = FontFamilyDelegate(self)
self.setItemDelegate(self.d)
self.setCurrentIndex(0)
def add_fonts(self):
from calibre.utils.fonts.metadata import FontMetadata
files = choose_files(self, 'add fonts to calibre',
_('Select font files'), filters=[(_('TrueType/OpenType Fonts'),
['ttf', 'otf'])], all_files=False)
if not files: return
families = set()
for f in files:
try:
with open(f, 'rb') as stream:
fm = FontMetadata(stream)
except:
import traceback
error_dialog(self, _('Corrupt font'),
_('Failed to read metadata from the font file: %s')%
f, det_msg=traceback.format_exc(), show=True)
return
families.add(fm.font_family)
families = sorted(families)
def event(self, e):
if e.type() == e.Resize:
view = self.view()
view.window().setFixedWidth(self.width() * 5/3)
return QComboBox.event(self, e)
dest = os.path.join(config_dir, 'fonts')
for f in files:
shutil.copyfile(f, os.path.join(dest, os.path.basename(f)))
self.font_scanner.do_scan()
self.build_font_list()
self.m.reset()
self.view.setCurrentIndex(self.m.index(0))
if families:
for i, val in enumerate(self.families):
if icu_lower(val) == icu_lower(families[0]):
self.view.setCurrentIndex(self.m.index(i))
break
def sizeHint(self):
ans = QComboBox.sizeHint(self)
ans.setWidth(QFontMetrics(self.font()).width('m'*14))
return ans
info_dialog(self, _('Added fonts'),
_('Added font families: %s')%(
', '.join(families)), show=True)
@property
def font_family(self):
idx = self.view.currentIndex().row()
if idx == 0: return None
return self.families[idx]
def current_changed(self):
fam = self.font_family
self.faces.show_family(fam, self.font_scanner.fonts_for_family(fam)
if fam else None)
class FontFamilyChooser(QWidget):
family_changed = pyqtSignal(object)
def __init__(self, parent=None):
QWidget.__init__(self, parent)
self.l = l = QHBoxLayout()
self.setLayout(l)
self.button = QPushButton(self)
self.button.setIcon(QIcon(I('font.png')))
l.addWidget(self.button)
self.default_text = _('Choose &font family')
self.font_family = None
self.button.clicked.connect(self.show_chooser)
self.clear_button = QToolButton(self)
self.clear_button.setIcon(QIcon(I('clear_left.png')))
self.clear_button.clicked.connect(self.clear_family)
l.addWidget(self.clear_button)
self.setToolTip = self.button.setToolTip
self.toolTip = self.button.toolTip
self.clear_button.setToolTip(_('Clear the font family'))
def clear_family(self):
self.font_family = None
@dynamic_property
def font_family(self):
def fget(self):
idx= self.currentIndex()
if idx == 0: return None
return self.families[idx]
return self._current_family
def fset(self, val):
if not val:
idx = 0
try:
idx = self.families.index(type(u'')(val))
except ValueError:
idx = 0
self.setCurrentIndex(idx)
val = None
self._current_family = val
self.button.setText(val or self.default_text)
self.family_changed.emit(val)
return property(fget=fget, fset=fset)
def show_chooser(self):
d = FontFamilyDialog(self.font_family, self)
if d.exec_() == d.Accepted:
self.font_family = d.font_family
if __name__ == '__main__':
def test():
app = QApplication([])
app
d = QDialog()
d.setLayout(QVBoxLayout())
d.layout().addWidget(FontFamilyChooser(d))
d.layout().addWidget(QFontComboBox(d))
d.exec_()
if __name__ == '__main__':
test()

View File

@ -871,11 +871,17 @@ class BooksModel(QAbstractTableModel): # {{{
try:
return self._set_data(index, value)
except (IOError, OSError) as err:
if getattr(err, 'errno', None) == errno.EACCES: # Permission denied
import traceback
if getattr(err, 'errno', None) == errno.EACCES: # Permission denied
fname = getattr(err, 'filename', None)
p = 'Locked file: %s\n\n'%fname if fname else ''
error_dialog(get_gui(), _('Permission denied'),
_('Could not change the on disk location of this'
' book. Is it open in another program?'),
det_msg=p+traceback.format_exc(), show=True)
return False
error_dialog(get_gui(), _('Failed to set data'),
_('Could not set data, click Show Details to see why.'),
det_msg=traceback.format_exc(), show=True)
except:
import traceback
@ -1368,6 +1374,8 @@ class DeviceBooksModel(BooksModel): # {{{
return QVariant(authors_to_string(au))
elif cname == 'size':
size = self.db[self.map[row]].size
if not isinstance(size, (float, int)):
size = 0
return QVariant(human_readable(size))
elif cname == 'timestamp':
dt = self.db[self.map[row]].datetime

View File

@ -291,6 +291,7 @@ def run_in_debug_mode(logpath=None):
def run_gui(opts, args, actions, listener, app, gui_debug=None):
initialize_file_icon_provider()
app.load_builtin_fonts(scan_for_fonts=True)
if not dynamic.get('welcome_wizard_was_run', False):
from calibre.gui2.wizard import wizard
wizard().exec_()

View File

@ -139,3 +139,5 @@ class MainWindow(QMainWindow):
show=True)
except BaseException:
pass
except:
pass

View File

@ -91,6 +91,9 @@ class TitleEdit(EnLineEdit):
def commit(self, db, id_):
title = self.current_val
if title != self.original_val:
# Only try to commit if changed. This allow setting of other fields
# to work even if some of the book files are opened in windows.
try:
if self.COMMIT:
getattr(db, 'set_'+ self.TITLE_ATTR)(id_, title, notify=False)
@ -98,13 +101,14 @@ class TitleEdit(EnLineEdit):
getattr(db, 'set_'+ self.TITLE_ATTR)(id_, title, notify=False,
commit=False)
except (IOError, OSError) as err:
if getattr(err, 'errno', -1) == errno.EACCES: # Permission denied
if getattr(err, 'errno', None) == errno.EACCES: # Permission denied
import traceback
fname = err.filename if err.filename else 'file'
fname = getattr(err, 'filename', None)
p = 'Locked file: %s\n\n'%fname if fname else ''
error_dialog(self, _('Permission denied'),
_('Could not open %s. Is it being used by another'
' program?')%fname, det_msg=traceback.format_exc(),
show=True)
_('Could not change the on disk location of this'
' book. Is it open in another program?'),
det_msg=p+traceback.format_exc(), show=True)
return False
raise
return True
@ -262,17 +266,21 @@ class AuthorsEdit(EditWithComplete):
def commit(self, db, id_):
authors = self.current_val
if authors != self.original_val:
# Only try to commit if changed. This allow setting of other fields
# to work even if some of the book files are opened in windows.
try:
self.books_to_refresh |= db.set_authors(id_, authors, notify=False,
allow_case_change=True)
except (IOError, OSError) as err:
if getattr(err, 'errno', -1) == errno.EACCES: # Permission denied
if getattr(err, 'errno', None) == errno.EACCES: # Permission denied
import traceback
fname = err.filename if err.filename else 'file'
fname = getattr(err, 'filename', None)
p = 'Locked file: %s\n\n'%fname if fname else ''
error_dialog(self, _('Permission denied'),
_('Could not open %s. Is it being used by another'
' program?')%fname, det_msg=traceback.format_exc(),
show=True)
_('Could not change the on disk location of this'
' book. Is it open in another program?'),
det_msg=p+traceback.format_exc(), show=True)
return False
raise
return True

View File

@ -322,6 +322,7 @@ class MetadataSingleDialogBase(ResizableDialog):
' program?')%fname, det_msg=traceback.format_exc(),
show=True)
return
raise
if mi is None:
return
cdata = None
@ -444,11 +445,12 @@ class MetadataSingleDialogBase(ResizableDialog):
except (IOError, OSError) as err:
if getattr(err, 'errno', None) == errno.EACCES: # Permission denied
import traceback
fname = err.filename if err.filename else 'file'
fname = getattr(err, 'filename', None)
p = 'Locked file: %s\n\n'%fname if fname else ''
error_dialog(self, _('Permission denied'),
_('Could not open %s. Is it being used by another'
' program?')%fname, det_msg=traceback.format_exc(),
show=True)
_('Could not change the on disk location of this'
' book. Is it open in another program?'),
det_msg=p+traceback.format_exc(), show=True)
return False
raise
for widget in getattr(self, 'custom_metadata_widgets', []):

View File

@ -156,7 +156,7 @@ Author matching is exact.</string>
<property name="toolTip">
<string>If set, this option will causes calibre to check if a file
being auto-added is already in the calibre library.
If it is, a meesage will pop up asking you whether
If it is, a message will pop up asking you whether
you want to add it anyway.</string>
</property>
<property name="text">

View File

@ -19,12 +19,14 @@ from calibre.utils.smtp import config as smtp_prefs
class EmailAccounts(QAbstractTableModel): # {{{
def __init__(self, accounts, subjects):
def __init__(self, accounts, subjects, aliases={}):
QAbstractTableModel.__init__(self)
self.accounts = accounts
self.subjects = subjects
self.aliases = aliases
self.account_order = sorted(self.accounts.keys())
self.headers = map(QVariant, [_('Email'), _('Formats'), _('Subject'), _('Auto send')])
self.headers = map(QVariant, [_('Email'), _('Formats'), _('Subject'),
_('Auto send'), _('Alias')])
self.default_font = QFont()
self.default_font.setBold(True)
self.default_font = QVariant(self.default_font)
@ -36,7 +38,9 @@ class EmailAccounts(QAbstractTableModel): # {{{
'{author_sort} can be used here.'),
'<p>'+_('If checked, downloaded news will be automatically '
'mailed <br>to this email address '
'(provided it is in one of the listed formats).')])))
'(provided it is in one of the listed formats).'),
_('Friendly name to use for this email address')
])))
def rowCount(self, *args):
return len(self.account_order)
@ -67,6 +71,8 @@ class EmailAccounts(QAbstractTableModel): # {{{
return QVariant(self.accounts[account][0])
if col == 2:
return QVariant(self.subjects.get(account, ''))
if col == 4:
return QVariant(self.aliases.get(account, ''))
if role == Qt.FontRole and self.accounts[account][2]:
return self.default_font
if role == Qt.CheckStateRole and col == 3:
@ -88,6 +94,11 @@ class EmailAccounts(QAbstractTableModel): # {{{
self.accounts[account][1] ^= True
elif col == 2:
self.subjects[account] = unicode(value.toString())
elif col == 4:
self.aliases.pop(account, None)
aval = unicode(value.toString()).strip()
if aval:
self.aliases[account] = aval
elif col == 1:
self.accounts[account][0] = unicode(value.toString()).upper()
elif col == 0:
@ -156,7 +167,8 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
self.send_email_widget.initialize(self.preferred_to_address)
self.send_email_widget.changed_signal.connect(self.changed_signal.emit)
opts = self.send_email_widget.smtp_opts
self._email_accounts = EmailAccounts(opts.accounts, opts.subjects)
self._email_accounts = EmailAccounts(opts.accounts, opts.subjects,
opts.aliases)
self._email_accounts.dataChanged.connect(lambda x,y:
self.changed_signal.emit())
self.email_view.setModel(self._email_accounts)
@ -184,6 +196,7 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
raise AbortCommit('abort')
self.proxy['accounts'] = self._email_accounts.accounts
self.proxy['subjects'] = self._email_accounts.subjects
self.proxy['aliases'] = self._email_accounts.aliases
return ConfigWidgetBase.commit(self)

View File

@ -403,7 +403,12 @@ class ConfigWidget(ConfigWidgetBase, Ui_Form):
return
all_locations = OrderedDict(ConfigWidget.LOCATIONS)
try:
plugin_action = plugin.load_actual_plugin(self.gui)
except:
# Broken plugin, fails to initialize. Given that, it's probably
# already configured, so we can just quit.
return
installed_actions = OrderedDict([
(key, list(gprefs.get('action-layout-'+key, [])))
for key in all_locations])

View File

@ -128,34 +128,13 @@ class AmazonKindleStore(StorePlugin):
with closing(br.open(url, timeout=timeout)) as f:
doc = html.fromstring(f.read().decode('latin-1', 'replace'))
# Amazon has two results pages.
is_shot = doc.xpath('boolean(//div[@id="shotgunMainResults"])')
# Horizontal grid of books. Search "Paolo Bacigalupi"
if is_shot:
data_xpath = '//div[contains(@class, "result")]'
format_xpath = './/div[@class="productTitle"]//text()'
asin_xpath = './/div[@class="productTitle"]//a'
cover_xpath = './/div[@class="productTitle"]//img/@src'
title_xpath = './/div[@class="productTitle"]/a//text()'
price_xpath = './/div[@class="newPrice"]/span/text()'
# Vertical list of books.
else:
# New style list. Search "Paolo Bacigalupi"
if doc.xpath('boolean(//div[@class="image"])'):
data_xpath = '//div[contains(@class, "results")]//div[contains(@class, "result")]'
format_xpath = './/span[@class="binding"]//text()'
data_xpath = '//div[contains(@class, "prod")]'
format_xpath = './/ul[contains(@class, "rsltL")]//span[contains(@class, "lrg") and not(contains(@class, "bld"))]/text()'
asin_xpath = './/div[@class="image"]/a[1]'
cover_xpath = './/img[@class="productImage"]/@src'
title_xpath = './/a[@class="title"]/text()'
price_xpath = './/span[contains(@class, "price")]/text()'
# Old style list. Search "martin"
else:
data_xpath = '//div[contains(@class, "result")]'
format_xpath = './/span[@class="format"]//text()'
asin_xpath = './/div[@class="productImage"]/a[1]'
cover_xpath = './/div[@class="productImage"]//img/@src'
title_xpath = './/div[@class="productTitle"]/a/text()'
price_xpath = './/div[@class="newPrice"]//span//text()'
title_xpath = './/h3[@class="newaps"]/a//text()'
author_xpath = './/h3[@class="newaps"]//span[contains(@class, "reg")]/text()'
price_xpath = './/ul[contains(@class, "rsltL")]//span[contains(@class, "lrg") and contains(@class, "bld")]/text()'
for data in doc.xpath(data_xpath):
if counter <= 0:
@ -186,13 +165,13 @@ class AmazonKindleStore(StorePlugin):
cover_url = ''.join(data.xpath(cover_xpath))
title = ''.join(data.xpath(title_xpath))
price = ''.join(data.xpath(price_xpath))
author = ''.join(data.xpath(author_xpath))
try:
author = author.split('by ', 1)[1].split(" (")[0]
except:
pass
if is_shot:
author = format.split(' by ')[-1]
else:
author = ''.join(data.xpath('.//span[@class="ptBrand"]/text()'))
author = author.split('by ')[-1]
price = ''.join(data.xpath(price_xpath))
counter -= 1

View File

@ -6,7 +6,6 @@ __license__ = 'GPL 3'
__copyright__ = '2011, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en'
import random
import re
import urllib2
from contextlib import closing
@ -25,23 +24,12 @@ from calibre.gui2.store.web_store_dialog import WebStoreDialog
class EHarlequinStore(BasicStoreConfig, StorePlugin):
def open(self, parent=None, detail_item=None, external=False):
m_url = 'http://www.dpbolvw.net/'
h_click = 'click-4879827-534091'
d_click = 'click-4879827-10375439'
# Use Kovid's affiliate id 30% of the time.
if random.randint(1, 10) in (1, 2, 3):
h_click = 'click-4913808-534091'
d_click = 'click-4913808-10375439'
url = m_url + h_click
detail_url = None
if detail_item:
detail_url = m_url + d_click + detail_item
url = 'http://www.harlequin.com/'
if external or self.config.get('open_external', False):
open_url(QUrl(url_slash_cleaner(detail_url if detail_url else url)))
open_url(QUrl(url_slash_cleaner(detail_item if detail_item else url)))
else:
d = WebStoreDialog(self.gui, url, parent, detail_url)
d = WebStoreDialog(self.gui, url, parent, detail_item)
d.setWindowTitle(self.name)
d.set_tags(self.config.get('tags', ''))
d.exec_()
@ -74,7 +62,7 @@ class EHarlequinStore(BasicStoreConfig, StorePlugin):
s.title = title.strip()
s.author = author.strip()
s.price = price.strip()
s.detail_item = '?url=http://ebooks.eharlequin.com/' + id.strip()
s.detail_item = 'http://ebooks.eharlequin.com/' + id.strip()
s.formats = 'EPUB'
yield s

View File

@ -0,0 +1,80 @@
# -*- coding: utf-8 -*-
from __future__ import (unicode_literals, division, absolute_import, print_function)
__license__ = 'GPL 3'
__copyright__ = '2011-2012, Tomasz Długosz <tomek3d@gmail.com>'
__docformat__ = 'restructuredtext en'
import re
import urllib
from contextlib import closing
from lxml import html
from PyQt4.Qt import QUrl
from calibre import browser, url_slash_cleaner
from calibre.gui2 import open_url
from calibre.gui2.store import StorePlugin
from calibre.gui2.store.basic_config import BasicStoreConfig
from calibre.gui2.store.search_result import SearchResult
from calibre.gui2.store.web_store_dialog import WebStoreDialog
class EmpikStore(BasicStoreConfig, StorePlugin):
def open(self, parent=None, detail_item=None, external=False):
plain_url = 'http://www.empik.com/ebooki'
url = 'https://ssl.afiliant.com/affskrypt,,2f9de2,,23c7f,,,?u=(' + plain_url + ')'
detail_url = None
if detail_item:
detail_url = 'https://ssl.afiliant.com/affskrypt,,2f9de2,,23c7f,,,?u=(' + detail_item + ')'
if external or self.config.get('open_external', False):
open_url(QUrl(url_slash_cleaner(detail_url if detail_url else url)))
else:
d = WebStoreDialog(self.gui, url, parent, detail_url)
d.setWindowTitle(self.name)
d.set_tags(self.config.get('tags', ''))
d.exec_()
def search(self, query, max_results=10, timeout=60):
url = 'http://www.empik.com/szukaj/produkt?c=ebooki-ebooki&q=' + urllib.quote(query) + '&qtype=basicForm&start=1&catalogType=pl&searchCategory=3501&resultsPP=' + str(max_results)
br = browser()
counter = max_results
with closing(br.open(url, timeout=timeout)) as f:
doc = html.fromstring(f.read())
for data in doc.xpath('//div[@class="productsSet"]/div'):
if counter <= 0:
break
id = ''.join(data.xpath('.//a[@class="productBox-450Title"]/@href'))
if not id:
continue
cover_url = ''.join(data.xpath('.//div[@class="productBox-450Pic"]/a/img/@src'))
title = ''.join(data.xpath('.//a[@class="productBox-450Title"]/text()'))
title = re.sub(r' \(ebook\)', '', title)
author = ''.join(data.xpath('.//div[@class="productBox-450Author"]/a/text()'))
price = ''.join(data.xpath('.//div[@class="actPrice"]/text()'))
formats = ''.join(data.xpath('.//div[@class="productBox-450Type"]/text()'))
formats = re.sub(r'Ebook *,? *','', formats)
formats = re.sub(r'\(.*\)','', formats)
drm = data.xpath('boolean(.//div[@class="productBox-450Type" and contains(text(), "ADE")])')
counter -= 1
s = SearchResult()
s.cover_url = cover_url
s.title = title.strip() + ' ' + formats
s.author = author.strip()
s.price = price
s.detail_item = 'http://empik.com' + id.strip()
s.formats = formats.upper().strip()
s.drm = SearchResult.DRM_LOCKED if drm else SearchResult.DRM_UNLOCKED
yield s

View File

@ -68,10 +68,10 @@ class GoogleBooksStore(BasicStoreConfig, StorePlugin):
continue
title = ''.join(data.xpath('.//h3/a//text()'))
authors = data.xpath('.//span[@class="f"]//a//text()')
if authors and authors[-1].strip().lower() in ('preview', 'read'):
authors = data.xpath('.//div[@class="f"]//a//text()')
while authors and authors[-1].strip().lower() in ('preview', 'read', 'more editions'):
authors = authors[:-1]
else:
if not authors:
continue
author = ', '.join(authors)

View File

@ -7,6 +7,7 @@ __copyright__ = '2011, John Schember <john@nachtimwald.com>'
__docformat__ = 'restructuredtext en'
import random
import urllib
import urllib2
from contextlib import closing
@ -24,23 +25,24 @@ from calibre.gui2.store.web_store_dialog import WebStoreDialog
class KoboStore(BasicStoreConfig, StorePlugin):
def open(self, parent=None, detail_item=None, external=False):
m_url = 'http://www.dpbolvw.net/'
h_click = 'click-4879827-10762497'
d_click = 'click-4879827-10772898'
pub_id = 'sHa5EXvYOwA'
# Use Kovid's affiliate id 30% of the time.
if random.randint(1, 10) in (1, 2, 3):
h_click = 'click-4913808-10762497'
d_click = 'click-4913808-10772898'
pub_id = '0dsO3kDu/AU'
murl = 'http://click.linksynergy.com/fs-bin/click?id=%s&offerid=268429.4&type=3&subid=0' % pub_id
url = m_url + h_click
detail_url = None
if detail_item:
detail_url = m_url + d_click + detail_item
purl = 'http://click.linksynergy.com/link?id=%s&offerid=268429&type=2&murl=%s' % (pub_id, urllib.quote_plus(detail_item))
url = purl
else:
purl = None
url = murl
if external or self.config.get('open_external', False):
open_url(QUrl(url_slash_cleaner(detail_url if detail_url else url)))
open_url(QUrl(url_slash_cleaner(url)))
else:
d = WebStoreDialog(self.gui, url, parent, detail_url)
d = WebStoreDialog(self.gui, murl, parent, purl)
d.setWindowTitle(self.name)
d.set_tags(self.config.get('tags', ''))
d.exec_()
@ -60,15 +62,19 @@ class KoboStore(BasicStoreConfig, StorePlugin):
id = ''.join(data.xpath('.//div[@class="SearchImageContainer"]/a[1]/@href'))
if not id:
continue
try:
id = id.split('?', 1)[0]
except:
continue
price = ''.join(data.xpath('.//span[@class="OurPrice"]/strong/text()'))
price = ''.join(data.xpath('.//span[@class="KV2OurPrice"]/strong/text()'))
if not price:
price = '$0.00'
cover_url = ''.join(data.xpath('.//div[@class="SearchImageContainer"]//img[1]/@src'))
title = ''.join(data.xpath('.//div[@class="SCItemHeader"]/h1/a[1]/text()'))
author = ', '.join(data.xpath('.//div[@class="SCItemSummary"]//span//a/text()'))
title = ''.join(data.xpath('.//div[@class="SCItemHeader"]//a[1]/text()'))
author = ', '.join(data.xpath('.//div[@class="SCItemSummary"]//span[contains(@class, "Author")]//a/text()'))
drm = data.xpath('boolean(.//span[@class="SCAvailibilityFormatsText" and not(contains(text(), "DRM-Free"))])')
counter -= 1
@ -78,7 +84,7 @@ class KoboStore(BasicStoreConfig, StorePlugin):
s.title = title.strip()
s.author = author.strip()
s.price = price.strip()
s.detail_item = '?url=http://www.kobobooks.com/' + id.strip()
s.detail_item = 'http://www.kobobooks.com/' + id.strip()
s.drm = SearchResult.DRM_LOCKED if drm else SearchResult.DRM_UNLOCKED
s.formats = 'EPUB'

View File

@ -66,6 +66,8 @@ class SonyStore(BasicStoreConfig, StorePlugin):
detail_url = ''.join(item.xpath('descendant::h3[@class="item"]'
'/descendant::a[@class="fn" and @href]/@href'))
if not detail_url: continue
if detail_url.startswith('/'):
detail_url = 'http:'+detail_url
s.detail_item = detail_url
counter -= 1

View File

@ -415,10 +415,10 @@ class TagsModel(QAbstractItemModel): # {{{
if not tag.sort:
c = ' '
else:
c = tag.sort
c = icu_upper(tag.sort)
ordnum, ordlen = collation_order(c)
if last_ordnum != ordnum:
last_c = icu_upper(c[0:ordlen])
last_c = c[0:ordlen]
last_ordnum = ordnum
cl_list[idx] = last_c
top_level_component = 'z' + data[key][0].original_name

View File

@ -191,10 +191,6 @@ class Main(MainWindow, MainWindowMixin, DeviceMixin, EmailMixin, # {{{
self.content_server = None
self.spare_servers = []
self.must_restart_before_config = False
# Initialize fontconfig in a separate thread as this can be a lengthy
# process if run for the first time on this machine
from calibre.utils.fonts import fontconfig
self.fc = fontconfig
self.listener = Listener(listener)
self.check_messages_timer = QTimer()
self.connect(self.check_messages_timer, SIGNAL('timeout()'),

View File

@ -16,7 +16,7 @@ from PyQt4.QtWebKit import QWebPage, QWebView, QWebSettings
from calibre.gui2.viewer.flip import SlideFlip
from calibre.gui2.shortcuts import Shortcuts
from calibre import prints, load_builtin_fonts
from calibre import prints
from calibre.customize.ui import all_viewer_plugins
from calibre.gui2.viewer.keys import SHORTCUTS
from calibre.gui2.viewer.javascript import JavaScriptLoader
@ -86,7 +86,6 @@ class Document(QWebPage): # {{{
settings = self.settings()
# Fonts
load_builtin_fonts()
self.all_viewer_plugins = tuple(all_viewer_plugins())
for pl in self.all_viewer_plugins:
pl.load_fonts()
@ -486,7 +485,7 @@ class DocumentView(QWebView): # {{{
self.dictionary_action.triggered.connect(self.lookup)
self.addAction(self.dictionary_action)
self.image_popup = ImagePopup(self)
self.view_image_action = QAction(_('View &image...'), self)
self.view_image_action = QAction(QIcon(I('view-image.png')), _('View &image...'), self)
self.view_image_action.triggered.connect(self.image_popup)
self.search_action = QAction(QIcon(I('dictionary.png')),
_('&Search for next occurrence'), self)

View File

@ -8,7 +8,8 @@ __copyright__ = '2012, Kovid Goyal <kovid at kovidgoyal.net>'
__docformat__ = 'restructuredtext en'
from PyQt4.Qt import (QDialog, QPixmap, QUrl, QScrollArea, QLabel, QSizePolicy,
QDialogButtonBox, QVBoxLayout, QPalette, QApplication, QSize, QIcon, Qt)
QDialogButtonBox, QVBoxLayout, QPalette, QApplication, QSize, QIcon,
Qt, QTransform)
from calibre.gui2 import choose_save_file, gprefs
@ -37,12 +38,15 @@ class ImageView(QDialog):
self.zi_button = zi = bb.addButton(_('Zoom &in'), bb.ActionRole)
self.zo_button = zo = bb.addButton(_('Zoom &out'), bb.ActionRole)
self.save_button = so = bb.addButton(_('&Save as'), bb.ActionRole)
self.rotate_button = ro = bb.addButton(_('&Rotate'), bb.ActionRole)
zi.setIcon(QIcon(I('plus.png')))
zo.setIcon(QIcon(I('minus.png')))
so.setIcon(QIcon(I('save.png')))
ro.setIcon(QIcon(I('rotate-right.png')))
zi.clicked.connect(self.zoom_in)
zo.clicked.connect(self.zoom_out)
so.clicked.connect(self.save_image)
ro.clicked.connect(self.rotate_image)
self.l = l = QVBoxLayout()
self.setLayout(l)
@ -76,6 +80,14 @@ class ImageView(QDialog):
self.scrollarea.verticalScrollBar()):
sb.setValue(int(factor*sb.value()) + ((factor - 1) * sb.pageStep()/2))
def rotate_image(self):
pm = self.label.pixmap()
t = QTransform()
t.rotate(90)
pm = pm.transformed(t)
self.label.setPixmap(pm)
self.label.adjustSize()
def __call__(self):
geom = self.avail_geom
self.label.setPixmap(self.current_img)
@ -93,6 +105,14 @@ class ImageView(QDialog):
gprefs['viewer_image_popup_geometry'] = bytearray(self.saveGeometry())
return QDialog.done(self, e)
def wheelEvent(self, event):
if event.delta() < -14:
self.zoom_out()
event.accept()
elif event.delta() > 14:
event.accept()
self.zoom_in()
class ImagePopup(object):
def __init__(self, parent):
@ -114,3 +134,12 @@ class ImagePopup(object):
if not d.isVisible():
self.dialogs.remove(d)
if __name__ == '__main__':
import sys
app = QApplication([])
p = QPixmap()
p.load(sys.argv[-1])
u = QUrl.fromLocalFile(sys.argv[-1])
d = ImageView(None, p, u)
d()
app.exec_()

View File

@ -963,7 +963,8 @@ class EbookViewer(MainWindow, Ui_EbookViewer):
self.iterator.__exit__()
self.iterator = EbookIterator(pathtoebook)
self.open_progress_indicator(_('Loading ebook...'))
worker = Worker(target=self.iterator.__enter__)
worker = Worker(target=partial(self.iterator.__enter__,
extract_embedded_fonts_for_qt=True))
worker.start()
while worker.isAlive():
worker.join(0.1)
@ -1136,6 +1137,7 @@ def main(args=sys.argv):
if pid <= 0:
override = 'calibre-ebook-viewer' if islinux else None
app = Application(args, override_program_name=override)
app.load_builtin_fonts()
app.setWindowIcon(QIcon(I('viewer.png')))
QApplication.setOrganizationName(ORG_NAME)
QApplication.setApplicationName(APP_UID)

View File

@ -3,18 +3,16 @@ __copyright__ = '2008, Kovid Goyal <kovid at kovidgoyal.net>'
'''
Miscellaneous widgets used in the GUI
'''
import re, traceback, os
import re, os
from PyQt4.Qt import (QIcon, QFont, QLabel, QListWidget, QAction,
QListWidgetItem, QTextCharFormat, QApplication, QSyntaxHighlighter,
QCursor, QColor, QWidget, QPixmap, QSplitterHandle, QToolButton,
QAbstractListModel, QVariant, Qt, SIGNAL, pyqtSignal, QRegExp, QSize,
QSplitter, QPainter, QLineEdit, QComboBox, QPen, QGraphicsScene, QMenu,
QStringListModel, QCompleter, QStringList, QTimer, QRect,
QFontDatabase, QGraphicsView, QByteArray)
QVariant, Qt, SIGNAL, pyqtSignal, QRegExp, QSize, QSplitter, QPainter,
QLineEdit, QComboBox, QPen, QGraphicsScene, QMenu, QStringListModel,
QCompleter, QStringList, QTimer, QRect, QGraphicsView, QByteArray)
from calibre.constants import iswindows
from calibre.gui2 import (NONE, error_dialog, pixmap_to_data, gprefs,
from calibre.gui2 import (error_dialog, pixmap_to_data, gprefs,
warning_dialog)
from calibre.gui2.filename_pattern_ui import Ui_Form
from calibre import fit_image
@ -348,46 +346,6 @@ class CoverView(QGraphicsView, ImageDropMixin): # {{{
# }}}
class FontFamilyModel(QAbstractListModel): # {{{
def __init__(self, *args):
QAbstractListModel.__init__(self, *args)
from calibre.utils.fonts import fontconfig
try:
self.families = fontconfig.find_font_families()
except:
self.families = []
print 'WARNING: Could not load fonts'
traceback.print_exc()
# Restrict to Qt families as Qt tends to crash
qt_families = set([unicode(x) for x in QFontDatabase().families()])
self.families = list(qt_families.intersection(set(self.families)))
self.families.sort()
self.families[:0] = [_('None')]
self.font = QFont('Arial' if iswindows else 'sansserif')
def rowCount(self, *args):
return len(self.families)
def data(self, index, role):
try:
family = self.families[index.row()]
except:
traceback.print_exc()
return NONE
if role == Qt.DisplayRole:
return QVariant(family)
if role == Qt.FontRole:
# If a user chooses some non standard font as the interface font,
# rendering some font names causes Qt to crash, so return what is
# hopefully a "safe" font
return QVariant(self.font)
return NONE
def index_of(self, family):
return self.families.index(family.strip())
# }}}
# BasicList {{{
class BasicListItem(QListWidgetItem):

View File

@ -569,12 +569,12 @@ class CatalogBuilder(object):
prefix (str): matched a prefix_rule
None: no match
"""
def _log_prefix_rule_match_info(rule, record, field_contents):
def _log_prefix_rule_match_info(rule, record, matched):
self.opts.log.info(" %s '%s' by %s (%s: '%s' contains '%s')" %
(rule['prefix'],record['title'],
record['authors'][0], rule['name'],
self.db.metadata_for_field(rule['field'])['name'],
field_contents))
matched))
# Compare the record to each rule looking for a match
for rule in self.prefix_rules:
@ -582,7 +582,7 @@ class CatalogBuilder(object):
if rule['field'].lower() == 'tags':
if rule['pattern'].lower() in map(unicode.lower,record['tags']):
if self.opts.verbose:
_log_prefix_rule_match_info(rule, record)
_log_prefix_rule_match_info(rule, record, rule['pattern'])
return rule['prefix']
# Regex match for custom field
@ -649,7 +649,6 @@ class CatalogBuilder(object):
cl_list = [None] * len(item_list)
last_ordnum = 0
last_c = u''
for idx, item in enumerate(item_list):
if key:
@ -659,9 +658,10 @@ class CatalogBuilder(object):
ordnum, ordlen = collation_order(c)
if isosx and platform.mac_ver()[0] < '10.8':
# Hackhackhackhackhack
# icu returns bogus results with curly apostrophes, maybe others under OS X 10.6.x
# When we see the magic combo of 0/-1 for ordnum/ordlen, special case the logic
last_c = u''
if ordnum == 0 and ordlen == -1:
if icu_upper(c[0]) != last_c:
last_c = icu_upper(c[0])
@ -2757,7 +2757,6 @@ class CatalogBuilder(object):
"""
from calibre.ebooks.conversion.config import load_defaults
from calibre.utils.fonts import fontconfig
MI_WIDTH = 600
MI_HEIGHT = 60
@ -2767,11 +2766,10 @@ class CatalogBuilder(object):
masthead_font_family = recs.get('masthead_font', 'Default')
if masthead_font_family != 'Default':
masthead_font = fontconfig.files_for_family(masthead_font_family)
# Assume 'normal' always in dict, else use default
# {'normal': (path_to_font, friendly name)}
if 'normal' in masthead_font:
font_path = masthead_font['normal'][0]
from calibre.utils.fonts.scanner import font_scanner
faces = font_scanner.fonts_for_family(masthead_font_family)
if faces:
font_path = faces[0]['path']
if not font_path or not os.access(font_path, os.R_OK):
font_path = default_font

View File

@ -30,7 +30,8 @@ from calibre.ptempfile import (PersistentTemporaryFile,
base_dir, SpooledTemporaryFile)
from calibre.customize.ui import run_plugins_on_import
from calibre import isbytestring
from calibre.utils.filenames import ascii_filename, samefile
from calibre.utils.filenames import (ascii_filename, samefile,
WindowsAtomicFolderMove, hardlink_file)
from calibre.utils.date import (utcnow, now as nowf, utcfromtimestamp,
parse_only_date, UNDEFINED_DATE)
from calibre.utils.config import prefs, tweaks, from_json, to_json
@ -640,22 +641,25 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
if name and name != fname:
changed = True
break
tpath = os.path.join(self.library_path, *path.split('/'))
if not os.path.exists(tpath):
os.makedirs(tpath)
if path == current_path and not changed:
return
spath = os.path.join(self.library_path, *current_path.split('/'))
tpath = os.path.join(self.library_path, *path.split('/'))
if current_path and os.path.exists(spath): # Migrate existing files
cdata = self.cover(id, index_is_id=True)
if cdata is not None:
with lopen(os.path.join(tpath, 'cover.jpg'), 'wb') as f:
f.write(cdata)
source_ok = current_path and os.path.exists(spath)
wam = WindowsAtomicFolderMove(spath) if iswindows and source_ok else None
try:
if not os.path.exists(tpath):
os.makedirs(tpath)
if source_ok: # Migrate existing files
self.copy_cover_to(id, os.path.join(tpath, 'cover.jpg'),
index_is_id=True, windows_atomic_move=wam,
use_hardlink=True)
for format in formats:
copy_function = functools.partial(self.copy_format_to, id,
format, index_is_id=True)
format, index_is_id=True, windows_atomic_move=wam,
use_hardlink=True)
try:
self.add_format(id, format, None, index_is_id=True,
path=tpath, notify=False, copy_function=copy_function)
@ -666,12 +670,17 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.conn.commit()
self.data.set(id, self.FIELD_MAP['path'], path, row_is_id=True)
# Delete not needed directories
if current_path and os.path.exists(spath):
if source_ok:
if not samefile(spath, tpath):
if wam is not None:
wam.delete_originals()
self.rmtree(spath, permanent=True)
parent = os.path.dirname(spath)
if len(os.listdir(parent)) == 0:
self.rmtree(parent, permanent=True)
finally:
if wam is not None:
wam.close_handles()
curpath = self.library_path
c1, c2 = current_path.split('/'), path.split('/')
@ -1340,27 +1349,98 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
return None
return fmt_path
def copy_format_to(self, index, fmt, dest, index_is_id=False):
def copy_format_to(self, index, fmt, dest, index_is_id=False,
windows_atomic_move=None, use_hardlink=False):
'''
Copy the format ``fmt`` to the file like object ``dest``. If the
specified format does not exist, raises :class:`NoSuchFormat` error.
dest can also be a path, in which case the format is copied to it, iff
the path is different from the current path (taking case sensitivity
into account).
If use_hardlink is True, a hard link will be created instead of the
file being copied. Use with care, because a hard link means that
modifying any one file will cause both files to be modified.
windows_atomic_move is an internally used parameter. You should not use
it in any code outside this module.
'''
path = self.format_abspath(index, fmt, index_is_id=index_is_id)
if path is None:
id_ = index if index_is_id else self.id(index)
raise NoSuchFormat('Record %d has no %s file'%(id_, fmt))
if windows_atomic_move is not None:
if not isinstance(dest, basestring):
raise Exception("Error, you must pass the dest as a path when"
" using windows_atomic_move")
if dest and not samefile(dest, path):
windows_atomic_move.copy_path_to(path, dest)
else:
if hasattr(dest, 'write'):
with lopen(path, 'rb') as f:
shutil.copyfileobj(f, dest)
if hasattr(dest, 'flush'):
dest.flush()
elif dest and not samefile(dest, path):
if use_hardlink:
try:
hardlink_file(path, dest)
return
except:
pass
with lopen(path, 'rb') as f, lopen(dest, 'wb') as d:
shutil.copyfileobj(f, d)
def copy_cover_to(self, index, dest, index_is_id=False,
windows_atomic_move=None, use_hardlink=False):
'''
Copy the cover to the file like object ``dest``. Returns False
if no cover exists or dest is the same file as the current cover.
dest can also be a path in which case the cover is
copied to it iff the path is different from the current path (taking
case sensitivity into account).
If use_hardlink is True, a hard link will be created instead of the
file being copied. Use with care, because a hard link means that
modifying any one file will cause both files to be modified.
windows_atomic_move is an internally used parameter. You should not use
it in any code outside this module.
'''
id = index if index_is_id else self.id(index)
path = os.path.join(self.library_path, self.path(id, index_is_id=True), 'cover.jpg')
if windows_atomic_move is not None:
if not isinstance(dest, basestring):
raise Exception("Error, you must pass the dest as a path when"
" using windows_atomic_move")
if os.access(path, os.R_OK) and dest and not samefile(dest, path):
windows_atomic_move.copy_path_to(path, dest)
return True
else:
if os.access(path, os.R_OK):
try:
f = lopen(path, 'rb')
except (IOError, OSError):
time.sleep(0.2)
f = lopen(path, 'rb')
with f:
if hasattr(dest, 'write'):
shutil.copyfileobj(f, dest)
if hasattr(dest, 'flush'):
dest.flush()
return True
elif dest and not samefile(dest, path):
if use_hardlink:
try:
hardlink_file(path, dest)
return True
except:
pass
with lopen(dest, 'wb') as d:
shutil.copyfileobj(f, d)
return True
return False
def format(self, index, format, index_is_id=False, as_file=False,
mode='r+b', as_path=False, preserve_filename=False):
'''
@ -2125,13 +2205,14 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
def set(self, row, column, val, allow_case_change=False):
'''
Convenience method for setting the title, authors, publisher or rating
Convenience method for setting the title, authors, publisher, tags or
rating
'''
id = self.data[row][0]
col = {'title':1, 'authors':2, 'publisher':3, 'rating':4, 'tags':7}[column]
col = self.FIELD_MAP[column]
books_to_refresh = set()
self.data.set(row, col, val)
set_args = (row, col, val)
if column == 'authors':
val = string_to_authors(val)
books_to_refresh |= self.set_authors(id, val, notify=False,
@ -2147,6 +2228,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
books_to_refresh |= \
self.set_tags(id, [x.strip() for x in val.split(',') if x.strip()],
append=False, notify=False, allow_case_change=allow_case_change)
self.data.set(*set_args)
self.data.refresh_ids(self, [id])
self.set_path(id, True)
self.notify('metadata', [id])
@ -2394,6 +2476,23 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
self.clean_standard_field('authors', commit=True)
return books_to_refresh
def windows_check_if_files_in_use(self, book_id):
'''
Raises an EACCES IOError if any of the files in the folder of book_id
are opened in another program on windows.
'''
if iswindows:
path = self.path(book_id, index_is_id=True)
if path:
spath = os.path.join(self.library_path, *path.split('/'))
wam = None
if os.path.exists(spath):
try:
wam = WindowsAtomicFolderMove(spath)
finally:
if wam is not None:
wam.close_handles()
def set_authors(self, id, authors, notify=True, commit=True,
allow_case_change=False):
'''
@ -2402,6 +2501,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
:param authors: A list of authors.
'''
self.windows_check_if_files_in_use(id)
books_to_refresh = self._set_authors(id, authors,
allow_case_change=allow_case_change)
self.dirtied(set([id])|books_to_refresh, commit=False)
@ -2452,6 +2552,7 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
Note that even if commit is False, the db will still be committed to
because this causes the location of files to change
'''
self.windows_check_if_files_in_use(id)
if not self._set_title(id, title):
return
self.set_path(id, index_is_id=True)

View File

@ -32,13 +32,15 @@ def test_lxml():
else:
raise RuntimeError('lxml failed')
def test_fontconfig():
from calibre.utils.fonts import fontconfig
families = fontconfig.find_font_families()
num = len(families)
if num < 10:
raise RuntimeError('Fontconfig found only %d font families'%num)
print ('Fontconfig OK! (%d families)'%num)
def test_freetype():
from calibre.utils.fonts.free_type import test
test()
print ('FreeType OK!')
def test_sfntly():
from calibre.utils.fonts.subset import test
test()
print ('sfntly OK!')
def test_winutil():
from calibre.devices.scanner import win_pnp_drives
@ -103,21 +105,28 @@ def test_icu():
def test_wpd():
wpd = plugins['wpd'][0]
try:
wpd.init()
wpd.init('calibre', 1, 1, 1)
except wpd.NoWPD:
print ('This computer does not have WPD')
else:
wpd.uninit()
def test_woff():
from calibre.utils.fonts.woff import test
test()
print ('WOFF ok!')
def test():
test_plugins()
test_lxml()
test_fontconfig()
test_freetype()
test_sfntly()
test_sqlite()
test_qt()
test_imaging()
test_unrar()
test_icu()
test_woff()
test_qt()
if iswindows:
test_win32()
test_winutil()

Some files were not shown because too many files have changed in this diff Show More