Merge from trunk

This commit is contained in:
Charles Haley 2012-10-22 08:48:48 +02:00
commit e0efbe0f0f
47 changed files with 2738 additions and 261 deletions

View File

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

View File

@ -7,7 +7,7 @@ class AdvancedUserRecipe1306061239(BasicNewsRecipe):
description = 'News as provided by The Daily Mirror -UK' description = 'News as provided by The Daily Mirror -UK'
__author__ = 'Dave Asbury' __author__ = 'Dave Asbury'
# last updated 8/6/12 # last updated 19/10/12
language = 'en_GB' language = 'en_GB'
#cover_url = 'http://yookeo.com/screens/m/i/mirror.co.uk.jpg' #cover_url = 'http://yookeo.com/screens/m/i/mirror.co.uk.jpg'
@ -15,10 +15,12 @@ class AdvancedUserRecipe1306061239(BasicNewsRecipe):
oldest_article = 1 oldest_article = 1
max_articles_per_feed = 12 max_articles_per_feed = 1
remove_empty_feeds = True remove_empty_feeds = True
remove_javascript = True remove_javascript = True
no_stylesheets = True no_stylesheets = True
ignore_duplicate_articles = {'title'}
# auto_cleanup = True # auto_cleanup = True
#conversion_options = { 'linearize_tables' : True } #conversion_options = { 'linearize_tables' : True }
@ -60,11 +62,12 @@ class AdvancedUserRecipe1306061239(BasicNewsRecipe):
# example of commented out feed not needed ,(u'Travel','http://www.mirror.co.uk/advice/travel/rss.xml') # example of commented out feed not needed ,(u'Travel','http://www.mirror.co.uk/advice/travel/rss.xml')
] ]
extra_css = ''' extra_css = '''
h1{ font-size:medium;} h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
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;} h2{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;}
img { display:block} 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): def get_cover_url(self):
soup = self.index_to_soup('http://www.politicshome.com/uk/latest_frontpage.html') 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 #cov2 now contains url of the page containing pic
soup = self.index_to_soup(cov2) soup = self.index_to_soup(cov2)
cov = soup.find(attrs={'id' : 'large'}) cov = soup.find(attrs={'id' : 'large'})
cov2 = str(cov) cov=str(cov)
cov2=cov2[27:-18] 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 #cov2 now is pic url, now go back to original function
br = browser() br = browser()
br.set_handle_redirect(False) br.set_handle_redirect(False)

View File

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

View File

