mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-08-11 09:13:57 -04:00
Merge from trunk
This commit is contained in:
commit
e0efbe0f0f
@ -15,7 +15,8 @@ class BusinessStandard(BasicNewsRecipe):
|
||||
no_stylesheets = True
|
||||
delay = 1
|
||||
use_embedded_content = False
|
||||
encoding = 'cp1252'
|
||||
auto_cleanup = True
|
||||
encoding = 'utf-8'
|
||||
publisher = 'Boston'
|
||||
category = 'news, boston, usa, world'
|
||||
language = 'en'
|
||||
@ -30,23 +31,23 @@ class BusinessStandard(BasicNewsRecipe):
|
||||
,'publisher' : publisher
|
||||
}
|
||||
|
||||
keep_only_tags = [dict(attrs={'id':['INDblogEntry','blogEntry','articleHeader','articleGraphs','galleryShell']})]
|
||||
remove_tags = [
|
||||
dict(name=['object','link','script','iframe'])
|
||||
,dict(attrs={'id':['blogheadTools','bdc_emailWidget','tools','relatedContent']})
|
||||
]
|
||||
#keep_only_tags = [dict(attrs={'id':['INDblogEntry','blogEntry','articleHeader','articleGraphs','galleryShell']})]
|
||||
#remove_tags = [
|
||||
#dict(name=['object','link','script','iframe'])
|
||||
#,dict(attrs={'id':['blogheadTools','bdc_emailWidget','tools','relatedContent']})
|
||||
#]
|
||||
|
||||
feeds = [
|
||||
(u'Top Stories' , u'http://feeds.boston.com/boston/topstories' )
|
||||
,(u'Patriots news', u'http://feeds.boston.com/boston/sports/football/patriots')
|
||||
,(u'Patriots news', u'http://feeds.boston.com/boston/sports/football/patriots/patriots_rss')
|
||||
,(u'National news', u'http://feeds.boston.com/boston/news/nation' )
|
||||
,(u'World news' , u'http://feeds.boston.com/boston/news/world' )
|
||||
]
|
||||
|
||||
def print_version(self, url):
|
||||
return url + '?page=full'
|
||||
#def print_version(self, url):
|
||||
#return url + '?page=full'
|
||||
|
||||
def get_article_url(self, article):
|
||||
rawarticle = article.get('guid', None)
|
||||
return rawarticle.rpartition('?')[0]
|
||||
#def get_article_url(self, article):
|
||||
#rawarticle = article.get('guid', None)
|
||||
#return rawarticle.rpartition('?')[0]
|
||||
|
||||
|
@ -7,7 +7,7 @@ class AdvancedUserRecipe1306061239(BasicNewsRecipe):
|
||||
description = 'News as provided by The Daily Mirror -UK'
|
||||
|
||||
__author__ = 'Dave Asbury'
|
||||
# last updated 8/6/12
|
||||
# last updated 19/10/12
|
||||
language = 'en_GB'
|
||||
#cover_url = 'http://yookeo.com/screens/m/i/mirror.co.uk.jpg'
|
||||
|
||||
@ -15,10 +15,12 @@ class AdvancedUserRecipe1306061239(BasicNewsRecipe):
|
||||
|
||||
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 12
|
||||
max_articles_per_feed = 1
|
||||
remove_empty_feeds = True
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
ignore_duplicate_articles = {'title'}
|
||||
|
||||
# auto_cleanup = True
|
||||
#conversion_options = { 'linearize_tables' : True }
|
||||
|
||||
@ -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')
|
||||
]
|
||||
extra_css = '''
|
||||
h1{ font-size:medium;}
|
||||
body{ text-align: justify; font-family:Arial,Helvetica,sans-serif; font-size:11px; font-size-adjust:none; font-stretch:normal; font-style:normal; font-variant:normal; font-weight:normal;}
|
||||
img { display:block}
|
||||
'''#
|
||||
extra_css = '''
|
||||
h1{font-family:Arial,Helvetica,sans-serif; font-weight:bold;font-size:large;}
|
||||
h2{font-family:Arial,Helvetica,sans-serif; font-weight:normal;font-size:small;}
|
||||
p{font-family:Arial,Helvetica,sans-serif;font-size:small;}
|
||||
body{font-family:Helvetica,Arial,sans-serif;font-size:small;}
|
||||
'''
|
||||
|
||||
def get_cover_url(self):
|
||||
soup = self.index_to_soup('http://www.politicshome.com/uk/latest_frontpage.html')
|
||||
@ -75,8 +78,10 @@ class AdvancedUserRecipe1306061239(BasicNewsRecipe):
|
||||
#cov2 now contains url of the page containing pic
|
||||
soup = self.index_to_soup(cov2)
|
||||
cov = soup.find(attrs={'id' : 'large'})
|
||||
cov2 = str(cov)
|
||||
cov2=cov2[27:-18]
|
||||
cov=str(cov)
|
||||
cov2 = re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', cov)
|
||||
cov2 = str(cov2)
|
||||
cov2=cov2[2:len(cov2)-2]
|
||||
#cov2 now is pic url, now go back to original function
|
||||
br = browser()
|
||||
br.set_handle_redirect(False)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import random
|
||||
import re, random
|
||||
|
||||
from calibre import browser
|
||||
from calibre.web.feeds.recipes import BasicNewsRecipe
|
||||
@ -8,7 +8,7 @@ class AdvancedUserRecipe1325006965(BasicNewsRecipe):
|
||||
title = u'The Sun UK'
|
||||
description = 'Articles from The Sun tabloid UK'
|
||||
__author__ = 'Dave Asbury'
|
||||
# last updated 12/10/12 added starsons remove article code
|
||||
# last updated 19/10/12 better cover fetch
|
||||
language = 'en_GB'
|
||||
oldest_article = 1
|
||||
max_articles_per_feed = 15
|
||||
@ -19,7 +19,7 @@ class AdvancedUserRecipe1325006965(BasicNewsRecipe):
|
||||
remove_javascript = True
|
||||
no_stylesheets = True
|
||||
|
||||
ignore_duplicate_articles = {'title'}
|
||||
ignore_duplicate_articles = {'title','url'}
|
||||
|
||||
|
||||
extra_css = '''
|
||||
@ -72,9 +72,10 @@ class AdvancedUserRecipe1325006965(BasicNewsRecipe):
|
||||
#cov2 now contains url of the page containing pic
|
||||
soup = self.index_to_soup(cov2)
|
||||
cov = soup.find(attrs={'id' : 'large'})
|
||||
cov2 = str(cov)
|
||||
cov2=cov2[27:-18]
|
||||
#cov2 now is pic url, now go back to original function
|
||||
cov=str(cov)
|
||||
cov2 = re.findall('http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+', cov)
|
||||
cov2 = str(cov2)
|
||||
cov2=cov2[2:len(cov2)-2]
|
||||
br = browser()
|
||||
br.set_handle_redirect(False)
|
||||
try:
|
||||
|
@ -23,16 +23,15 @@ class Time(BasicNewsRecipe):
|
||||
|
||||
keep_only_tags = [
|
||||
{
|
||||
'class':['tout1', 'entry-content', 'external-gallery-img', 'image-meta']
|
||||
'class':['primary-col', 'tout1']
|
||||
},
|
||||
]
|
||||
remove_tags = [
|
||||
{'class':['thumbnail', 'button']},
|
||||
{'class':['button', 'entry-sharing group', 'wp-paginate',
|
||||
'moving-markup', 'entry-comments']},
|
||||
|
||||
]
|
||||
|
||||
recursions = 10
|
||||
match_regexps = [r'/[0-9,]+-(2|3|4|5|6|7|8|9)(,\d+){0,1}.html',r'http://www.time.com/time/specials/packages/article/.*']
|
||||
extra_css = '.entry-date { padding-left: 2ex }'
|
||||
|
||||
preprocess_regexps = [(re.compile(
|
||||
r'<meta .+/>'), lambda m:'')]
|
||||
@ -45,7 +44,7 @@ class Time(BasicNewsRecipe):
|
||||
br.select_form(predicate=lambda f: 'action' in f.attrs and f.attrs['action'] == 'https://auth.time.com/login.php')
|
||||
br['username'] = self.username
|
||||
br['password'] = self.password
|
||||
br['magcode'] = ['TD']
|
||||
# br['magcode'] = ['TD']
|
||||
br.find_control('turl').readonly = False
|
||||
br['turl'] = 'http://www.time.com/time/magazine'
|
||||
br.find_control('rurl').readonly = False
|
||||
@ -104,7 +103,14 @@ class Time(BasicNewsRecipe):
|
||||
method='text').strip()
|
||||
if not title: continue
|
||||
url = a[0].get('href')
|
||||
url = re.sub('/magazine/article/0,9171','/subscriber/printout/0,8816', url)
|
||||
if url.startswith('/'):
|
||||
url = 'http://www.time.com'+url
|
||||
if '/article/0,' in url:
|
||||
soup = self.index_to_soup(url)
|
||||
a = soup.find('a', href=lambda x:x and '/printout/' in x)
|
||||
url = a['href'].replace('/printout', '/subscriber/printout')
|
||||
else:
|
||||
url += 'print/' if url.endswith('/') else '/print/'
|
||||
if url.startswith('/'):
|
||||
url = 'http://www.time.com'+url
|
||||
desc = ''
|
||||
@ -112,10 +118,18 @@ class Time(BasicNewsRecipe):
|
||||
if p:
|
||||
desc = html.tostring(p[0], encoding=unicode,
|
||||
method='text')
|
||||
self.log('\t', title, ':\n\t\t', desc)
|
||||
self.log('\t', title, ':\n\t\t', url)
|
||||
yield {
|
||||
'title' : title,
|
||||
'url' : url,
|
||||
'date' : '',
|
||||
'description' : desc
|
||||
}
|
||||
|
||||
def preprocess_html(self, soup):
|
||||
for fig in soup.findAll('figure'):
|
||||
img = fig.find('img')
|
||||
if img is not None:
|
||||
fig.replaceWith(img)
|
||||
return soup
|
||||
|
||||
|
19
recipes/yazihane.recipe
Normal file
19
recipes/yazihane.recipe
Normal 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/')]
|
@ -787,12 +787,10 @@ application/x-font-framemaker
|
||||
application/x-font-ghostscript gsf
|
||||
application/x-font-libgrx
|
||||
application/x-font-linux-psf psf
|
||||
application/x-font-otf otf
|
||||
application/x-font-pcf pcf
|
||||
application/x-font-snf snf
|
||||
application/x-font-speedo
|
||||
application/x-font-sunos-news
|
||||
application/x-font-ttf ttc ttf
|
||||
application/x-font-type1 afm pfa pfb pfm
|
||||
application/x-font-vfont
|
||||
application/x-freemind mm
|
||||
@ -1368,8 +1366,6 @@ text/fb2+xml fb2
|
||||
text/x-sony-bbeb+xml lrs
|
||||
application/x-sony-bbeb lrf lrx
|
||||
application/adobe-page-template+xml xpgt
|
||||
application/x-font-opentype otf
|
||||
application/x-font-truetype ttf
|
||||
application/x-mobipocket-ebook mobi prc azw
|
||||
application/x-topaz-ebook tpz azw1
|
||||
application/x-mobipocket-subscription pobi
|
||||
@ -1381,3 +1377,7 @@ application/x-cb7 cb7
|
||||
application/x-koboreader-ebook kobo
|
||||
image/wmf wmf
|
||||
application/ereader pdb
|
||||
# See http://idpf.org/epub/30/spec/epub30-publications.html#sec-core-media-types
|
||||
application/vnd.ms-opentype otf
|
||||
application/font-woff woff
|
||||
application/x-font-truetype ttf
|
||||
|
@ -8,6 +8,8 @@ let g:syntastic_cpp_include_dirs = [
|
||||
\'/usr/include/qt4/QtCore',
|
||||
\'/usr/include/qt4/QtGui',
|
||||
\'/usr/include/qt4',
|
||||
\'/usr/include/freetype2',
|
||||
\'/usr/include/fontconfig',
|
||||
\'src/qtcurve/common', 'src/qtcurve',
|
||||
\'/usr/include/ImageMagick',
|
||||
\]
|
||||
|
@ -84,6 +84,7 @@ qt_inc = pyqt.qt_inc_dir
|
||||
qt_lib = pyqt.qt_lib_dir
|
||||
ft_lib_dirs = []
|
||||
ft_libs = []
|
||||
ft_inc_dirs = []
|
||||
jpg_libs = []
|
||||
jpg_lib_dirs = []
|
||||
fc_inc = '/usr/include/fontconfig'
|
||||
@ -94,6 +95,9 @@ chmlib_inc_dirs = chmlib_lib_dirs = []
|
||||
sqlite_inc_dirs = []
|
||||
icu_inc_dirs = []
|
||||
icu_lib_dirs = []
|
||||
zlib_inc_dirs = []
|
||||
zlib_lib_dirs = []
|
||||
zlib_libs = ['z']
|
||||
|
||||
if iswindows:
|
||||
prefix = r'C:\cygwin\home\kovid\sw'
|
||||
@ -116,6 +120,10 @@ if iswindows:
|
||||
jpg_libs = ['jpeg']
|
||||
ft_lib_dirs = [sw_lib_dir]
|
||||
ft_libs = ['freetype']
|
||||
ft_inc_dirs = [sw_inc_dir]
|
||||
zlib_inc_dirs = [sw_inc_dir]
|
||||
zlib_lib_dirs = [sw_lib_dir]
|
||||
zlib_libs = ['zlib']
|
||||
|
||||
magick_inc_dirs = [os.path.join(prefix, 'build', 'ImageMagick-6.7.6')]
|
||||
magick_lib_dirs = [os.path.join(magick_inc_dirs[0], 'VisualMagick', 'lib')]
|
||||
@ -135,6 +143,8 @@ elif isosx:
|
||||
png_inc_dirs = consolidate('PNG_INC_DIR', '/sw/include')
|
||||
png_lib_dirs = consolidate('PNG_LIB_DIR', '/sw/lib')
|
||||
png_libs = ['png12']
|
||||
ft_libs = ['freetype']
|
||||
ft_inc_dirs = ['/sw/include/freetype2']
|
||||
else:
|
||||
# Include directories
|
||||
png_inc_dirs = pkgconfig_include_dirs('libpng', 'PNG_INC_DIR',
|
||||
@ -150,6 +160,10 @@ else:
|
||||
if not magick_libs:
|
||||
magick_libs = ['MagickWand', 'MagickCore']
|
||||
png_libs = ['png']
|
||||
ft_inc_dirs = pkgconfig_include_dirs('freetype2', 'FT_INC_DIR',
|
||||
'/usr/include/freetype2')
|
||||
ft_lib_dirs = pkgconfig_lib_dirs('freetype2', 'FT_LIB_DIR', '/usr/lib')
|
||||
ft_libs = pkgconfig_libs('freetype2', '', '')
|
||||
|
||||
|
||||
fc_inc = os.environ.get('FC_INC_DIR', fc_inc)
|
||||
|
@ -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,
|
||||
msvc, MT, win_inc, win_lib, win_ddk, magick_inc_dirs, magick_lib_dirs,
|
||||
magick_libs, chmlib_lib_dirs, sqlite_inc_dirs, icu_inc_dirs,
|
||||
icu_lib_dirs, win_ddk_lib_dirs)
|
||||
icu_lib_dirs, win_ddk_lib_dirs, ft_libs, ft_lib_dirs, ft_inc_dirs,
|
||||
zlib_libs, zlib_lib_dirs, zlib_inc_dirs)
|
||||
MT
|
||||
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_headers = glob.glob(os.path.join(SRC, 'calibre', 'ebooks', 'pdf', '*.h'))
|
||||
|
||||
pdfreflow_libs = []
|
||||
if iswindows:
|
||||
pdfreflow_libs = ['advapi32', 'User32', 'Gdi32', 'zlib']
|
||||
|
||||
icu_libs = ['icudata', 'icui18n', 'icuuc', 'icuio']
|
||||
icu_cflags = []
|
||||
if iswindows:
|
||||
@ -119,6 +116,12 @@ extensions = [
|
||||
'calibre/utils/lzx/mspack.h'],
|
||||
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',
|
||||
['calibre/utils/fonts/fontconfig.c'],
|
||||
inc_dirs = [fc_inc],
|
||||
@ -126,6 +129,18 @@ extensions = [
|
||||
lib_dirs=[fc_lib],
|
||||
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',
|
||||
['calibre/utils/msdes/msdesmodule.c',
|
||||
'calibre/utils/msdes/des.c'],
|
||||
|
@ -329,6 +329,7 @@ def get_parsed_proxy(typ='http', debug=True):
|
||||
ans['port'] = int(ans['port'])
|
||||
except:
|
||||
if debug:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
else:
|
||||
if debug:
|
||||
@ -689,26 +690,18 @@ def remove_bracketed_text(src,
|
||||
buf.append(char)
|
||||
return u''.join(buf)
|
||||
|
||||
if isosx:
|
||||
import glob, shutil
|
||||
fdir = os.path.expanduser('~/.fonts')
|
||||
try:
|
||||
if not os.path.exists(fdir):
|
||||
os.makedirs(fdir)
|
||||
if not os.path.exists(os.path.join(fdir, 'LiberationSans_Regular.ttf')):
|
||||
base = P('fonts/liberation/*.ttf')
|
||||
for f in glob.glob(base):
|
||||
shutil.copy2(f, fdir)
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
|
||||
def load_builtin_fonts():
|
||||
import glob
|
||||
from PyQt4.Qt import QFontDatabase
|
||||
base = P('fonts/liberation/*.ttf')
|
||||
for f in glob.glob(base):
|
||||
QFontDatabase.addApplicationFont(f)
|
||||
if iswindows or isosx:
|
||||
import glob
|
||||
from PyQt4.Qt import QFontDatabase
|
||||
for f in glob.glob(P('fonts/liberation/*.ttf')):
|
||||
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'
|
||||
|
||||
|
||||
|
@ -89,6 +89,8 @@ class Plugins(collections.Mapping):
|
||||
'chm_extra',
|
||||
'icu',
|
||||
'speedup',
|
||||
'freetype',
|
||||
'woff',
|
||||
]
|
||||
if iswindows:
|
||||
plugins.extend(['winutil', 'wpd', 'winfonts'])
|
||||
|
@ -178,18 +178,41 @@ def normalize(x):
|
||||
|
||||
def calibre_cover(title, author_string, series_string=None,
|
||||
output_format='jpg', title_size=46, author_size=36, logo_path=None):
|
||||
from calibre.utils.config_base import tweaks
|
||||
title = normalize(title)
|
||||
author_string = normalize(author_string)
|
||||
series_string = normalize(series_string)
|
||||
from calibre.utils.magick.draw import create_cover_page, TextLine
|
||||
lines = [TextLine(title, title_size), TextLine(author_string, author_size)]
|
||||
text = title + author_string + (series_string or u'')
|
||||
font_path = tweaks['generate_cover_title_font']
|
||||
if font_path is None:
|
||||
font_path = P('fonts/liberation/LiberationSerif-Bold.ttf')
|
||||
|
||||
from calibre.utils.fonts.utils import get_font_for_text
|
||||
font = open(font_path, 'rb').read()
|
||||
c = get_font_for_text(text, font)
|
||||
cleanup = False
|
||||
if c is not None and c != font:
|
||||
from calibre.ptempfile import PersistentTemporaryFile
|
||||
pt = PersistentTemporaryFile('.ttf')
|
||||
pt.write(c)
|
||||
pt.close()
|
||||
font_path = pt.name
|
||||
cleanup = True
|
||||
|
||||
lines = [TextLine(title, title_size, font_path=font_path),
|
||||
TextLine(author_string, author_size, font_path=font_path)]
|
||||
if series_string:
|
||||
lines.append(TextLine(series_string, author_size))
|
||||
lines.append(TextLine(series_string, author_size, font_path=font_path))
|
||||
if logo_path is None:
|
||||
logo_path = I('library.png')
|
||||
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',
|
||||
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)$')
|
||||
|
||||
|
@ -132,7 +132,7 @@ def add_pipeline_options(parser, plumber):
|
||||
_('Options to control the look and feel of the output'),
|
||||
[
|
||||
'base_font_size', 'disable_font_rescaling',
|
||||
'font_size_mapping',
|
||||
'font_size_mapping', 'embed_font_family',
|
||||
'line_height', 'minimum_line_height',
|
||||
'linearize_tables',
|
||||
'extra_css', 'filter_css',
|
||||
|
@ -193,6 +193,17 @@ OptionRecommendation(name='line_height',
|
||||
)
|
||||
),
|
||||
|
||||
OptionRecommendation(name='embed_font_family',
|
||||
recommended_value=None, level=OptionRecommendation.LOW,
|
||||
help=_(
|
||||
'Embed the specified font family into the book. This specifies '
|
||||
'the "base" font used for the book. If the input document '
|
||||
'specifies its own fonts, they may override this base font. '
|
||||
'You can use the filter style information option to remove fonts from the '
|
||||
'input document. Note that font embedding only works '
|
||||
'with some output formats, principally EPUB and AZW3.')
|
||||
),
|
||||
|
||||
OptionRecommendation(name='linearize_tables',
|
||||
recommended_value=False, level=OptionRecommendation.LOW,
|
||||
help=_('Some badly designed documents use tables to control the '
|
||||
|
@ -379,6 +379,10 @@ def set_metadata(stream, mi, apply_null=False, update_timestamp=False):
|
||||
|
||||
stream.seek(0)
|
||||
stream.truncate()
|
||||
# Apparently there exists FB2 reading software that chokes on the use of
|
||||
# single quotes in xml declaration. Sigh. See
|
||||
# http://www.mobileread.com/forums/showthread.php?p=2273184#post2273184
|
||||
stream.write(b'<?xml version="1.0" encoding="UTF-8"?>\n')
|
||||
stream.write(etree.tostring(root, method='xml', encoding='utf-8',
|
||||
xml_declaration=True))
|
||||
xml_declaration=False))
|
||||
|
||||
|
@ -258,7 +258,7 @@ OPF_MIME = types_map['.opf']
|
||||
PAGE_MAP_MIME = 'application/oebps-page-map+xml'
|
||||
OEB_DOC_MIME = 'text/x-oeb1-document'
|
||||
OEB_CSS_MIME = 'text/x-oeb1-css'
|
||||
OPENTYPE_MIME = 'application/x-font-opentype'
|
||||
OPENTYPE_MIME = types_map['.otf']
|
||||
GIF_MIME = types_map['.gif']
|
||||
JPEG_MIME = types_map['.jpeg']
|
||||
PNG_MIME = types_map['.png']
|
||||
|
@ -126,6 +126,17 @@ class CaseInsensitiveAttributesTranslator(HTMLTranslator):
|
||||
|
||||
ci_css_to_xpath = CaseInsensitiveAttributesTranslator().css_to_xpath
|
||||
|
||||
NULL_NAMESPACE_REGEX = re.compile(ur'''(name\(\) = ['"])h:''')
|
||||
def fix_namespace(raw):
|
||||
'''
|
||||
cssselect uses name() = 'h:p' to select tags for some CSS selectors (e.g.
|
||||
h|p+h|p).
|
||||
However, since for us the XHTML namespace is the default namespace (with no
|
||||
prefix), name() is the same as local-name(). So this is a hack to
|
||||
workaround the problem.
|
||||
'''
|
||||
return NULL_NAMESPACE_REGEX.sub(ur'\1', raw)
|
||||
|
||||
class CSSSelector(object):
|
||||
|
||||
def __init__(self, css, log=None, namespaces=XPNSMAP):
|
||||
@ -136,7 +147,7 @@ class CSSSelector(object):
|
||||
|
||||
def build_selector(self, css, log, func=css_to_xpath):
|
||||
try:
|
||||
return etree.XPath(func(css), namespaces=self.namespaces)
|
||||
return etree.XPath(fix_namespace(func(css)), namespaces=self.namespaces)
|
||||
except:
|
||||
if log is not None:
|
||||
log.exception('Failed to parse CSS selector: %r'%css)
|
||||
|
@ -14,9 +14,11 @@ from lxml import etree
|
||||
import cssutils
|
||||
from cssutils.css import Property
|
||||
|
||||
from calibre import guess_type
|
||||
from calibre.ebooks.oeb.base import (XHTML, XHTML_NS, CSS_MIME, OEB_STYLES,
|
||||
namespace, barename, XPath)
|
||||
from calibre.ebooks.oeb.stylizer import Stylizer
|
||||
from calibre.utils.filenames import ascii_filename
|
||||
|
||||
COLLAPSE = re.compile(r'[ \t\r\n\v]+')
|
||||
STRIPNUM = re.compile(r'[-0-9]+$')
|
||||
@ -144,11 +146,61 @@ class CSSFlattener(object):
|
||||
cssutils.replaceUrls(item.data, item.abshref,
|
||||
ignoreImportRules=True)
|
||||
|
||||
self.body_font_family, self.embed_font_rules = self.get_embed_font_info(
|
||||
self.opts.embed_font_family)
|
||||
self.stylize_spine()
|
||||
self.sbase = self.baseline_spine() if self.fbase else None
|
||||
self.fmap = FontMapper(self.sbase, self.fbase, self.fkey)
|
||||
self.flatten_spine()
|
||||
|
||||
def get_embed_font_info(self, family, failure_critical=True):
|
||||
efi = []
|
||||
body_font_family = None
|
||||
if not family:
|
||||
return body_font_family, efi
|
||||
from calibre.utils.fonts 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):
|
||||
self.stylizers = {}
|
||||
profile = self.context.source
|
||||
@ -170,6 +222,8 @@ class CSSFlattener(object):
|
||||
bs.extend(['page-break-before: always'])
|
||||
if self.context.change_justification != 'original':
|
||||
bs.append('text-align: '+ self.context.change_justification)
|
||||
if self.body_font_family:
|
||||
bs.append(u'font-family: '+self.body_font_family)
|
||||
body.set('style', '; '.join(bs))
|
||||
stylizer = Stylizer(html, item.href, self.oeb, self.context, profile,
|
||||
user_css=self.context.extra_css,
|
||||
@ -450,7 +504,8 @@ class CSSFlattener(object):
|
||||
items.sort()
|
||||
css = ';\n'.join("%s: %s" % (key, val) for key, val in items)
|
||||
css = ('@page {\n%s\n}\n'%css) if items else ''
|
||||
rules = [r.cssText for r in stylizer.font_face_rules]
|
||||
rules = [r.cssText for r in stylizer.font_face_rules +
|
||||
self.embed_font_rules]
|
||||
raw = '\n\n'.join(rules)
|
||||
css += '\n\n' + raw
|
||||
global_css[css].append(item)
|
||||
|
@ -73,6 +73,7 @@ class Split(object):
|
||||
|
||||
def find_page_breaks(self, item):
|
||||
if self.page_break_selectors is None:
|
||||
from calibre.ebooks.oeb.stylizer import fix_namespace
|
||||
css_to_xpath = HTMLTranslator().css_to_xpath
|
||||
self.page_break_selectors = set([])
|
||||
stylesheets = [x.data for x in self.oeb.manifest if x.media_type in
|
||||
@ -84,7 +85,7 @@ class Split(object):
|
||||
'page-break-after'), 'cssText', '').strip().lower()
|
||||
try:
|
||||
if before and before not in {'avoid', 'auto', 'inherit'}:
|
||||
self.page_break_selectors.add((XPath(css_to_xpath(rule.selectorText)),
|
||||
self.page_break_selectors.add((XPath(fix_namespace(css_to_xpath(rule.selectorText))),
|
||||
True))
|
||||
if self.remove_css_pagebreaks:
|
||||
rule.style.removeProperty('page-break-before')
|
||||
@ -92,7 +93,7 @@ class Split(object):
|
||||
pass
|
||||
try:
|
||||
if after and after not in {'avoid', 'auto', 'inherit'}:
|
||||
self.page_break_selectors.add((XPath(css_to_xpath(rule.selectorText)),
|
||||
self.page_break_selectors.add((XPath(fix_namespace(css_to_xpath(rule.selectorText))),
|
||||
False))
|
||||
if self.remove_css_pagebreaks:
|
||||
rule.style.removeProperty('page-break-after')
|
||||
|
@ -12,7 +12,7 @@ from PyQt4.Qt import (QVariant, QFileInfo, QObject, SIGNAL, QBuffer, Qt,
|
||||
|
||||
ORG_NAME = 'KovidsBrain'
|
||||
APP_UID = 'libprs500'
|
||||
from calibre import prints
|
||||
from calibre import prints, load_builtin_fonts
|
||||
from calibre.constants import (islinux, iswindows, isbsd, isfrozen, isosx,
|
||||
plugins, config_dir, filesystem_encoding, DEBUG)
|
||||
from calibre.utils.config import Config, ConfigProxy, dynamic, JSONConfig
|
||||
@ -779,6 +779,7 @@ class Application(QApplication):
|
||||
qt_app = self
|
||||
self._file_open_paths = []
|
||||
self._file_open_lock = RLock()
|
||||
load_builtin_fonts()
|
||||
self.setup_styles(force_calibre_style)
|
||||
|
||||
if DEBUG:
|
||||
|
@ -32,6 +32,7 @@ class LookAndFeelWidget(Widget, Ui_Form):
|
||||
Widget.__init__(self, parent,
|
||||
['change_justification', 'extra_css', 'base_font_size',
|
||||
'font_size_mapping', 'line_height', 'minimum_line_height',
|
||||
'embed_font_family',
|
||||
'smarten_punctuation', 'unsmarten_punctuation',
|
||||
'disable_font_rescaling', 'insert_blank_line',
|
||||
'remove_paragraph_spacing',
|
||||
|
@ -7,27 +7,53 @@
|
||||
<x>0</x>
|
||||
<y>0</y>
|
||||
<width>655</width>
|
||||
<height>522</height>
|
||||
<height>619</height>
|
||||
</rect>
|
||||
</property>
|
||||
<property name="windowTitle">
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="0" column="0">
|
||||
<widget class="QCheckBox" name="opt_disable_font_rescaling">
|
||||
<property name="text">
|
||||
<string>&Disable font size rescaling</string>
|
||||
<item row="3" column="4">
|
||||
<widget class="QDoubleSpinBox" name="opt_line_height">
|
||||
<property name="suffix">
|
||||
<string> pt</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>1</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_18">
|
||||
<item row="3" column="3">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
<string>Base &font size:</string>
|
||||
<string>Line &height:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_base_font_size</cstring>
|
||||
<cstring>opt_line_height</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Minimum &line height:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_minimum_line_height</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QDoubleSpinBox" name="opt_minimum_line_height">
|
||||
<property name="suffix">
|
||||
<string> %</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>900.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
@ -97,49 +123,6 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_6">
|
||||
<property name="text">
|
||||
<string>Minimum &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 &height:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_line_height</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="4" column="1">
|
||||
<widget class="QDoubleSpinBox" name="opt_line_height">
|
||||
<property name="suffix">
|
||||
<string> pt</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>1</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_3">
|
||||
<property name="text">
|
||||
@ -157,14 +140,14 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0" colspan="2">
|
||||
<item row="7" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_remove_paragraph_spacing">
|
||||
<property name="text">
|
||||
<string>Remove &spacing between paragraphs</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="3">
|
||||
<item row="7" column="3">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>&Indent size:</string>
|
||||
@ -177,7 +160,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="4">
|
||||
<item row="7" column="4">
|
||||
<widget class="QDoubleSpinBox" name="opt_remove_paragraph_spacing_indent_size">
|
||||
<property name="toolTip">
|
||||
<string><p>When calibre removes inter paragraph spacing, it automatically sets a paragraph indent, to ensure that paragraphs can be easily distinguished. This option controls the width of that indent.</string>
|
||||
@ -199,85 +182,7 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_insert_blank_line">
|
||||
<property name="text">
|
||||
<string>Insert &blank line between paragraphs</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="3">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>&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 &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 &punctuation</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="1" colspan="4">
|
||||
<widget class="QCheckBox" name="opt_asciiize">
|
||||
<property name="text">
|
||||
<string>&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>&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 &ligatures</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="3">
|
||||
<widget class="QCheckBox" name="opt_linearize_tables">
|
||||
<property name="text">
|
||||
<string>&Linearize tables</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="0" colspan="5">
|
||||
<item row="12" column="0" colspan="5">
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
@ -378,10 +283,131 @@
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<spacer name="verticalSpacer">
|
||||
<property name="orientation">
|
||||
<enum>Qt::Vertical</enum>
|
||||
</property>
|
||||
<property name="sizeHint" stdset="0">
|
||||
<size>
|
||||
<width>20</width>
|
||||
<height>40</height>
|
||||
</size>
|
||||
</property>
|
||||
</spacer>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_insert_blank_line">
|
||||
<property name="text">
|
||||
<string>Insert &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 &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 &punctuation</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1" colspan="4">
|
||||
<widget class="QCheckBox" name="opt_asciiize">
|
||||
<property name="text">
|
||||
<string>&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>&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 &ligatures</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="3">
|
||||
<widget class="QCheckBox" name="opt_linearize_tables">
|
||||
<property name="text">
|
||||
<string>&Linearize tables</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_18">
|
||||
<property name="text">
|
||||
<string>Base &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>&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>&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>&Disable font size rescaling</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1" colspan="2">
|
||||
<widget class="FontFamilyChooser" name="opt_embed_font_family"/>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
@ -390,6 +416,11 @@
|
||||
<extends>QComboBox</extends>
|
||||
<header>widgets.h</header>
|
||||
</customwidget>
|
||||
<customwidget>
|
||||
<class>FontFamilyChooser</class>
|
||||
<extends>QComboBox</extends>
|
||||
<header>calibre/gui2/font_family_chooser.h</header>
|
||||
</customwidget>
|
||||
</customwidgets>
|
||||
<resources>
|
||||
<include location="../../../../resources/images.qrc"/>
|
||||
|
@ -211,6 +211,9 @@ class RegexEdit(QWidget, Ui_Edit):
|
||||
self.button.clicked.connect(self.builder)
|
||||
|
||||
def builder(self):
|
||||
if self.db is None:
|
||||
self.doc_cache = _('Click the Open button below to open a '
|
||||
'ebook to use for testing.')
|
||||
bld = RegexBuilder(self.db, self.book_id, self.edit.text(), self.doc_cache, self)
|
||||
if bld.cancelled:
|
||||
return
|
||||
|
@ -55,6 +55,12 @@ def writing_system_for_font(font):
|
||||
class FontFamilyDelegate(QStyledItemDelegate):
|
||||
|
||||
def sizeHint(self, option, index):
|
||||
try:
|
||||
return self.do_size_hint(option, index)
|
||||
except:
|
||||
return QSize(300, 50)
|
||||
|
||||
def do_size_hint(self, option, index):
|
||||
text = index.data(Qt.DisplayRole).toString()
|
||||
font = QFont(option.font)
|
||||
font.setPointSize(QFontInfo(font).pointSize() * 1.5)
|
||||
@ -62,6 +68,14 @@ class FontFamilyDelegate(QStyledItemDelegate):
|
||||
return QSize(m.width(text), m.height())
|
||||
|
||||
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())
|
||||
font = QFont(option.font)
|
||||
font.setPointSize(QFontInfo(font).pointSize() * 1.5)
|
||||
@ -75,7 +89,6 @@ class FontFamilyDelegate(QStyledItemDelegate):
|
||||
r = option.rect
|
||||
|
||||
if option.state & QStyle.State_Selected:
|
||||
painter.save()
|
||||
painter.setBrush(option.palette.highlight())
|
||||
painter.setPen(Qt.NoPen)
|
||||
painter.drawRect(option.rect)
|
||||
@ -102,9 +115,6 @@ class FontFamilyDelegate(QStyledItemDelegate):
|
||||
|
||||
painter.setFont(old)
|
||||
|
||||
if (option.state & QStyle.State_Selected):
|
||||
painter.restore()
|
||||
|
||||
class FontFamilyChooser(QComboBox):
|
||||
|
||||
def __init__(self, parent=None):
|
||||
@ -138,7 +148,10 @@ class FontFamilyChooser(QComboBox):
|
||||
|
||||
def 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
|
||||
|
||||
@dynamic_property
|
||||
@ -157,12 +170,15 @@ class FontFamilyChooser(QComboBox):
|
||||
self.setCurrentIndex(idx)
|
||||
return property(fget=fget, fset=fset)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
def test():
|
||||
app = QApplication([])
|
||||
app
|
||||
d = QDialog()
|
||||
d.setLayout(QVBoxLayout())
|
||||
d.layout().addWidget(FontFamilyChooser(d))
|
||||
d.layout().addWidget(QFontComboBox(d))
|
||||
d.exec_()
|
||||
|
||||
if __name__ == '__main__':
|
||||
test()
|
||||
|
||||
|
@ -1368,6 +1368,8 @@ class DeviceBooksModel(BooksModel): # {{{
|
||||
return QVariant(authors_to_string(au))
|
||||
elif cname == 'size':
|
||||
size = self.db[self.map[row]].size
|
||||
if not isinstance(size, (float, int)):
|
||||
size = 0
|
||||
return QVariant(human_readable(size))
|
||||
elif cname == 'timestamp':
|
||||
dt = self.db[self.map[row]].datetime
|
||||
|
@ -139,3 +139,5 @@ class MainWindow(QMainWindow):
|
||||
show=True)
|
||||
except BaseException:
|
||||
pass
|
||||
except:
|
||||
pass
|
||||
|
@ -270,7 +270,7 @@ class AuthorsEdit(EditWithComplete):
|
||||
import traceback
|
||||
fname = err.filename if err.filename else 'file'
|
||||
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(),
|
||||
show=True)
|
||||
return False
|
||||
|
@ -66,6 +66,8 @@ class SonyStore(BasicStoreConfig, StorePlugin):
|
||||
detail_url = ''.join(item.xpath('descendant::h3[@class="item"]'
|
||||
'/descendant::a[@class="fn" and @href]/@href'))
|
||||
if not detail_url: continue
|
||||
if detail_url.startswith('/'):
|
||||
detail_url = 'http:'+detail_url
|
||||
s.detail_item = detail_url
|
||||
|
||||
counter -= 1
|
||||
|
@ -16,7 +16,7 @@ from PyQt4.QtWebKit import QWebPage, QWebView, QWebSettings
|
||||
|
||||
from calibre.gui2.viewer.flip import SlideFlip
|
||||
from calibre.gui2.shortcuts import Shortcuts
|
||||
from calibre import prints, load_builtin_fonts
|
||||
from calibre import prints
|
||||
from calibre.customize.ui import all_viewer_plugins
|
||||
from calibre.gui2.viewer.keys import SHORTCUTS
|
||||
from calibre.gui2.viewer.javascript import JavaScriptLoader
|
||||
@ -86,7 +86,6 @@ class Document(QWebPage): # {{{
|
||||
settings = self.settings()
|
||||
|
||||
# Fonts
|
||||
load_builtin_fonts()
|
||||
self.all_viewer_plugins = tuple(all_viewer_plugins())
|
||||
for pl in self.all_viewer_plugins:
|
||||
pl.load_fonts()
|
||||
|
@ -693,16 +693,16 @@ datatype is one of: {0}
|
||||
'the data in this column will be interpreted. This is a JSON '
|
||||
' string. For enumeration columns, use '
|
||||
'--display="{\\"enum_values\\":[\\"val1\\", \\"val2\\"]}"'
|
||||
''
|
||||
'\n'
|
||||
'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,'
|
||||
' contains_html, use_decorations'
|
||||
'datetime: date_format'
|
||||
'enumeration: enum_values, enum_colors, use_decorations'
|
||||
'int, float: number_format'
|
||||
'text: is_names. use_decorations'
|
||||
''
|
||||
'contains_html, use_decorations\n'
|
||||
'datetime: date_format\n'
|
||||
'enumeration: enum_values, enum_colors, use_decorations\n'
|
||||
'int, float: number_format\n'
|
||||
'text: is_names, use_decorations\n'
|
||||
'\n'
|
||||
'The best way to find legal combinations is to create a custom'
|
||||
'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'
|
||||
@ -720,7 +720,6 @@ def command_add_custom_column(args, dbpath):
|
||||
print
|
||||
print >>sys.stderr, _('You must specify label, name and datatype')
|
||||
return 1
|
||||
print opts.display
|
||||
do_add_custom_column(get_db(dbpath, opts), args[0], args[1], args[2],
|
||||
opts.is_multiple, json.loads(opts.display))
|
||||
# Re-open the DB so that field_metadata is reflects the column changes
|
||||
|
@ -7,7 +7,7 @@ __docformat__ = 'restructuredtext en'
|
||||
The database used to store ebook metadata
|
||||
'''
|
||||
import os, sys, shutil, cStringIO, glob, time, functools, traceback, re, \
|
||||
json, uuid, hashlib, copy
|
||||
json, uuid, hashlib, copy, errno
|
||||
from collections import defaultdict
|
||||
import threading, random
|
||||
from itertools import repeat
|
||||
@ -30,7 +30,8 @@ from calibre.ptempfile import (PersistentTemporaryFile,
|
||||
base_dir, SpooledTemporaryFile)
|
||||
from calibre.customize.ui import run_plugins_on_import
|
||||
from calibre import isbytestring
|
||||
from calibre.utils.filenames import ascii_filename, samefile
|
||||
from calibre.utils.filenames import (ascii_filename, samefile,
|
||||
windows_is_folder_in_use)
|
||||
from calibre.utils.date import (utcnow, now as nowf, utcfromtimestamp,
|
||||
parse_only_date, UNDEFINED_DATE)
|
||||
from calibre.utils.config import prefs, tweaks, from_json, to_json
|
||||
@ -649,6 +650,13 @@ class LibraryDatabase2(LibraryDatabase, SchemaUpgrade, CustomColumns):
|
||||
spath = os.path.join(self.library_path, *current_path.split('/'))
|
||||
|
||||
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)
|
||||
if cdata is not None:
|
||||
with lopen(os.path.join(tpath, 'cover.jpg'), 'wb') as f:
|
||||
|
@ -32,6 +32,11 @@ def test_lxml():
|
||||
else:
|
||||
raise RuntimeError('lxml failed')
|
||||
|
||||
def test_freetype():
|
||||
from calibre.utils.fonts.free_type import test
|
||||
test()
|
||||
print ('FreeType OK!')
|
||||
|
||||
def test_fontconfig():
|
||||
from calibre.utils.fonts import fontconfig
|
||||
families = fontconfig.find_font_families()
|
||||
@ -103,21 +108,28 @@ def test_icu():
|
||||
def test_wpd():
|
||||
wpd = plugins['wpd'][0]
|
||||
try:
|
||||
wpd.init()
|
||||
wpd.init('calibre', 1, 1, 1)
|
||||
except wpd.NoWPD:
|
||||
print ('This computer does not have WPD')
|
||||
else:
|
||||
wpd.uninit()
|
||||
|
||||
def test_woff():
|
||||
from calibre.utils.fonts.woff import test
|
||||
test()
|
||||
print ('WOFF ok!')
|
||||
|
||||
def test():
|
||||
test_plugins()
|
||||
test_lxml()
|
||||
test_freetype()
|
||||
test_fontconfig()
|
||||
test_sqlite()
|
||||
test_qt()
|
||||
test_imaging()
|
||||
test_unrar()
|
||||
test_icu()
|
||||
test_woff()
|
||||
if iswindows:
|
||||
test_win32()
|
||||
test_winutil()
|
||||
|
@ -249,4 +249,32 @@ def samefile(src, dst):
|
||||
os.path.normcase(os.path.abspath(dst)))
|
||||
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
|
||||
|
||||
|
@ -55,19 +55,65 @@ class Fonts(object):
|
||||
files = self.backend.files_for_family(family, normalize=normalize)
|
||||
ans = {}
|
||||
for ft, val in files.iteritems():
|
||||
name, f = val
|
||||
f, name = val
|
||||
ext = f.rpartition('.')[-1].lower()
|
||||
ans[ft] = (ext, name, open(f, 'rb').read())
|
||||
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()
|
||||
|
||||
def test():
|
||||
import os
|
||||
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():
|
||||
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__':
|
||||
test()
|
||||
|
@ -8,7 +8,7 @@ __docformat__ = 'restructuredtext en'
|
||||
|
||||
import os, sys
|
||||
|
||||
from calibre.constants import plugins, iswindows, islinux, isbsd
|
||||
from calibre.constants import plugins, islinux, isbsd
|
||||
|
||||
_fc, _fc_err = plugins['fontconfig']
|
||||
|
||||
@ -35,18 +35,14 @@ class FontConfig(Thread):
|
||||
if isinstance(config_dir, unicode):
|
||||
config_dir = config_dir.encode(sys.getfilesystemencoding())
|
||||
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:
|
||||
_fc.initialize(config)
|
||||
except:
|
||||
import traceback
|
||||
traceback.print_exc()
|
||||
self.failed = True
|
||||
if not self.failed and hasattr(_fc, 'add_font_dir'):
|
||||
_fc.add_font_dir(P('fonts/liberation'))
|
||||
|
||||
def wait(self):
|
||||
if not (islinux or isbsd):
|
||||
@ -162,7 +158,7 @@ def test():
|
||||
from pprint import pprint;
|
||||
pprint(fontconfig.find_font_families())
|
||||
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))
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
@ -44,6 +44,17 @@ fontconfig_initialize(PyObject *self, PyObject *args) {
|
||||
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
|
||||
fontconfig_cleanup_find(FcPattern *p, FcObjectSet *oset, FcFontSet *fs) {
|
||||
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}
|
||||
};
|
||||
|
||||
|
98
src/calibre/utils/fonts/free_type.py
Normal file
98
src/calibre/utils/fonts/free_type.py
Normal 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()
|
||||
|
304
src/calibre/utils/fonts/freetype.cpp
Normal file
304
src/calibre/utils/fonts/freetype.cpp
Normal 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);
|
||||
}
|
||||
|
@ -38,8 +38,8 @@ def get_table(raw, name):
|
||||
|
||||
def get_font_characteristics(raw):
|
||||
'''
|
||||
Return (weight, is_italic, is_bold, is_regular, fs_type). These values are taken
|
||||
from the OS/2 table of the font. See
|
||||
Return (weight, is_italic, is_bold, is_regular, fs_type, panose). These
|
||||
values are taken from the OS/2 table of the font. See
|
||||
http://www.microsoft.com/typography/otspec/os2.htm for details
|
||||
'''
|
||||
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)
|
||||
offset = struct.calcsize(common_fields)
|
||||
panose = struct.unpack_from(b'>10B', os2_table, offset)
|
||||
panose
|
||||
offset += 10
|
||||
(range1,) = struct.unpack_from(b'>L', os2_table, offset)
|
||||
offset += struct.calcsize(b'>L')
|
||||
@ -69,7 +68,21 @@ def get_font_characteristics(raw):
|
||||
is_italic = (selection & 0b1) != 0
|
||||
is_bold = (selection & 0b100000) != 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):
|
||||
'''
|
||||
@ -225,13 +238,33 @@ def remove_embed_restriction(raw):
|
||||
verify_checksums(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():
|
||||
import sys, os
|
||||
for f in sys.argv[1:]:
|
||||
print (os.path.basename(f))
|
||||
raw = open(f, 'rb').read()
|
||||
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)
|
||||
remove_embed_restriction(raw)
|
||||
|
||||
|
@ -19,6 +19,23 @@ class WinFonts(object):
|
||||
|
||||
def __init__(self, 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):
|
||||
names = set()
|
||||
@ -30,7 +47,7 @@ class WinFonts(object):
|
||||
not font['name'].startswith('@')
|
||||
):
|
||||
names.add(font['name'])
|
||||
return sorted(names)
|
||||
return sorted(names.union(frozenset(self.app_font_families)))
|
||||
|
||||
def get_normalized_name(self, is_italic, weight):
|
||||
if is_italic:
|
||||
@ -43,12 +60,18 @@ class WinFonts(object):
|
||||
family = type(u'')(family)
|
||||
ans = {}
|
||||
for weight, is_italic in product( (self.w.FW_NORMAL, self.w.FW_BOLD), (False, True) ):
|
||||
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
|
||||
if family in self.app_font_families:
|
||||
m = self.app_font_families[family]
|
||||
path = m.get((weight, is_italic), None)
|
||||
if path is None: continue
|
||||
data = P(path, data=True)
|
||||
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)
|
||||
if not ok:
|
||||
@ -122,7 +145,7 @@ def test_ttf_reading():
|
||||
get_font_characteristics(raw)
|
||||
print()
|
||||
|
||||
if __name__ == '__main__':
|
||||
def test():
|
||||
base = os.path.abspath(__file__)
|
||||
d = os.path.dirname
|
||||
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]))
|
||||
print ()
|
||||
|
||||
if __name__ == '__main__':
|
||||
test()
|
||||
|
36
src/calibre/utils/fonts/woff/__init__.py
Normal file
36
src/calibre/utils/fonts/woff/__init__.py
Normal 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()
|
||||
|
||||
|
108
src/calibre/utils/fonts/woff/main.c
Normal file
108
src/calibre/utils/fonts/woff/main.c
Normal 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);
|
||||
}
|
||||
|
||||
|
151
src/calibre/utils/fonts/woff/woff-private.h
Normal file
151
src/calibre/utils/fonts/woff/woff-private.h
Normal 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
|
1170
src/calibre/utils/fonts/woff/woff.c
Normal file
1170
src/calibre/utils/fonts/woff/woff.c
Normal file
File diff suppressed because it is too large
Load Diff
211
src/calibre/utils/fonts/woff/woff.h
Normal file
211
src/calibre/utils/fonts/woff/woff.h
Normal 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
|
@ -10,7 +10,7 @@ import os
|
||||
from calibre.utils.magick import Image, DrawingWand, create_canvas
|
||||
from calibre.constants import __appname__, __version__
|
||||
from calibre.utils.config import tweaks
|
||||
from calibre import fit_image
|
||||
from calibre import fit_image, force_unicode
|
||||
|
||||
def _data_to_image(data):
|
||||
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)
|
||||
|
||||
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.font = font_path
|
||||
if font_path is not None:
|
||||
ans.font = font_path
|
||||
ans.font_size = font_size
|
||||
ans.gravity = 'CenterGravity'
|
||||
ans.text_alias = True
|
||||
@ -238,6 +235,17 @@ class TextLine(object):
|
||||
def __init__(self, text, font_size, bottom_margin=30, font_path=None):
|
||||
self.text, self.font_size, = text, font_size
|
||||
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
|
||||
|
||||
def __repr__(self):
|
||||
|
@ -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):
|
||||
if log is None:
|
||||
log = Log()
|
||||
log = Log(level=Log.DEBUG) if options.verbose else Log()
|
||||
return RecursiveFetcher(options, log, image_map={})
|
||||
|
||||
def main(args=sys.argv):
|
||||
|
Loading…
x
Reference in New Issue
Block a user