@ -23,16 +23,15 @@ class Time(BasicNewsRecipe):
keep_only_tags = [ keep_only_tags = [
{ {
'class':['tout1', 'entry-content', 'external-gallery-img', 'image-meta'] 'class':['primary-col', 'tout1']
}, },
] ]
remove_tags = [ remove_tags = [
{'class':['thumbnail', 'button']}, {'class':['button', 'entry-sharing group', 'wp-paginate',
'moving-markup', 'entry-comments']},
] ]
extra_css = '.entry-date { padding-left: 2ex }'
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/.*']
preprocess_regexps = [(re.compile( preprocess_regexps = [(re.compile(
r'<meta .+/>'), lambda m:'')] 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.select_form(predicate=lambda f: 'action' in f.attrs and f.attrs['action'] == 'https://auth.time.com/login.php')
br['username'] = self.username br['username'] = self.username
br['password'] = self.password br['password'] = self.password
br['magcode'] = ['TD'] # br['magcode'] = ['TD']
br.find_control('turl').readonly = False br.find_control('turl').readonly = False
br['turl'] = 'http://www.time.com/time/magazine' br['turl'] = 'http://www.time.com/time/magazine'
br.find_control('rurl').readonly = False br.find_control('rurl').readonly = False
@ -104,7 +103,14 @@ class Time(BasicNewsRecipe):
method='text').strip() method='text').strip()
if not title: continue if not title: continue
url = a[0].get('href') 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('/'): if url.startswith('/'):
url = 'http://www.time.com'+url url = 'http://www.time.com'+url
desc = '' desc = ''
@ -112,10 +118,18 @@ class Time(BasicNewsRecipe):
if p: if p:
desc = html.tostring(p[0], encoding=unicode, desc = html.tostring(p[0], encoding=unicode,
method='text') method='text')
self.log('\t', title, ':\n\t\t', desc) self.log('\t', title, ':\n\t\t', url)
yield { yield {
'title' : title, 'title' : title,
'url' : url, 'url' : url,
'date' : '', 'date' : '',
'description' : desc '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

@ -787,12 +787,10 @@ application/x-font-framemaker
application/x-font-ghostscript gsf application/x-font-ghostscript gsf
application/x-font-libgrx application/x-font-libgrx
application/x-font-linux-psf psf application/x-font-linux-psf psf
application/x-font-otf otf
application/x-font-pcf pcf application/x-font-pcf pcf
application/x-font-snf snf application/x-font-snf snf
application/x-font-speedo application/x-font-speedo
application/x-font-sunos-news application/x-font-sunos-news
application/x-font-ttf ttc ttf
application/x-font-type1 afm pfa pfb pfm application/x-font-type1 afm pfa pfb pfm
application/x-font-vfont application/x-font-vfont
application/x-freemind mm application/x-freemind mm
@ -1368,8 +1366,6 @@ text/fb2+xml fb2
text/x-sony-bbeb+xml lrs text/x-sony-bbeb+xml lrs
application/x-sony-bbeb lrf lrx application/x-sony-bbeb lrf lrx
application/adobe-page-template+xml xpgt 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-mobipocket-ebook mobi prc azw
application/x-topaz-ebook tpz azw1 application/x-topaz-ebook tpz azw1
application/x-mobipocket-subscription pobi application/x-mobipocket-subscription pobi
@ -1381,3 +1377,7 @@ application/x-cb7 cb7
application/x-koboreader-ebook kobo application/x-koboreader-ebook kobo
image/wmf wmf image/wmf wmf
application/ereader pdb 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

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

View File

@ -84,6 +84,7 @@ qt_inc = pyqt.qt_inc_dir
qt_lib = pyqt.qt_lib_dir qt_lib = pyqt.qt_lib_dir
ft_lib_dirs = [] ft_lib_dirs = []
ft_libs = [] ft_libs = []
ft_inc_dirs = []
jpg_libs = [] jpg_libs = []
jpg_lib_dirs = [] jpg_lib_dirs = []
fc_inc = '/usr/include/fontconfig' fc_inc = '/usr/include/fontconfig'
@ -94,6 +95,9 @@ chmlib_inc_dirs = chmlib_lib_dirs = []
sqlite_inc_dirs = [] sqlite_inc_dirs = []
icu_inc_dirs = [] icu_inc_dirs = []
icu_lib_dirs = [] icu_lib_dirs = []
zlib_inc_dirs = []
zlib_lib_dirs = []
zlib_libs = ['z']
if iswindows: if iswindows:
prefix = r'C:\cygwin\home\kovid\sw' prefix = r'C:\cygwin\home\kovid\sw'
@ -116,6 +120,10 @@ if iswindows:
jpg_libs = ['jpeg'] jpg_libs = ['jpeg']
ft_lib_dirs = [sw_lib_dir] ft_lib_dirs = [sw_lib_dir]
ft_libs = ['freetype'] 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_inc_dirs = [os.path.join(prefix, 'build', 'ImageMagick-6.7.6')]
magick_lib_dirs = [os.path.join(magick_inc_dirs[0], 'VisualMagick', 'lib')] magick_lib_dirs = [os.path.join(magick_inc_dirs[0], 'VisualMagick', 'lib')]
@ -135,6 +143,8 @@ elif isosx:
png_inc_dirs = consolidate('PNG_INC_DIR', '/sw/include') png_inc_dirs = consolidate('PNG_INC_DIR', '/sw/include')
png_lib_dirs = consolidate('PNG_LIB_DIR', '/sw/lib') png_lib_dirs = consolidate('PNG_LIB_DIR', '/sw/lib')
png_libs = ['png12'] png_libs = ['png12']
ft_libs = ['freetype']
ft_inc_dirs = ['/sw/include/freetype2']
else: else:
# Include directories # Include directories
png_inc_dirs = pkgconfig_include_dirs('libpng', 'PNG_INC_DIR', png_inc_dirs = pkgconfig_include_dirs('libpng', 'PNG_INC_DIR',
@ -150,6 +160,10 @@ else:
if not magick_libs: if not magick_libs:
magick_libs = ['MagickWand', 'MagickCore'] magick_libs = ['MagickWand', 'MagickCore']
png_libs = ['png'] 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_inc = os.environ.get('FC_INC_DIR', fc_inc)

View File

@ -17,7 +17,8 @@ from setup.build_environment import (fc_inc, fc_lib, chmlib_inc_dirs, fc_error,
podofo_inc, podofo_lib, podofo_error, pyqt, OSX_SDK, NMAKE, QMAKE, 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, 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, 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)
MT MT
isunix = islinux or isosx or isbsd isunix = islinux or isosx or isbsd
@ -50,10 +51,6 @@ class Extension(object):
reflow_sources = glob.glob(os.path.join(SRC, 'calibre', 'ebooks', 'pdf', '*.cpp')) reflow_sources = glob.glob(os.path.join(SRC, 'calibre', 'ebooks', 'pdf', '*.cpp'))
reflow_headers = glob.glob(os.path.join(SRC, 'calibre', 'ebooks', 'pdf', '*.h')) 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_libs = ['icudata', 'icui18n', 'icuuc', 'icuio']
icu_cflags = [] icu_cflags = []
if iswindows: if iswindows:
@ -119,6 +116,12 @@ extensions = [
'calibre/utils/lzx/mspack.h'], 'calibre/utils/lzx/mspack.h'],
inc_dirs=['calibre/utils/lzx']), inc_dirs=['calibre/utils/lzx']),
Extension('freetype',
['calibre/utils/fonts/freetype.cpp'],
inc_dirs = ft_inc_dirs,
libraries=ft_libs,
lib_dirs=ft_lib_dirs),
Extension('fontconfig', Extension('fontconfig',
['calibre/utils/fonts/fontconfig.c'], ['calibre/utils/fonts/fontconfig.c'],
inc_dirs = [fc_inc], inc_dirs = [fc_inc],
@ -126,6 +129,18 @@ extensions = [
lib_dirs=[fc_lib], lib_dirs=[fc_lib],
error=fc_error), error=fc_error),
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', Extension('msdes',
['calibre/utils/msdes/msdesmodule.c', ['calibre/utils/msdes/msdesmodule.c',
'calibre/utils/msdes/des.c'], 'calibre/utils/msdes/des.c'],

View File

@ -329,6 +329,7 @@ def get_parsed_proxy(typ='http', debug=True):
ans['port'] = int(ans['port']) ans['port'] = int(ans['port'])
except: except:
if debug: if debug:
import traceback
traceback.print_exc() traceback.print_exc()
else: else:
if debug: if debug:
@ -689,26 +690,18 @@ def remove_bracketed_text(src,
buf.append(char) buf.append(char)
return u''.join(buf) 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(): def load_builtin_fonts():
import glob if iswindows or isosx:
from PyQt4.Qt import QFontDatabase import glob
base = P('fonts/liberation/*.ttf') from PyQt4.Qt import QFontDatabase
for f in glob.glob(base): for f in glob.glob(P('fonts/liberation/*.ttf')):
QFontDatabase.addApplicationFont(f) QFontDatabase.addApplicationFont(f)
else:
# On linux these are loaded by fontconfig which means that
# they are available to Qt as well, since Qt uses fontconfig
from calibre.utils.fonts import fontconfig
fontconfig
return 'Liberation Serif', 'Liberation Sans', 'Liberation Mono' return 'Liberation Serif', 'Liberation Sans', 'Liberation Mono'

View File

@ -89,6 +89,8 @@ class Plugins(collections.Mapping):
'chm_extra', 'chm_extra',
'icu', 'icu',
'speedup', 'speedup',
'freetype',
'woff',
] ]
if iswindows: if iswindows:
plugins.extend(['winutil', 'wpd', 'winfonts']) plugins.extend(['winutil', 'wpd', 'winfonts'])

View File

@ -178,18 +178,41 @@ def normalize(x):
def calibre_cover(title, author_string, series_string=None, def calibre_cover(title, author_string, series_string=None,
output_format='jpg', title_size=46, author_size=36, logo_path=None): output_format='jpg', title_size=46, author_size=36, logo_path=None):
from calibre.utils.config_base import tweaks
title = normalize(title) title = normalize(title)
author_string = normalize(author_string) author_string = normalize(author_string)
series_string = normalize(series_string) series_string = normalize(series_string)
from calibre.utils.magick.draw import create_cover_page, TextLine 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: 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: if logo_path is None:
logo_path = I('library.png') logo_path = I('library.png')
return create_cover_page(lines, logo_path, output_format='jpg', try:
return create_cover_page(lines, logo_path, output_format='jpg',
texture_opacity=0.3, texture_data=I('cover_texture.png', texture_opacity=0.3, texture_data=I('cover_texture.png',
data=True)) 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)$') UNIT_RE = re.compile(r'^(-*[0-9]*[.]?[0-9]*)\s*(%|em|ex|en|px|mm|cm|in|pt|pc)$')

View File

@ -132,7 +132,7 @@ def add_pipeline_options(parser, plumber):
_('Options to control the look and feel of the output'), _('Options to control the look and feel of the output'),
[ [
'base_font_size', 'disable_font_rescaling', 'base_font_size', 'disable_font_rescaling',
'font_size_mapping', 'font_size_mapping', 'embed_font_family',
'line_height', 'minimum_line_height', 'line_height', 'minimum_line_height',
'linearize_tables', 'linearize_tables',
'extra_css', 'filter_css', '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', OptionRecommendation(name='linearize_tables',
recommended_value=False, level=OptionRecommendation.LOW, recommended_value=False, level=OptionRecommendation.LOW,
help=_('Some badly designed documents use tables to control the ' help=_('Some badly designed documents use tables to control the '

View File

@ -379,6 +379,10 @@ def set_metadata(stream, mi, apply_null=False, update_timestamp=False):
stream.seek(0) stream.seek(0)
stream.truncate() 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', stream.write(etree.tostring(root, method='xml', encoding='utf-8',
xml_declaration=True)) xml_declaration=False))

View File

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

View File

@ -126,6 +126,17 @@ class CaseInsensitiveAttributesTranslator(HTMLTranslator):
ci_css_to_xpath = CaseInsensitiveAttributesTranslator().css_to_xpath 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): class CSSSelector(object):
def __init__(self, css, log=None, namespaces=XPNSMAP): 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): def build_selector(self, css, log, func=css_to_xpath):
try: try:
return etree.XPath(func(css), namespaces=self.namespaces) return etree.XPath(fix_namespace(func(css)), namespaces=self.namespaces)
except: except:
if log is not None: if log is not None:
log.exception('Failed to parse CSS selector: %r'%css) log.exception('Failed to parse CSS selector: %r'%css)

View File

@ -14,9 +14,11 @@ from lxml import etree
import cssutils import cssutils
from cssutils.css import Property from cssutils.css import Property
from calibre import guess_type
from calibre.ebooks.oeb.base import (XHTML, XHTML_NS, CSS_MIME, OEB_STYLES, from calibre.ebooks.oeb.base import (XHTML, XHTML_NS, CSS_MIME, OEB_STYLES,
namespace, barename, XPath) namespace, barename, XPath)
from calibre.ebooks.oeb.stylizer import Stylizer from calibre.ebooks.oeb.stylizer import Stylizer
from calibre.utils.filenames import ascii_filename
COLLAPSE = re.compile(r'[ \t\r\n\v]+') COLLAPSE = re.compile(r'[ \t\r\n\v]+')
STRIPNUM = re.compile(r'[-0-9]+$') STRIPNUM = re.compile(r'[-0-9]+$')
@ -144,11 +146,61 @@ class CSSFlattener(object):
cssutils.replaceUrls(item.data, item.abshref, cssutils.replaceUrls(item.data, item.abshref,
ignoreImportRules=True) ignoreImportRules=True)
self.body_font_family, self.embed_font_rules = self.get_embed_font_info(
self.opts.embed_font_family)
self.stylize_spine() self.stylize_spine()
self.sbase = self.baseline_spine() if self.fbase else None self.sbase = self.baseline_spine() if self.fbase else None
self.fmap = FontMapper(self.sbase, self.fbase, self.fkey) self.fmap = FontMapper(self.sbase, self.fbase, self.fkey)
self.flatten_spine() 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 import fontconfig
from calibre.utils.fonts.utils import (get_font_characteristics,
panose_to_css_generic_family, get_font_names)
faces = fontconfig.fonts_for_family(family)
if not faces or not u'normal' in 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 k, v in faces.iteritems():
ext, data = v[0::2]
weight, is_italic, is_bold, is_regular, fs_type, panose = \
get_font_characteristics(data)
generic_family = panose_to_css_generic_family(panose)
family_name, subfamily_name, full_name = get_font_names(data)
if k == u'normal':
body_font_family = u"'%s',%s"%(family_name, generic_family)
if family_name.lower() != family.lower():
self.oeb.log.warn(u'Failed to find an exact match for font:'
u' %r, using %r instead'%(family, family_name))
else:
self.oeb.log(u'Embedding font: %s'%family_name)
font = {u'font-family':u'"%s"'%family_name}
if is_italic:
font[u'font-style'] = u'italic'
if is_bold:
font[u'font-weight'] = u'bold'
fid, href = self.oeb.manifest.generate(id=u'font',
href=u'%s.%s'%(ascii_filename(full_name).replace(u' ', u'-'), ext))
item = self.oeb.manifest.add(fid, href,
guess_type(full_name+'.'+ext)[0],
data=data)
item.unload_data_from_memory()
font[u'src'] = u'url(%s)'%item.href
rule = '@font-face { %s }'%('; '.join(u'%s:%s'%(k, v) for k, v in
font.iteritems()))
rule = cssutils.parseString(rule)
efi.append(rule)
return body_font_family, efi
def stylize_spine(self): def stylize_spine(self):
self.stylizers = {} self.stylizers = {}
profile = self.context.source profile = self.context.source
@ -170,6 +222,8 @@ class CSSFlattener(object):
bs.extend(['page-break-before: always']) bs.extend(['page-break-before: always'])
if self.context.change_justification != 'original': if self.context.change_justification != 'original':
bs.append('text-align: '+ self.context.change_justification) 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)) body.set('style', '; '.join(bs))
stylizer = Stylizer(html, item.href, self.oeb, self.context, profile, stylizer = Stylizer(html, item.href, self.oeb, self.context, profile,
user_css=self.context.extra_css, user_css=self.context.extra_css,
@ -450,7 +504,8 @@ class CSSFlattener(object):
items.sort() items.sort()
css = ';\n'.join("%s: %s" % (key, val) for key, val in items) css = ';\n'.join("%s: %s" % (key, val) for key, val in items)
css = ('@page {\n%s\n}\n'%css) if items else '' 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) raw = '\n\n'.join(rules)
css += '\n\n' + raw css += '\n\n' + raw
global_css[css].append(item) global_css[css].append(item)

View File

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

View File

@ -12,7 +12,7 @@ from PyQt4.Qt import (QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt,
ORG_NAME = 'KovidsBrain' ORG_NAME = 'KovidsBrain'
APP_UID = 'libprs500' APP_UID = 'libprs500'
from calibre import prints from calibre import prints, load_builtin_fonts
from calibre.constants import (islinux, iswindows, isbsd, isfrozen, isosx, from calibre.constants import (islinux, iswindows, isbsd, isfrozen, isosx,
plugins, config_dir, filesystem_encoding, DEBUG) plugins, config_dir, filesystem_encoding, DEBUG)
from calibre.utils.config import Config, ConfigProxy, dynamic, JSONConfig from calibre.utils.config import Config, ConfigProxy, dynamic, JSONConfig
@ -779,6 +779,7 @@ class Application(QApplication):
qt_app = self qt_app = self
self._file_open_paths = [] self._file_open_paths = []
self._file_open_lock = RLock() self._file_open_lock = RLock()
load_builtin_fonts()
self.setup_styles(force_calibre_style) self.setup_styles(force_calibre_style)
if DEBUG: if DEBUG:

View File

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

View File

@ -7,27 +7,53 @@
<x>0</x> <x>0</x>
<y>0</y> <y>0</y>
<width>655</width> <width>655</width>
<height>522</height> <height>619</height>
</rect> </rect>
</property> </property>
<property name="windowTitle"> <property name="windowTitle">
<string>Form</string> <string>Form</string>
</property> </property>
<layout class="QGridLayout" name="gridLayout"> <layout class="QGridLayout" name="gridLayout">
<item row="0" column="0"> <item row="3" column="4">
<widget class="QCheckBox" name="opt_disable_font_rescaling"> <widget class="QDoubleSpinBox" name="opt_line_height">
<property name="text"> <property name="suffix">
<string>&amp;Disable font size rescaling</string> <string> pt</string>
</property>
<property name="decimals">
<number>1</number>
</property> </property>
</widget> </widget>
</item> </item>
<item row="1" column="0"> <item row="3" column="3">
<widget class="QLabel" name="label_18"> <widget class="QLabel" name="label">
<property name="text"> <property name="text">
<string>Base &amp;font size:</string> <string>Line &amp;height:</string>
</property> </property>
<property name="buddy"> <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> </property>
</widget> </widget>
</item> </item>
@ -97,49 +123,6 @@
</item> </item>
</layout> </layout>
</item> </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"> <item row="5" column="0">
<widget class="QLabel" name="label_3"> <widget class="QLabel" name="label_3">
<property name="text"> <property name="text">
@ -157,14 +140,14 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="0" colspan="2"> <item row="7" column="0" colspan="2">
<widget class="QCheckBox" name="opt_remove_paragraph_spacing"> <widget class="QCheckBox" name="opt_remove_paragraph_spacing">
<property name="text"> <property name="text">
<string>Remove &amp;spacing between paragraphs</string> <string>Remove &amp;spacing between paragraphs</string>
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="3"> <item row="7" column="3">
<widget class="QLabel" name="label_4"> <widget class="QLabel" name="label_4">
<property name="text"> <property name="text">
<string>&amp;Indent size:</string> <string>&amp;Indent size:</string>
@ -177,7 +160,7 @@
</property> </property>
</widget> </widget>
</item> </item>
<item row="6" column="4"> <item row="7" column="4">
<widget class="QDoubleSpinBox" name="opt_remove_paragraph_spacing_indent_size"> <widget class="QDoubleSpinBox" name="opt_remove_paragraph_spacing_indent_size">
<property name="toolTip"> <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> <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> </property>
</widget> </widget>
</item> </item>
<item row="7" column="0" colspan="2"> <item row="12" column="0" colspan="5">
<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">
<widget class="QTabWidget" name="tabWidget"> <widget class="QTabWidget" name="tabWidget">
<property name="currentIndex"> <property name="currentIndex">
<number>0</number> <number>0</number>
@ -378,10 +283,131 @@
</item> </item>
</layout> </layout>
</item> </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> </layout>
</widget> </widget>
</widget> </widget>
</item> </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> </layout>
</widget> </widget>
<customwidgets> <customwidgets>
@ -390,6 +416,11 @@
<extends>QComboBox</extends> <extends>QComboBox</extends>
<header>widgets.h</header> <header>widgets.h</header>
</customwidget> </customwidget>
<customwidget>
<class>FontFamilyChooser</class>
<extends>QComboBox</extends>
<header>calibre/gui2/font_family_chooser.h</header>
</customwidget>
</customwidgets> </customwidgets>
<resources> <resources>
<include location="../../../../resources/images.qrc"/> <include location="../../../../resources/images.qrc"/>

View File

@ -211,6 +211,9 @@ class RegexEdit(QWidget, Ui_Edit):
self.button.clicked.connect(self.builder) self.button.clicked.connect(self.builder)
def builder(self): 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) bld = RegexBuilder(self.db, self.book_id, self.edit.text(), self.doc_cache, self)
if bld.cancelled: if bld.cancelled:
return return

View File

@ -55,6 +55,12 @@ def writing_system_for_font(font):
class FontFamilyDelegate(QStyledItemDelegate): class FontFamilyDelegate(QStyledItemDelegate):
def sizeHint(self, option, index): 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() text = index.data(Qt.DisplayRole).toString()
font = QFont(option.font) font = QFont(option.font)
font.setPointSize(QFontInfo(font).pointSize() * 1.5) font.setPointSize(QFontInfo(font).pointSize() * 1.5)
@ -62,6 +68,14 @@ class FontFamilyDelegate(QStyledItemDelegate):
return QSize(m.width(text), m.height()) return QSize(m.width(text), m.height())
def paint(self, painter, option, index): def paint(self, painter, option, index):
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()) text = unicode(index.data(Qt.DisplayRole).toString())
font = QFont(option.font) font = QFont(option.font)
font.setPointSize(QFontInfo(font).pointSize() * 1.5) font.setPointSize(QFontInfo(font).pointSize() * 1.5)
@ -75,7 +89,6 @@ class FontFamilyDelegate(QStyledItemDelegate):
r = option.rect r = option.rect
if option.state & QStyle.State_Selected: if option.state & QStyle.State_Selected:
painter.save()
painter.setBrush(option.palette.highlight()) painter.setBrush(option.palette.highlight())
painter.setPen(Qt.NoPen) painter.setPen(Qt.NoPen)
painter.drawRect(option.rect) painter.drawRect(option.rect)
@ -102,9 +115,6 @@ class FontFamilyDelegate(QStyledItemDelegate):
painter.setFont(old) painter.setFont(old)
if (option.state & QStyle.State_Selected):
painter.restore()
class FontFamilyChooser(QComboBox): class FontFamilyChooser(QComboBox):
def __init__(self, parent=None): def __init__(self, parent=None):
@ -138,7 +148,10 @@ class FontFamilyChooser(QComboBox):
def sizeHint(self): def sizeHint(self):
ans = QComboBox.sizeHint(self) ans = QComboBox.sizeHint(self)
ans.setWidth(QFontMetrics(self.font()).width('m'*14)) try:
ans.setWidth(QFontMetrics(self.font()).width('m'*14))
except:
pass
return ans return ans
@dynamic_property @dynamic_property
@ -157,12 +170,15 @@ class FontFamilyChooser(QComboBox):
self.setCurrentIndex(idx) self.setCurrentIndex(idx)
return property(fget=fget, fset=fset) return property(fget=fget, fset=fset)
def test():
if __name__ == '__main__':
app = QApplication([]) app = QApplication([])
app
d = QDialog() d = QDialog()
d.setLayout(QVBoxLayout()) d.setLayout(QVBoxLayout())
d.layout().addWidget(FontFamilyChooser(d)) d.layout().addWidget(FontFamilyChooser(d))
d.layout().addWidget(QFontComboBox(d)) d.layout().addWidget(QFontComboBox(d))
d.exec_() d.exec_()
if __name__ == '__main__':
test()

View File

@ -1368,6 +1368,8 @@ class DeviceBooksModel(BooksModel): # {{{
return QVariant(authors_to_string(au)) return QVariant(authors_to_string(au))
elif cname == 'size': elif cname == 'size':
size = self.db[self.map[row]].size size = self.db[self.map[row]].size
if not isinstance(size, (float, int)):
size = 0
return QVariant(human_readable(size)) return QVariant(human_readable(size))
elif cname == 'timestamp': elif cname == 'timestamp':
dt = self.db[self.map[row]].datetime dt = self.db[self.map[row]].datetime

View File

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

View File

@ -270,7 +270,7 @@ class AuthorsEdit(EditWithComplete):
import traceback import traceback
fname = err.filename if err.filename else 'file' fname = err.filename if err.filename else 'file'
error_dialog(self, _('Permission denied'), error_dialog(self, _('Permission denied'),
_('Could not open %s. Is it being used by another' _('Could not open "%s". Is it being used by another'
' program?')%fname, det_msg=traceback.format_exc(), ' program?')%fname, det_msg=traceback.format_exc(),
show=True) show=True)
return False return False

View File

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

View File

@ -16,7 +16,7 @@ from PyQt4.QtWebKit import QWebPage, QWebView, QWebSettings
from calibre.gui2.viewer.flip import SlideFlip from calibre.gui2.viewer.flip import SlideFlip
from calibre.gui2.shortcuts import Shortcuts 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.customize.ui import all_viewer_plugins
from calibre.gui2.viewer.keys import SHORTCUTS from calibre.gui2.viewer.keys import SHORTCUTS
from calibre.gui2.viewer.javascript import JavaScriptLoader from calibre.gui2.viewer.javascript import JavaScriptLoader
@ -86,7 +86,6 @@ class Document(QWebPage): # {{{
settings = self.settings() settings = self.settings()
# Fonts # Fonts
load_builtin_fonts()
self.all_viewer_plugins = tuple(all_viewer_plugins()) self.all_viewer_plugins = tuple(all_viewer_plugins())
for pl in self.all_viewer_plugins: for pl in self.all_viewer_plugins:
pl.load_fonts() pl.load_fonts()

View File

@ -693,16 +693,16 @@ datatype is one of: {0}
'the data in this column will be interpreted. This is a JSON ' 'the data in this column will be interpreted. This is a JSON '
' string. For enumeration columns, use ' ' string. For enumeration columns, use '
'--display="{\\"enum_values\\":[\\"val1\\", \\"val2\\"]}"' '--display="{\\"enum_values\\":[\\"val1\\", \\"val2\\"]}"'
'' '\n'
'There are many options that can go into the display variable.' 'There are many options that can go into the display variable.'
'The options by column type are:' 'The options by column type are:\n'
'composite: composite_template, composite_sort, make_category,' 'composite: composite_template, composite_sort, make_category,'
' contains_html, use_decorations' 'contains_html, use_decorations\n'
'datetime: date_format' 'datetime: date_format\n'
'enumeration: enum_values, enum_colors, use_decorations' 'enumeration: enum_values, enum_colors, use_decorations\n'
'int, float: number_format' 'int, float: number_format\n'
'text: is_names. use_decorations' 'text: is_names, use_decorations\n'
'' '\n'
'The best way to find legal combinations is to create a custom' 'The best way to find legal combinations is to create a custom'
'column of the appropriate type in the GUI then look at the' 'column of the appropriate type in the GUI then look at the'
'backup OPF for a book (ensure that a new OPF has been created' 'backup OPF for a book (ensure that a new OPF has been created'
@ -720,7 +720,6 @@ def command_add_custom_column(args, dbpath):
print print
print >>sys.stderr, _('You must specify label, name and datatype') print >>sys.stderr, _('You must specify label, name and datatype')
return 1 return 1
print opts.display
do_add_custom_column(get_db(dbpath, opts), args[0], args[1], args[2], do_add_custom_column(get_db(dbpath, opts), args[0], args[1], args[2],
opts.is_multiple, json.loads(opts.display)) opts.is_multiple, json.loads(opts.display))
# Re-open the DB so that field_metadata is reflects the column changes # Re-open the DB so that field_metadata is reflects the column changes

View File

@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en'
The database used to store ebook metadata The database used to store ebook metadata
''' '''
import os, sys, shutil, cStringIO, glob, time, functools, traceback, re, \ import os, sys, shutil, cStringIO, glob, time, functools, traceback, re, \
json, uuid, hashlib, copy json, uuid, hashlib, copy, errno
from collections import defaultdict from collections import defaultdict
import threading, random import threading, random
from itertools import repeat from itertools import repeat
@ -30,7 +30,8 @@ from calibre.ptempfile import (PersistentTemporaryFile,
base_dir, SpooledTemporaryFile) base_dir, SpooledTemporaryFile)
from calibre.customize.ui import run_plugins_on_import from calibre.customize.ui import run_plugins_on_import
from calibre import isbytestring from calibre import isbytestring
from calibre.utils.filenames import ascii_filename, samefile from calibre.utils.filenames import (ascii_filename, samefile,
windows_is_folder_in_use)
from calibre.utils.date import (utcnow, now as nowf, utcfromtimestamp, from calibre.utils.date import (utcnow, now as nowf, utcfromtimestamp,
parse_only_date, UNDEFINED_DATE) parse_only_date, UNDEFINED_DATE)
from calibre.utils.config import prefs, tweaks, from_json, to_json from calibre.utils.config import prefs, tweaks, from_json, to_json
@ -649,6 +650,13 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
spath = os.path.join(self.library_path, *current_path.split('/')) spath = os.path.join(self.library_path, *current_path.split('/'))
if current_path and os.path.exists(spath): # Migrate existing files if current_path and os.path.exists(spath): # Migrate existing files
if iswindows:
uf = windows_is_folder_in_use(spath)
if uf is not None:
err = IOError(errno.EACCES,
_('File is open in another process'))
err.filename = uf
raise err
cdata = self.cover(id, index_is_id=True) cdata = self.cover(id, index_is_id=True)
if cdata is not None: if cdata is not None:
with lopen(os.path.join(tpath, 'cover.jpg'), 'wb') as f: with lopen(os.path.join(tpath, 'cover.jpg'), 'wb') as f:

View File

@ -32,6 +32,11 @@ def test_lxml():
else: else:
raise RuntimeError('lxml failed') raise RuntimeError('lxml failed')
def test_freetype():
from calibre.utils.fonts.free_type import test
test()
print ('FreeType OK!')
def test_fontconfig(): def test_fontconfig():
from calibre.utils.fonts import fontconfig from calibre.utils.fonts import fontconfig
families = fontconfig.find_font_families() families = fontconfig.find_font_families()
@ -103,21 +108,28 @@ def test_icu():
def test_wpd(): def test_wpd():
wpd = plugins['wpd'][0] wpd = plugins['wpd'][0]
try: try:
wpd.init() wpd.init('calibre', 1, 1, 1)
except wpd.NoWPD: except wpd.NoWPD:
print ('This computer does not have WPD') print ('This computer does not have WPD')
else: else:
wpd.uninit() wpd.uninit()
def test_woff():
from calibre.utils.fonts.woff import test
test()
print ('WOFF ok!')
def test(): def test():
test_plugins() test_plugins()
test_lxml() test_lxml()
test_freetype()
test_fontconfig() test_fontconfig()
test_sqlite() test_sqlite()
test_qt() test_qt()
test_imaging() test_imaging()
test_unrar() test_unrar()
test_icu() test_icu()
test_woff()
if iswindows: if iswindows:
test_win32() test_win32()
test_winutil() test_winutil()

View File

@ -249,4 +249,32 @@ def samefile(src, dst):
os.path.normcase(os.path.abspath(dst))) os.path.normcase(os.path.abspath(dst)))
return samestring return samestring
def windows_is_file_opened(path):
import win32file, winerror
from pywintypes import error
if isbytestring(path): path = path.decode(filesystem_encoding)
try:
h = win32file.CreateFile(path, win32file.GENERIC_READ, 0, None,
win32file.OPEN_EXISTING, 0, 0)
except error as e:
if getattr(e, 'winerror', 0) == winerror.ERROR_SHARING_VIOLATION:
return True
else:
win32file.CloseHandle(h)
return False
def windows_is_folder_in_use(path):
'''
Returns the path to a file that is used in another process in the specified
folder, or None if no such file exists. Note
that this function is not a guarantee. A file may well be opened in the
folder after this function returns. However, it is useful to handle the
common case of a sharing violation gracefully most of the time.
'''
if isbytestring(path): path = path.decode(filesystem_encoding)
for x in os.listdir(path):
f = os.path.join(path, x)
if windows_is_file_opened(f):
return f
return None

View File

@ -55,19 +55,65 @@ class Fonts(object):
files = self.backend.files_for_family(family, normalize=normalize) files = self.backend.files_for_family(family, normalize=normalize)
ans = {} ans = {}
for ft, val in files.iteritems(): for ft, val in files.iteritems():
name, f = val f, name = val
ext = f.rpartition('.')[-1].lower() ext = f.rpartition('.')[-1].lower()
ans[ft] = (ext, name, open(f, 'rb').read()) ans[ft] = (ext, name, open(f, 'rb').read())
return ans return ans
def find_font_for_text(self, text, allowed_families={'serif', 'sans-serif'},
preferred_families=('serif', 'sans-serif', 'monospace', 'cursive', 'fantasy')):
'''
Find a font on the system capable of rendering the given text.
Returns a font family (as given by fonts_for_family()) that has a
"normal" font and that can render the supplied text. If no such font
exists, returns None.
:return: (family name, faces) or None, None
'''
from calibre.utils.fonts.free_type import FreeType, get_printable_characters, FreeTypeError
from calibre.utils.fonts.utils import panose_to_css_generic_family, get_font_characteristics
ft = FreeType()
found = {}
if not isinstance(text, unicode):
raise TypeError(u'%r is not unicode'%text)
text = get_printable_characters(text)
def filter_faces(faces):
ans = {}
for k, v in faces.iteritems():
try:
font = ft.load_font(v[2])
except FreeTypeError:
continue
if font.supports_text(text, has_non_printable_chars=False):
ans[k] = v
return ans
for family in sorted(self.find_font_families()):
faces = filter_faces(self.fonts_for_family(family))
if 'normal' not in faces:
continue
panose = get_font_characteristics(faces['normal'][2])[5]
generic_family = panose_to_css_generic_family(panose)
if generic_family in allowed_families or generic_family == preferred_families[0]:
return (family, faces)
elif generic_family not in found:
found[generic_family] = (family, faces)
for f in preferred_families:
if f in found:
return found[f]
return None, None
fontconfig = Fonts() fontconfig = Fonts()
def test(): def test():
import os import os
print(fontconfig.find_font_families()) print(fontconfig.find_font_families())
m = 'times new roman' if iswindows else 'liberation serif' m = 'Liberation Serif'
for ft, val in fontconfig.files_for_family(m).iteritems(): for ft, val in fontconfig.files_for_family(m).iteritems():
print val[0], ft, val[1], os.path.getsize(val[1]) print val[0], ft, val[1], os.path.getsize(val[0])
if __name__ == '__main__': if __name__ == '__main__':
test() test()

View File

@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
import os, sys import os, sys
from calibre.constants import plugins, iswindows, islinux, isbsd from calibre.constants import plugins, islinux, isbsd
_fc, _fc_err = plugins['fontconfig'] _fc, _fc_err = plugins['fontconfig']
@ -35,18 +35,14 @@ class FontConfig(Thread):
if isinstance(config_dir, unicode): if isinstance(config_dir, unicode):
config_dir = config_dir.encode(sys.getfilesystemencoding()) config_dir = config_dir.encode(sys.getfilesystemencoding())
config = os.path.join(config_dir, 'fonts.conf') config = os.path.join(config_dir, 'fonts.conf')
if iswindows and getattr(sys, 'frozen', False):
config_dir = os.path.join(os.path.dirname(sys.executable),
'fontconfig')
if isinstance(config_dir, unicode):
config_dir = config_dir.encode(sys.getfilesystemencoding())
config = os.path.join(config_dir, 'fonts.conf')
try: try:
_fc.initialize(config) _fc.initialize(config)
except: except:
import traceback import traceback
traceback.print_exc() traceback.print_exc()
self.failed = True self.failed = True
if not self.failed and hasattr(_fc, 'add_font_dir'):
_fc.add_font_dir(P('fonts/liberation'))
def wait(self): def wait(self):
if not (islinux or isbsd): if not (islinux or isbsd):
@ -162,7 +158,7 @@ def test():
from pprint import pprint; from pprint import pprint;
pprint(fontconfig.find_font_families()) pprint(fontconfig.find_font_families())
pprint(fontconfig.files_for_family('liberation serif')) pprint(fontconfig.files_for_family('liberation serif'))
m = 'times new roman' if iswindows else 'liberation serif' m = 'liberation serif'
pprint(fontconfig.match(m+':slant=italic:weight=bold', verbose=True)) pprint(fontconfig.match(m+':slant=italic:weight=bold', verbose=True))
if __name__ == '__main__': if __name__ == '__main__':

View File

@ -44,6 +44,17 @@ fontconfig_initialize(PyObject *self, PyObject *args) {
Py_RETURN_FALSE; Py_RETURN_FALSE;
} }
static PyObject*
fontconfig_add_font_dir(PyObject *self, PyObject *args) {
FcChar8 *path;
if (!PyArg_ParseTuple(args, "s", &path)) return NULL;
if (FcConfigAppFontAddDir(NULL, path))
Py_RETURN_TRUE;
Py_RETURN_FALSE;
}
static void static void
fontconfig_cleanup_find(FcPattern *p, FcObjectSet *oset, FcFontSet *fs) { fontconfig_cleanup_find(FcPattern *p, FcObjectSet *oset, FcFontSet *fs) {
if (p != NULL) FcPatternDestroy(p); if (p != NULL) FcPatternDestroy(p);
@ -314,6 +325,11 @@ PyMethodDef fontconfig_methods[] = {
}, },
{"add_font_dir", fontconfig_add_font_dir, METH_VARARGS,
"add_font_dir(path_to_dir)\n\n"
"Add the fonts in the specified directory to the list of application specific fonts."
},
{NULL, NULL, 0, NULL} {NULL, NULL, 0, NULL}
}; };

View File

@ -0,0 +1,98 @@
#!/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 threading, unicodedata
from functools import wraps
from future_builtins import map
from calibre.constants import plugins
class ThreadingViolation(Exception):
def __init__(self):
Exception.__init__(self,
'You cannot use the MTP driver from a thread other than the '
' thread in which startup() was called')
def get_printable_characters(text):
return u''.join(x for x in unicodedata.normalize('NFC', text)
if unicodedata.category(x)[0] not in {'C', 'Z', 'M'})
def same_thread(func):
@wraps(func)
def check_thread(self, *args, **kwargs):
if self.start_thread is not threading.current_thread():
raise ThreadingViolation()
return func(self, *args, **kwargs)
return check_thread
FreeTypeError = getattr(plugins['freetype'][0], 'FreeTypeError', Exception)
class Face(object):
def __init__(self, face):
self.start_thread = threading.current_thread()
self.face = face
for x in ('family_name', 'style_name'):
val = getattr(self.face, x)
try:
val = val.decode('utf-8')
except UnicodeDecodeError:
val = repr(val).decode('utf-8')
setattr(self, x, val)
@same_thread
def supports_text(self, text, has_non_printable_chars=True):
'''
Returns True if all the characters in text have glyphs in this font.
'''
if not isinstance(text, unicode):
raise TypeError('%r is not a unicode object'%text)
if has_non_printable_chars:
text = get_printable_characters(text)
chars = tuple(frozenset(map(ord, text)))
return self.face.supports_text(chars)
class FreeType(object):
def __init__(self):
self.start_thread = threading.current_thread()
ft, ft_err = plugins['freetype']
if ft_err:
raise RuntimeError('Failed to load FreeType module with error: %s'
% ft_err)
self.ft = ft.FreeType()
@same_thread
def load_font(self, data):
return Face(self.ft.load_font(data))
def test():
data = P('fonts/calibreSymbols.otf', data=True)
ft = FreeType()
font = ft.load_font(data)
if not font.supports_text('.\u2605'):
raise RuntimeError('Incorrectly returning that text is not supported')
if font.supports_text('abc'):
raise RuntimeError('Incorrectly claiming that text is supported')
def test_find_font():
from calibre.utils.fonts import fontconfig
abcd = '诶比西迪'
family = fontconfig.find_font_for_text(abcd)[0]
print ('Family for Chinese text:', family)
family = fontconfig.find_font_for_text(abcd)[0]
abcd = 'لوحة المفاتيح العربية'
print ('Family for Arabic text:', family)
if __name__ == '__main__':
test()
test_find_font()

View File

@ -0,0 +1,304 @@
/*
* freetype.cpp
* Copyright (C) 2012 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#define _UNICODE
#define UNICODE
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include <ft2build.h>
#include FT_FREETYPE_H
static PyObject *FreeTypeError = NULL;
typedef struct {
PyObject_HEAD
FT_Face face;
// Every face must keep a reference to the FreeType library object to
// ensure it is garbage collected before the library object, to prevent
// segfaults.
PyObject *library;
} Face;
typedef struct {
PyObject_HEAD
FT_Library library;
} FreeType;
// Face.__init__() {{{
static void
Face_dealloc(Face* self)
{
if (self->face != NULL) {
Py_BEGIN_ALLOW_THREADS;
FT_Done_Face(self->face);
Py_END_ALLOW_THREADS;
}
self->face = NULL;
Py_DECREF(self->library);
self->library = NULL;
self->ob_type->tp_free((PyObject*)self);
}
static int
Face_init(Face *self, PyObject *args, PyObject *kwds)
{
FT_Error error = 0;
char *data;
Py_ssize_t sz;
PyObject *ft;
if (!PyArg_ParseTuple(args, "Os#", &ft, &data, &sz)) return -1;
self->library = ft;
Py_XINCREF(ft);
Py_BEGIN_ALLOW_THREADS;
error = FT_New_Memory_Face( ( (FreeType*)ft )->library,
(const FT_Byte*)data, (FT_Long)sz, 0, &self->face);
Py_END_ALLOW_THREADS;
if (error) {
self->face = NULL;
if ( error == FT_Err_Unknown_File_Format || error == FT_Err_Invalid_Stream_Operation)
PyErr_SetString(FreeTypeError, "Not a supported font format");
else
PyErr_Format(FreeTypeError, "Failed to initialize the Font with error: 0x%x", error);
return -1;
}
return 0;
}
// }}}
static PyObject *
family_name(Face *self, void *closure) {
return Py_BuildValue("s", self->face->family_name);
}
static PyObject *
style_name(Face *self, void *closure) {
return Py_BuildValue("s", self->face->style_name);
}
static PyObject*
supports_text(Face *self, PyObject *args) {
PyObject *chars, *fast, *ret = Py_True;
Py_ssize_t sz, i;
FT_ULong code;
if (!PyArg_ParseTuple(args, "O", &chars)) return NULL;
fast = PySequence_Fast(chars, "List of chars is not a sequence");
if (fast == NULL) return NULL;
sz = PySequence_Fast_GET_SIZE(fast);
for (i = 0; i < sz; i++) {
code = (FT_ULong)PyNumber_AsSsize_t(PySequence_Fast_GET_ITEM(fast, i), NULL);
if (FT_Get_Char_Index(self->face, code) == 0) {
ret = Py_False;
break;
}
}
Py_DECREF(fast);
Py_XINCREF(ret);
return ret;
}
static PyGetSetDef Face_getsetters[] = {
{(char *)"family_name",
(getter)family_name, NULL,
(char *)"The family name of this font.",
NULL},
{(char *)"style_name",
(getter)style_name, NULL,
(char *)"The style name of this font.",
NULL},
{NULL} /* Sentinel */
};
static PyMethodDef Face_methods[] = {
{"supports_text", (PyCFunction)supports_text, METH_VARARGS,
"supports_text(sequence of unicode character codes) -> Return True iff this font has glyphs for all the specified characters."
},
{NULL} /* Sentinel */
};
// FreeType.__init__() {{{
static void
dealloc(FreeType* self)
{
if (self->library != NULL) {
Py_BEGIN_ALLOW_THREADS;
FT_Done_FreeType(self->library);
Py_END_ALLOW_THREADS;
}
self->library = NULL;
self->ob_type->tp_free((PyObject*)self);
}
static int
init(FreeType *self, PyObject *args, PyObject *kwds)
{
FT_Error error = 0;
Py_BEGIN_ALLOW_THREADS;
error = FT_Init_FreeType(&self->library);
Py_END_ALLOW_THREADS;
if (error) {
self->library = NULL;
PyErr_Format(FreeTypeError, "Failed to initialize the FreeType library with error: %d", error);
return -1;
}
return 0;
}
// }}}
static PyTypeObject FaceType = { // {{{
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
"freetype.Face", /*tp_name*/
sizeof(Face), /*tp_basicsize*/
0, /*tp_itemsize*/
(destructor)Face_dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash */
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
"Face", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
Face_methods, /* tp_methods */
0, /* tp_members */
Face_getsetters, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)Face_init, /* tp_init */
0, /* tp_alloc */
0, /* tp_new */
}; // }}}
static PyObject*
load_font(FreeType *self, PyObject *args) {
PyObject *ret, *arg_list, *bytes;
if (!PyArg_ParseTuple(args, "O", &bytes)) return NULL;
arg_list = Py_BuildValue("OO", self, bytes);
if (arg_list == NULL) return NULL;
ret = PyObject_CallObject((PyObject *) &FaceType, arg_list);
Py_DECREF(arg_list);
return ret;
}
static PyMethodDef FreeType_methods[] = {
{"load_font", (PyCFunction)load_font, METH_VARARGS,
"load_font(bytestring) -> Load a font from font data."
},
{NULL} /* Sentinel */
};
static PyTypeObject FreeTypeType = { // {{{
PyObject_HEAD_INIT(NULL)
0, /*ob_size*/
"freetype.FreeType", /*tp_name*/
sizeof(FreeType), /*tp_basicsize*/
0, /*tp_itemsize*/
(destructor)dealloc, /*tp_dealloc*/
0, /*tp_print*/
0, /*tp_getattr*/
0, /*tp_setattr*/
0, /*tp_compare*/
0, /*tp_repr*/
0, /*tp_as_number*/
0, /*tp_as_sequence*/
0, /*tp_as_mapping*/
0, /*tp_hash */
0, /*tp_call*/
0, /*tp_str*/
0, /*tp_getattro*/
0, /*tp_setattro*/
0, /*tp_as_buffer*/
Py_TPFLAGS_DEFAULT|Py_TPFLAGS_BASETYPE, /*tp_flags*/
"FreeType", /* tp_doc */
0, /* tp_traverse */
0, /* tp_clear */
0, /* tp_richcompare */
0, /* tp_weaklistoffset */
0, /* tp_iter */
0, /* tp_iternext */
FreeType_methods, /* tp_methods */
0, /* tp_members */
0, /* tp_getset */
0, /* tp_base */
0, /* tp_dict */
0, /* tp_descr_get */
0, /* tp_descr_set */
0, /* tp_dictoffset */
(initproc)init, /* tp_init */
0, /* tp_alloc */
0, /* tp_new */
}; // }}}
static
PyMethodDef methods[] = {
{NULL, NULL, 0, NULL}
};
PyMODINIT_FUNC
initfreetype(void) {
PyObject *m;
FreeTypeType.tp_new = PyType_GenericNew;
if (PyType_Ready(&FreeTypeType) < 0)
return;
FaceType.tp_new = PyType_GenericNew;
if (PyType_Ready(&FaceType) < 0)
return;
m = Py_InitModule3(
"freetype", methods,
"FreeType API"
);
if (m == NULL) return;
FreeTypeError = PyErr_NewException((char*)"freetype.FreeTypeError", NULL, NULL);
if (FreeTypeError == NULL) return;
PyModule_AddObject(m, "FreeTypeError", FreeTypeError);
Py_INCREF(&FreeTypeType);
PyModule_AddObject(m, "FreeType", (PyObject *)&FreeTypeType);
PyModule_AddObject(m, "Face", (PyObject *)&FaceType);
}

View File

@ -38,8 +38,8 @@ def get_table(raw, name):
def get_font_characteristics(raw): def get_font_characteristics(raw):
''' '''
Return (weight, is_italic, is_bold, is_regular, fs_type). These values are taken Return (weight, is_italic, is_bold, is_regular, fs_type, panose). These
from the OS/2 table of the font. See values are taken from the OS/2 table of the font. See
http://www.microsoft.com/typography/otspec/os2.htm for details http://www.microsoft.com/typography/otspec/os2.htm for details
''' '''
os2_table = get_table(raw, 'os/2')[0] os2_table = get_table(raw, 'os/2')[0]
@ -54,7 +54,6 @@ def get_font_characteristics(raw):
family_class) = struct.unpack_from(common_fields, os2_table) family_class) = struct.unpack_from(common_fields, os2_table)
offset = struct.calcsize(common_fields) offset = struct.calcsize(common_fields)
panose = struct.unpack_from(b'>10B', os2_table, offset) panose = struct.unpack_from(b'>10B', os2_table, offset)
panose
offset += 10 offset += 10
(range1,) = struct.unpack_from(b'>L', os2_table, offset) (range1,) = struct.unpack_from(b'>L', os2_table, offset)
offset += struct.calcsize(b'>L') offset += struct.calcsize(b'>L')
@ -69,7 +68,21 @@ def get_font_characteristics(raw):
is_italic = (selection & 0b1) != 0 is_italic = (selection & 0b1) != 0
is_bold = (selection & 0b100000) != 0 is_bold = (selection & 0b100000) != 0
is_regular = (selection & 0b1000000) != 0 is_regular = (selection & 0b1000000) != 0
return weight, is_italic, is_bold, is_regular, fs_type return weight, is_italic, is_bold, is_regular, fs_type, panose
def panose_to_css_generic_family(panose):
proportion = panose[3]
if proportion == 9:
return 'monospace'
family_type = panose[0]
if family_type == 3:
return 'cursive'
if family_type == 4:
return 'fantasy'
serif_style = panose[1]
if serif_style in (11, 12, 13):
return 'sans-serif'
return 'serif'
def decode_name_record(recs): def decode_name_record(recs):
''' '''
@ -225,13 +238,33 @@ def remove_embed_restriction(raw):
verify_checksums(raw) verify_checksums(raw)
return raw return raw
def get_font_for_text(text, candidate_font_data=None):
ok = False
if candidate_font_data is not None:
from calibre.utils.fonts.free_type import FreeType, FreeTypeError
ft = FreeType()
try:
font = ft.load_font(candidate_font_data)
ok = font.supports_text(text)
except FreeTypeError:
ok = True
if not ok:
from calibre.utils.fonts import fontconfig
family, faces = fontconfig.find_font_for_text(text)
if family is not None:
f = faces.get('bold', faces['normal'])
candidate_font_data = f[2]
return candidate_font_data
def test(): def test():
import sys, os import sys, os
for f in sys.argv[1:]: for f in sys.argv[1:]:
print (os.path.basename(f)) print (os.path.basename(f))
raw = open(f, 'rb').read() raw = open(f, 'rb').read()
print (get_font_names(raw)) print (get_font_names(raw))
print (get_font_characteristics(raw)) characs = get_font_characteristics(raw)
print (characs)
print (panose_to_css_generic_family(characs[5]))
verify_checksums(raw) verify_checksums(raw)
remove_embed_restriction(raw) remove_embed_restriction(raw)

View File

@ -19,6 +19,23 @@ class WinFonts(object):
def __init__(self, winfonts): def __init__(self, winfonts):
self.w = winfonts self.w = winfonts
# Windows thinks the Liberation font files are not valid, so we use
# this hack to make them available
self.app_font_families = {}
for f in ('Serif', 'Sans', 'Mono'):
base = 'fonts/liberation/Liberation%s-%s.ttf'
self.app_font_families['Liberation %s'%f] = m = {}
for weight, is_italic in product( (self.w.FW_NORMAL, self.w.FW_BOLD), (False, True) ):
name = {(self.w.FW_NORMAL, False):'Regular',
(self.w.FW_NORMAL, True):'Italic',
(self.w.FW_BOLD, False):'Bold',
(self.w.FW_BOLD, True):'BoldItalic'}[(weight,
is_italic)]
m[(weight, is_italic)] = base%(f, name)
# import pprint
# pprint.pprint(self.app_font_families)
def font_families(self): def font_families(self):
names = set() names = set()
@ -30,7 +47,7 @@ class WinFonts(object):
not font['name'].startswith('@') not font['name'].startswith('@')
): ):
names.add(font['name']) names.add(font['name'])
return sorted(names) return sorted(names.union(frozenset(self.app_font_families)))
def get_normalized_name(self, is_italic, weight): def get_normalized_name(self, is_italic, weight):
if is_italic: if is_italic:
@ -43,12 +60,18 @@ class WinFonts(object):
family = type(u'')(family) family = type(u'')(family)
ans = {} ans = {}
for weight, is_italic in product( (self.w.FW_NORMAL, self.w.FW_BOLD), (False, True) ): for weight, is_italic in product( (self.w.FW_NORMAL, self.w.FW_BOLD), (False, True) ):
try: if family in self.app_font_families:
data = self.w.font_data(family, is_italic, weight) m = self.app_font_families[family]
except Exception as e: path = m.get((weight, is_italic), None)
prints('Failed to get font data for font: %s [%s] with error: %s'% if path is None: continue
(family, self.get_normalized_name(is_italic, weight), e)) data = P(path, data=True)
continue else:
try:
data = self.w.font_data(family, is_italic, weight)
except Exception as e:
prints('Failed to get font data for font: %s [%s] with error: %s'%
(family, self.get_normalized_name(is_italic, weight), e))
continue
ok, sig = is_truetype_font(data) ok, sig = is_truetype_font(data)
if not ok: if not ok:
@ -122,7 +145,7 @@ def test_ttf_reading():
get_font_characteristics(raw) get_font_characteristics(raw)
print() print()
if __name__ == '__main__': def test():
base = os.path.abspath(__file__) base = os.path.abspath(__file__)
d = os.path.dirname d = os.path.dirname
pluginsd = os.path.join(d(d(d(base))), 'plugins') pluginsd = os.path.join(d(d(d(base))), 'plugins')
@ -143,3 +166,5 @@ if __name__ == '__main__':
prints(' ', font, data[0], data[1], len(data[2])) prints(' ', font, data[0], data[1], len(data[2]))
print () print ()
if __name__ == '__main__':
test()

View File

@ -0,0 +1,36 @@
#!/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'
from calibre.constants import plugins
def get_woff():
woff, woff_err = plugins['woff']
if woff_err:
raise RuntimeError('Failed to load the WOFF plugin: %s'%woff_err)
return woff
def to_woff(raw):
woff = get_woff()
return woff.to_woff(raw)
def from_woff(raw):
woff = get_woff()
return woff.from_woff(raw)
def test():
sfnt = P('fonts/calibreSymbols.otf', data=True)
woff = to_woff(sfnt)
recon = from_woff(woff)
if recon != sfnt:
raise ValueError('WOFF roundtrip resulted in different sfnt')
if __name__ == '__main__':
test()

View File

@ -0,0 +1,108 @@
/*
* main.c
* Copyright (C) 2012 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#define _UNICODE
#define UNICODE
#define PY_SSIZE_T_CLEAN
#include <Python.h>
#include "woff.h"
static PyObject *WOFFError = NULL;
static PyObject* woff_err(uint32_t status) {
const char *msg;
switch(status) {
case eWOFF_out_of_memory:
return PyErr_NoMemory();
case eWOFF_invalid:
msg = "Invalid input data"; break;
case eWOFF_compression_failure:
msg = "Compression failed"; break;
case eWOFF_bad_signature:
msg = "Bad font signature"; break;
case eWOFF_buffer_too_small:
msg = "Buffer too small"; break;
case eWOFF_bad_parameter:
msg = "Bad parameter"; break;
case eWOFF_illegal_order:
msg = "Illegal order of WOFF chunks"; break;
default:
msg = "Unknown Error";
}
PyErr_SetString(WOFFError, msg);
return NULL;
}
static PyObject*
to_woff(PyObject *self, PyObject *args) {
const char *sfnt;
char *woff = NULL;
Py_ssize_t sz;
uint32_t wofflen = 0, status = eWOFF_ok;
PyObject *ans;
if (!PyArg_ParseTuple(args, "s#", &sfnt, &sz)) return NULL;
woff = (char*)woffEncode((uint8_t*)sfnt, sz, 0, 0, &wofflen, &status);
if (WOFF_FAILURE(status) || woff == NULL) return woff_err(status);
ans = Py_BuildValue("s#", woff, wofflen);
free(woff);
return ans;
}
static PyObject*
from_woff(PyObject *self, PyObject *args) {
const char *woff;
char *sfnt;
Py_ssize_t sz;
uint32_t sfntlen = 0, status = eWOFF_ok;
PyObject *ans;
if (!PyArg_ParseTuple(args, "s#", &woff, &sz)) return NULL;
sfnt = (char*)woffDecode((uint8_t*)woff, sz, &sfntlen, &status);
if (WOFF_FAILURE(status) || sfnt == NULL) return woff_err(status);
ans = Py_BuildValue("s#", sfnt, sfntlen);
free(sfnt);
return ans;
}
static
PyMethodDef methods[] = {
{"to_woff", (PyCFunction)to_woff, METH_VARARGS,
"to_woff(bytestring) -> Convert the sfnt data in bytestring to WOFF format (returned as a bytestring)."
},
{"from_woff", (PyCFunction)from_woff, METH_VARARGS,
"from_woff(bytestring) -> Convert the woff data in bytestring to SFNT format (returned as a bytestring)."
},
{NULL, NULL, 0, NULL}
};
PyMODINIT_FUNC
initwoff(void) {
PyObject *m;
m = Py_InitModule3(
"woff", methods,
"Convert to/from the WOFF<->sfnt font formats"
);
if (m == NULL) return;
WOFFError = PyErr_NewException((char*)"woff.WOFFError", NULL, NULL);
if (WOFFError == NULL) return;
PyModule_AddObject(m, "WOFFError", WOFFError);
}

View File

@ -0,0 +1,151 @@
/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is WOFF font packaging code.
*
* The Initial Developer of the Original Code is Mozilla Corporation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Jonathan Kew <jfkthame@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef WOFF_PRIVATE_H_
#define WOFF_PRIVATE_H_
#include "woff.h"
/* private definitions used in the WOFF encoder/decoder functions */
/* create an OT tag from 4 characters */
#define TAG(a,b,c,d) ((a)<<24 | (b)<<16 | (c)<<8 | (d))
#define WOFF_SIGNATURE TAG('w','O','F','F')
#define SFNT_VERSION_CFF TAG('O','T','T','O')
#define SFNT_VERSION_TT 0x00010000
#define SFNT_VERSION_true TAG('t','r','u','e')
#define TABLE_TAG_DSIG TAG('D','S','I','G')
#define TABLE_TAG_head TAG('h','e','a','d')
#define TABLE_TAG_bhed TAG('b','h','e','d')
#define SFNT_CHECKSUM_CALC_CONST 0xB1B0AFBAU /* from the TT/OT spec */
#ifdef WOFF_MOZILLA_CLIENT
# include <prnetdb.h>
# define READ32BE(x) PR_ntohl(x)
# define READ16BE(x) PR_ntohs(x)
#else
/* These macros to read values as big-endian only work on "real" variables,
not general expressions, because of the use of &(x), but they are
designed to work on both BE and LE machines without the need for a
configure check. For production code, we might want to replace this
with something more efficient. */
/* read a 32-bit BigEndian value */
# define READ32BE(x) ( ( (uint32_t) ((uint8_t*)&(x))[0] << 24 ) + \
( (uint32_t) ((uint8_t*)&(x))[1] << 16 ) + \
( (uint32_t) ((uint8_t*)&(x))[2] << 8 ) + \
(uint32_t) ((uint8_t*)&(x))[3] )
/* read a 16-bit BigEndian value */
# define READ16BE(x) ( ( (uint16_t) ((uint8_t*)&(x))[0] << 8 ) + \
(uint16_t) ((uint8_t*)&(x))[1] )
#endif
#pragma pack(push,1)
typedef struct {
uint32_t version;
uint16_t numTables;
uint16_t searchRange;
uint16_t entrySelector;
uint16_t rangeShift;
} sfntHeader;
typedef struct {
uint32_t tag;
uint32_t checksum;
uint32_t offset;
uint32_t length;
} sfntDirEntry;
typedef struct {
uint32_t signature;
uint32_t flavor;
uint32_t length;
uint16_t numTables;
uint16_t reserved;
uint32_t totalSfntSize;
uint16_t majorVersion;
uint16_t minorVersion;
uint32_t metaOffset;
uint32_t metaCompLen;
uint32_t metaOrigLen;
uint32_t privOffset;
uint32_t privLen;
} woffHeader;
typedef struct {
uint32_t tag;
uint32_t offset;
uint32_t compLen;
uint32_t origLen;
uint32_t checksum;
} woffDirEntry;
typedef struct {
uint32_t version;
uint32_t fontRevision;
uint32_t checkSumAdjustment;
uint32_t magicNumber;
uint16_t flags;
uint16_t unitsPerEm;
uint32_t created[2];
uint32_t modified[2];
int16_t xMin;
int16_t yMin;
int16_t xMax;
int16_t yMax;
uint16_t macStyle;
uint16_t lowestRecPpem;
int16_t fontDirectionHint;
int16_t indexToLocFormat;
int16_t glyphDataFormat;
} sfntHeadTable;
#define HEAD_TABLE_SIZE 54 /* sizeof(sfntHeadTable) may report 56 because of alignment */
typedef struct {
uint32_t offset;
uint16_t oldIndex;
uint16_t newIndex;
} tableOrderRec;
#pragma pack(pop)
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,211 @@
/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
* ***** BEGIN LICENSE BLOCK *****
* Version: MPL 1.1/GPL 2.0/LGPL 2.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
* http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
* for the specific language governing rights and limitations under the
* License.
*
* The Original Code is WOFF font packaging code.
*
* The Initial Developer of the Original Code is Mozilla Corporation.
* Portions created by the Initial Developer are Copyright (C) 2009
* the Initial Developer. All Rights Reserved.
*
* Contributor(s):
* Jonathan Kew <jfkthame@gmail.com>
*
* Alternatively, the contents of this file may be used under the terms of
* either the GNU General Public License Version 2 or later (the "GPL"), or
* the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
* in which case the provisions of the GPL or the LGPL are applicable instead
* of those above. If you wish to allow use of your version of this file only
* under the terms of either the GPL or the LGPL, and not to allow others to
* use your version of this file under the terms of the MPL, indicate your
* decision by deleting the provisions above and replace them with the notice
* and other provisions required by the GPL or the LGPL. If you do not delete
* the provisions above, a recipient may use your version of this file under
* the terms of any one of the MPL, the GPL or the LGPL.
*
* ***** END LICENSE BLOCK ***** */
#ifndef WOFF_H_
#define WOFF_H_
/* API for the WOFF encoder and decoder */
#ifdef _MSC_VER /* MS VC lacks inttypes.h
but we can make do with a few definitons here */
typedef char int8_t;
typedef short int16_t;
typedef int int32_t;
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
#else
#include <inttypes.h>
#endif
#include <stdio.h> /* only for FILE, needed for woffPrintStatus */
/* error codes returned in the status parameter of WOFF functions */
enum {
/* Success */
eWOFF_ok = 0,
/* Errors: no valid result returned */
eWOFF_out_of_memory = 1, /* malloc or realloc failed */
eWOFF_invalid = 2, /* invalid input file (e.g., bad offset) */
eWOFF_compression_failure = 3, /* error in zlib call */
eWOFF_bad_signature = 4, /* unrecognized file signature */
eWOFF_buffer_too_small = 5, /* the provided buffer is too small */
eWOFF_bad_parameter = 6, /* bad parameter (e.g., null source ptr) */
eWOFF_illegal_order = 7, /* improperly ordered chunks in WOFF font */
/* Warnings: call succeeded but something odd was noticed.
Multiple warnings may be OR'd together. */
eWOFF_warn_unknown_version = 0x0100, /* unrecognized version of sfnt,
not standard TrueType or CFF */
eWOFF_warn_checksum_mismatch = 0x0200, /* bad checksum, use with caution;
any DSIG will be invalid */
eWOFF_warn_misaligned_table = 0x0400, /* table not long-aligned; fixing,
but DSIG will be invalid */
eWOFF_warn_trailing_data = 0x0800, /* trailing junk discarded,
any DSIG may be invalid */
eWOFF_warn_unpadded_table = 0x1000, /* sfnt not correctly padded,
any DSIG may be invalid */
eWOFF_warn_removed_DSIG = 0x2000 /* removed digital signature
while fixing checksum errors */
};
/* Note: status parameters must be initialized to eWOFF_ok before calling
WOFF functions. If the status parameter contains an error code,
functions will return immediately. */
#define WOFF_SUCCESS(status) (((uint32_t)(status) & 0xff) == eWOFF_ok)
#define WOFF_FAILURE(status) (!WOFF_SUCCESS(status))
#define WOFF_WARNING(status) ((uint32_t)(status) & ~0xff)
#ifdef __cplusplus
extern "C" {
#endif
#ifndef WOFF_DISABLE_ENCODING
/*****************************************************************************
* Returns a new malloc() block containing the encoded data, or NULL on error;
* caller should free() this when finished with it.
* Returns length of the encoded data in woffLen.
* The new WOFF has no metadata or private block;
* see the following functions to update these elements.
*/
const uint8_t * woffEncode(const uint8_t * sfntData, uint32_t sfntLen,
uint16_t majorVersion, uint16_t minorVersion,
uint32_t * woffLen, uint32_t * status);
/*****************************************************************************
* Add the given metadata block to the WOFF font, replacing any existing
* metadata block. The block will be zlib-compressed.
* Metadata is required to be valid XML (use of UTF-8 is recommended),
* though this function does not currently check this.
* The woffData pointer must be a malloc() block (typically from woffEncode);
* it will be freed by this function and a new malloc() block will be returned.
* Returns NULL if an error occurs, in which case the original WOFF is NOT freed.
*/
const uint8_t * woffSetMetadata(const uint8_t * woffData, uint32_t * woffLen,
const uint8_t * metaData, uint32_t metaLen,
uint32_t * status);
/*****************************************************************************
* Add the given private data block to the WOFF font, replacing any existing
* private block. The block will NOT be zlib-compressed.
* Private data may be any arbitrary block of bytes; it may be externally
* compressed by the client if desired.
* The woffData pointer must be a malloc() block (typically from woffEncode);
* it will be freed by this function and a new malloc() block will be returned.
* Returns NULL if an error occurs, in which case the original WOFF is NOT freed.
*/
const uint8_t * woffSetPrivateData(const uint8_t * woffData, uint32_t * woffLen,
const uint8_t * privData, uint32_t privLen,
uint32_t * status);
#endif /* WOFF_DISABLE_ENCODING */
/*****************************************************************************
* Returns the size of buffer needed to decode the font (or zero on error).
*/
uint32_t woffGetDecodedSize(const uint8_t * woffData, uint32_t woffLen,
uint32_t * pStatus);
/*****************************************************************************
* Decodes WOFF font to a caller-supplied buffer of size bufferLen.
* Returns the actual size of the decoded sfnt data in pActualSfntLen
* (must be <= bufferLen, otherwise an error will be returned).
*/
void woffDecodeToBuffer(const uint8_t * woffData, uint32_t woffLen,
uint8_t * sfntData, uint32_t bufferLen,
uint32_t * pActualSfntLen, uint32_t * pStatus);
/*****************************************************************************
* Returns a new malloc() block containing the decoded data, or NULL on error;
* caller should free() this when finished with it.
* Returns length of the decoded data in sfntLen.
*/
const uint8_t * woffDecode(const uint8_t * woffData, uint32_t woffLen,
uint32_t * sfntLen, uint32_t * status);
/*****************************************************************************
* Returns a new malloc() block containing the metadata from the WOFF font,
* or NULL if an error occurs or no metadata is present.
* Length of the metadata is returned in metaLen.
* The metadata is decompressed before returning.
*/
const uint8_t * woffGetMetadata(const uint8_t * woffData, uint32_t woffLen,
uint32_t * metaLen, uint32_t * status);
/*****************************************************************************
* Returns a new malloc() block containing the private data from the WOFF font,
* or NULL if an error occurs or no private data is present.
* Length of the private data is returned in privLen.
*/
const uint8_t * woffGetPrivateData(const uint8_t * woffData, uint32_t woffLen,
uint32_t * privLen, uint32_t * status);
/*****************************************************************************
* Returns the font version numbers from the WOFF font in the major and minor
* parameters.
* Check the status result to know if the function succeeded.
*/
void woffGetFontVersion(const uint8_t * woffData, uint32_t woffLen,
uint16_t * major, uint16_t * minor,
uint32_t * status);
/*****************************************************************************
* Utility to print warning and/or error status to the specified FILE*.
* The prefix string will be prepended to each line (ok to pass NULL if no
* prefix is wanted).
* (Provides terse English messages only, not intended for end-user display;
* user-friendly tools should map the status codes to their own messages.)
*/
void woffPrintStatus(FILE * f, uint32_t status, const char * prefix);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -10,7 +10,7 @@ import os
from calibre.utils.magick import Image, DrawingWand, create_canvas from calibre.utils.magick import Image, DrawingWand, create_canvas
from calibre.constants import __appname__, __version__ from calibre.constants import __appname__, __version__
from calibre.utils.config import tweaks from calibre.utils.config import tweaks
from calibre import fit_image from calibre import fit_image, force_unicode
def _data_to_image(data): def _data_to_image(data):
if isinstance(data, Image): if isinstance(data, Image):
@ -166,12 +166,9 @@ def add_borders_to_image(img_data, left=0, top=0, right=0, bottom=0,
return canvas.export(fmt) return canvas.export(fmt)
def create_text_wand(font_size, font_path=None): def create_text_wand(font_size, font_path=None):
if font_path is None:
font_path = tweaks['generate_cover_title_font']
if font_path is None:
font_path = P('fonts/liberation/LiberationSerif-Bold.ttf')
ans = DrawingWand() ans = DrawingWand()
ans.font = font_path if font_path is not None:
ans.font = font_path
ans.font_size = font_size ans.font_size = font_size
ans.gravity = 'CenterGravity' ans.gravity = 'CenterGravity'
ans.text_alias = True ans.text_alias = True
@ -238,6 +235,17 @@ class TextLine(object):
def __init__(self, text, font_size, bottom_margin=30, font_path=None): def __init__(self, text, font_size, bottom_margin=30, font_path=None):
self.text, self.font_size, = text, font_size self.text, self.font_size, = text, font_size
self.bottom_margin = bottom_margin self.bottom_margin = bottom_margin
if font_path is None:
if not isinstance(text, unicode):
text = force_unicode(text)
from calibre.utils.fonts.utils import get_font_for_text
fd = get_font_for_text(text)
if fd is not None:
from calibre.ptempfile import PersistentTemporaryFile
pt = PersistentTemporaryFile('.ttf')
pt.write(fd)
pt.close()
font_path = pt.name
self.font_path = font_path self.font_path = font_path
def __repr__(self): def __repr__(self):

View File

@ -549,7 +549,7 @@ def option_parser(usage=_('%prog URL\n\nWhere URL is for example http://google.c
def create_fetcher(options, image_map={}, log=None): def create_fetcher(options, image_map={}, log=None):
if log is None: if log is None:
log = Log() log = Log(level=Log.DEBUG) if options.verbose else Log()
return RecursiveFetcher(options, log, image_map={}) return RecursiveFetcher(options, log, image_map={})
def main(args=sys.argv): def main(args=sys.argv):