diff --git a/recipes/boston.com.recipe b/recipes/boston.com.recipe
index 48add6112c..e8eb7d1a8b 100644
--- a/recipes/boston.com.recipe
+++ b/recipes/boston.com.recipe
@@ -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]
diff --git a/recipes/daily_mirror.recipe b/recipes/daily_mirror.recipe
index b53a22b648..bff337bcf7 100644
--- a/recipes/daily_mirror.recipe
+++ b/recipes/daily_mirror.recipe
@@ -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)
diff --git a/recipes/the_sun.recipe b/recipes/the_sun.recipe
index d7966c8289..3155bce3f2 100644
--- a/recipes/the_sun.recipe
+++ b/recipes/the_sun.recipe
@@ -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:
diff --git a/recipes/time_magazine.recipe b/recipes/time_magazine.recipe
index dfe897500e..9905a1df1d 100644
--- a/recipes/time_magazine.recipe
+++ b/recipes/time_magazine.recipe
@@ -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''), 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
+
diff --git a/recipes/yazihane.recipe b/recipes/yazihane.recipe
new file mode 100644
index 0000000000..941e3e8b3b
--- /dev/null
+++ b/recipes/yazihane.recipe
@@ -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/')]
diff --git a/resources/mime.types b/resources/mime.types
index 75452bb17a..f54443f30c 100644
--- a/resources/mime.types
+++ b/resources/mime.types
@@ -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
diff --git a/session.vim b/session.vim
index 6b8878b84d..079975f8d4 100644
--- a/session.vim
+++ b/session.vim
@@ -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',
\]
diff --git a/setup/build_environment.py b/setup/build_environment.py
index 58d2006ee7..6601578345 100644
--- a/setup/build_environment.py
+++ b/setup/build_environment.py
@@ -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)
diff --git a/setup/extensions.py b/setup/extensions.py
index 1827d32f4a..989a9ddbe9 100644
--- a/setup/extensions.py
+++ b/setup/extensions.py
@@ -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'],
diff --git a/src/calibre/__init__.py b/src/calibre/__init__.py
index 31ce61328a..1fce4a9da6 100644
--- a/src/calibre/__init__.py
+++ b/src/calibre/__init__.py
@@ -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'
diff --git a/src/calibre/constants.py b/src/calibre/constants.py
index 35b03f2b2f..6bc9995384 100644
--- a/src/calibre/constants.py
+++ b/src/calibre/constants.py
@@ -89,6 +89,8 @@ class Plugins(collections.Mapping):
'chm_extra',
'icu',
'speedup',
+ 'freetype',
+ 'woff',
]
if iswindows:
plugins.extend(['winutil', 'wpd', 'winfonts'])
diff --git a/src/calibre/ebooks/__init__.py b/src/calibre/ebooks/__init__.py
index ee880000f0..9cf0e51e7e 100644
--- a/src/calibre/ebooks/__init__.py
+++ b/src/calibre/ebooks/__init__.py
@@ -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)$')
diff --git a/src/calibre/ebooks/conversion/cli.py b/src/calibre/ebooks/conversion/cli.py
index fb1974f93b..2aa0add3ee 100644
--- a/src/calibre/ebooks/conversion/cli.py
+++ b/src/calibre/ebooks/conversion/cli.py
@@ -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',
diff --git a/src/calibre/ebooks/conversion/plumber.py b/src/calibre/ebooks/conversion/plumber.py
index 60cce24121..bfd2e36359 100644
--- a/src/calibre/ebooks/conversion/plumber.py
+++ b/src/calibre/ebooks/conversion/plumber.py
@@ -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 '
diff --git a/src/calibre/ebooks/metadata/fb2.py b/src/calibre/ebooks/metadata/fb2.py
index cecdfead0d..5a52ceb701 100644
--- a/src/calibre/ebooks/metadata/fb2.py
+++ b/src/calibre/ebooks/metadata/fb2.py
@@ -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'\n')
stream.write(etree.tostring(root, method='xml', encoding='utf-8',
- xml_declaration=True))
+ xml_declaration=False))
diff --git a/src/calibre/ebooks/oeb/base.py b/src/calibre/ebooks/oeb/base.py
index 2af72a07fb..50df05ed16 100644
--- a/src/calibre/ebooks/oeb/base.py
+++ b/src/calibre/ebooks/oeb/base.py
@@ -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']
diff --git a/src/calibre/ebooks/oeb/stylizer.py b/src/calibre/ebooks/oeb/stylizer.py
index 68978b9637..6b82f2f801 100644
--- a/src/calibre/ebooks/oeb/stylizer.py
+++ b/src/calibre/ebooks/oeb/stylizer.py
@@ -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)
diff --git a/src/calibre/ebooks/oeb/transforms/flatcss.py b/src/calibre/ebooks/oeb/transforms/flatcss.py
index 10b7e259ac..2f2fd727c0 100644
--- a/src/calibre/ebooks/oeb/transforms/flatcss.py
+++ b/src/calibre/ebooks/oeb/transforms/flatcss.py
@@ -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)
diff --git a/src/calibre/ebooks/oeb/transforms/split.py b/src/calibre/ebooks/oeb/transforms/split.py
index e46ddb5fb5..5b00400dbf 100644
--- a/src/calibre/ebooks/oeb/transforms/split.py
+++ b/src/calibre/ebooks/oeb/transforms/split.py
@@ -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')
diff --git a/src/calibre/gui2/__init__.py b/src/calibre/gui2/__init__.py
index 0ac0783bd5..688b134f0e 100644
--- a/src/calibre/gui2/__init__.py
+++ b/src/calibre/gui2/__init__.py
@@ -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:
diff --git a/src/calibre/gui2/convert/look_and_feel.py b/src/calibre/gui2/convert/look_and_feel.py
index ad604ec4e3..1609a0add3 100644
--- a/src/calibre/gui2/convert/look_and_feel.py
+++ b/src/calibre/gui2/convert/look_and_feel.py
@@ -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',
diff --git a/src/calibre/gui2/convert/look_and_feel.ui b/src/calibre/gui2/convert/look_and_feel.ui
index fae1cf2331..039625bc25 100644
--- a/src/calibre/gui2/convert/look_and_feel.ui
+++ b/src/calibre/gui2/convert/look_and_feel.ui
@@ -7,27 +7,53 @@
0
0
655
- 522
+ 619
Form
- -
-
-
- &Disable font size rescaling
+
-
+
+
+ pt
+
+
+ 1
- -
-
+
-
+
- Base &font size:
+ Line &height:
- opt_base_font_size
+ opt_line_height
+
+
+
+ -
+
+
+ Minimum &line height:
+
+
+ opt_minimum_line_height
+
+
+
+ -
+
+
+ %
+
+
+ 1
+
+
+ 900.000000000000000
@@ -97,49 +123,6 @@
- -
-
-
- Minimum &line height:
-
-
- opt_minimum_line_height
-
-
-
- -
-
-
- %
-
-
- 1
-
-
- 900.000000000000000
-
-
-
- -
-
-
- Line &height:
-
-
- opt_line_height
-
-
-
- -
-
-
- pt
-
-
- 1
-
-
-
-
@@ -157,14 +140,14 @@
- -
+
-
Remove &spacing between paragraphs
- -
+
-
&Indent size:
@@ -177,7 +160,7 @@
- -
+
-
<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.
@@ -199,85 +182,7 @@
- -
-
-
- Insert &blank line between paragraphs
-
-
-
- -
-
-
- &Line size:
-
-
- Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
-
-
- opt_insert_blank_line_size
-
-
-
- -
-
-
- em
-
-
- 1
-
-
-
- -
-
-
- Text &justification:
-
-
- opt_change_justification
-
-
-
- -
-
-
- -
-
-
- Smarten &punctuation
-
-
-
- -
-
-
- &Transliterate unicode characters to ASCII
-
-
-
- -
-
-
- &UnSmarten punctuation
-
-
-
- -
-
-
- Keep &ligatures
-
-
-
- -
-
-
- &Linearize tables
-
-
-
- -
+
-
0
@@ -378,10 +283,131 @@
+ -
+
+
+ Qt::Vertical
+
+
+
+ 20
+ 40
+
+
+
+
+ -
+
+
+ Insert &blank line between paragraphs
+
+
+
+ -
+
+
+ em
+
+
+ 1
+
+
+
+ -
+
+
+ Text &justification:
+
+
+ opt_change_justification
+
+
+
+ -
+
+
+ -
+
+
+ Smarten &punctuation
+
+
+
+ -
+
+
+ &Transliterate unicode characters to ASCII
+
+
+
+ -
+
+
+ &UnSmarten punctuation
+
+
+
+ -
+
+
+ Keep &ligatures
+
+
+
+ -
+
+
+ &Linearize tables
+
+
+
+ -
+
+
+ Base &font size:
+
+
+ opt_base_font_size
+
+
+
+ -
+
+
+ &Line size:
+
+
+ Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter
+
+
+ opt_insert_blank_line_size
+
+
+
+ -
+
+
+ &Embed font family:
+
+
+ opt_embed_font_family
+
+
+
+ -
+
+
+ &Disable font size rescaling
+
+
+
+ -
+
+
@@ -390,6 +416,11 @@
QComboBox
+
+ FontFamilyChooser
+ QComboBox
+ calibre/gui2/font_family_chooser.h
+
diff --git a/src/calibre/gui2/convert/regex_builder.py b/src/calibre/gui2/convert/regex_builder.py
index 3ce8ac36d5..f3c1f0a826 100644
--- a/src/calibre/gui2/convert/regex_builder.py
+++ b/src/calibre/gui2/convert/regex_builder.py
@@ -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
diff --git a/src/calibre/gui2/font_family_chooser.py b/src/calibre/gui2/font_family_chooser.py
index 04d9dfdfb6..b6514e5011 100644
--- a/src/calibre/gui2/font_family_chooser.py
+++ b/src/calibre/gui2/font_family_chooser.py
@@ -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()
+
diff --git a/src/calibre/gui2/library/models.py b/src/calibre/gui2/library/models.py
index 68031c8dd9..641ac23611 100644
--- a/src/calibre/gui2/library/models.py
+++ b/src/calibre/gui2/library/models.py
@@ -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
diff --git a/src/calibre/gui2/main_window.py b/src/calibre/gui2/main_window.py
index 134aae3ad1..88827fe646 100644
--- a/src/calibre/gui2/main_window.py
+++ b/src/calibre/gui2/main_window.py
@@ -139,3 +139,5 @@ class MainWindow(QMainWindow):
show=True)
except BaseException:
pass
+ except:
+ pass
diff --git a/src/calibre/gui2/metadata/basic_widgets.py b/src/calibre/gui2/metadata/basic_widgets.py
index 36605c7584..0da9b1bcf4 100644
--- a/src/calibre/gui2/metadata/basic_widgets.py
+++ b/src/calibre/gui2/metadata/basic_widgets.py
@@ -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
diff --git a/src/calibre/gui2/store/stores/sony_plugin.py b/src/calibre/gui2/store/stores/sony_plugin.py
index 2ad344e82c..aa0c65bcde 100644
--- a/src/calibre/gui2/store/stores/sony_plugin.py
+++ b/src/calibre/gui2/store/stores/sony_plugin.py
@@ -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
diff --git a/src/calibre/gui2/viewer/documentview.py b/src/calibre/gui2/viewer/documentview.py
index 8ea9c26d00..33b36cf6ec 100644
--- a/src/calibre/gui2/viewer/documentview.py
+++ b/src/calibre/gui2/viewer/documentview.py
@@ -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()
diff --git a/src/calibre/library/cli.py b/src/calibre/library/cli.py
index d9fe8af1ce..10265dd773 100644
--- a/src/calibre/library/cli.py
+++ b/src/calibre/library/cli.py
@@ -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
diff --git a/src/calibre/library/database2.py b/src/calibre/library/database2.py
index 5b4f7eec7e..5b2abd0c6b 100644
--- a/src/calibre/library/database2.py
+++ b/src/calibre/library/database2.py
@@ -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:
diff --git a/src/calibre/test_build.py b/src/calibre/test_build.py
index 35e050cf19..8ca0a36528 100644
--- a/src/calibre/test_build.py
+++ b/src/calibre/test_build.py
@@ -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()
diff --git a/src/calibre/utils/filenames.py b/src/calibre/utils/filenames.py
index 65451dab9c..f6455831ba 100644
--- a/src/calibre/utils/filenames.py
+++ b/src/calibre/utils/filenames.py
@@ -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
diff --git a/src/calibre/utils/fonts/__init__.py b/src/calibre/utils/fonts/__init__.py
index a5563acd4e..85bcd8eb39 100644
--- a/src/calibre/utils/fonts/__init__.py
+++ b/src/calibre/utils/fonts/__init__.py
@@ -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()
diff --git a/src/calibre/utils/fonts/fc.py b/src/calibre/utils/fonts/fc.py
index b6a4b1f906..793ee9b6b1 100644
--- a/src/calibre/utils/fonts/fc.py
+++ b/src/calibre/utils/fonts/fc.py
@@ -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__':
diff --git a/src/calibre/utils/fonts/fontconfig.c b/src/calibre/utils/fonts/fontconfig.c
index d8f7190798..be1cbdce13 100644
--- a/src/calibre/utils/fonts/fontconfig.c
+++ b/src/calibre/utils/fonts/fontconfig.c
@@ -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}
};
diff --git a/src/calibre/utils/fonts/free_type.py b/src/calibre/utils/fonts/free_type.py
new file mode 100644
index 0000000000..a2e8eca213
--- /dev/null
+++ b/src/calibre/utils/fonts/free_type.py
@@ -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 '
+__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()
+
diff --git a/src/calibre/utils/fonts/freetype.cpp b/src/calibre/utils/fonts/freetype.cpp
new file mode 100644
index 0000000000..e4e30000a0
--- /dev/null
+++ b/src/calibre/utils/fonts/freetype.cpp
@@ -0,0 +1,304 @@
+/*
+ * freetype.cpp
+ * Copyright (C) 2012 Kovid Goyal
+ *
+ * Distributed under terms of the GPL3 license.
+ */
+
+#define _UNICODE
+#define UNICODE
+#define PY_SSIZE_T_CLEAN
+#include
+
+#include
+#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);
+}
+
diff --git a/src/calibre/utils/fonts/utils.py b/src/calibre/utils/fonts/utils.py
index f20f238481..4fcaa20c44 100644
--- a/src/calibre/utils/fonts/utils.py
+++ b/src/calibre/utils/fonts/utils.py
@@ -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)
diff --git a/src/calibre/utils/fonts/win_fonts.py b/src/calibre/utils/fonts/win_fonts.py
index 747580d45e..9fcf5df226 100644
--- a/src/calibre/utils/fonts/win_fonts.py
+++ b/src/calibre/utils/fonts/win_fonts.py
@@ -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()
diff --git a/src/calibre/utils/fonts/woff/__init__.py b/src/calibre/utils/fonts/woff/__init__.py
new file mode 100644
index 0000000000..8684c03489
--- /dev/null
+++ b/src/calibre/utils/fonts/woff/__init__.py
@@ -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 '
+__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()
+
+
diff --git a/src/calibre/utils/fonts/woff/main.c b/src/calibre/utils/fonts/woff/main.c
new file mode 100644
index 0000000000..827c1365d5
--- /dev/null
+++ b/src/calibre/utils/fonts/woff/main.c
@@ -0,0 +1,108 @@
+/*
+ * main.c
+ * Copyright (C) 2012 Kovid Goyal
+ *
+ * Distributed under terms of the GPL3 license.
+ */
+
+
+#define _UNICODE
+#define UNICODE
+#define PY_SSIZE_T_CLEAN
+#include
+
+#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);
+}
+
+
diff --git a/src/calibre/utils/fonts/woff/woff-private.h b/src/calibre/utils/fonts/woff/woff-private.h
new file mode 100644
index 0000000000..8a41aa0c92
--- /dev/null
+++ b/src/calibre/utils/fonts/woff/woff-private.h
@@ -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
+ *
+ * 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
+# 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
diff --git a/src/calibre/utils/fonts/woff/woff.c b/src/calibre/utils/fonts/woff/woff.c
new file mode 100644
index 0000000000..4dcaadb00f
--- /dev/null
+++ b/src/calibre/utils/fonts/woff/woff.c
@@ -0,0 +1,1170 @@
+/* -*- 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
+ *
+ * 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 ***** */
+
+#include "woff-private.h"
+
+#include
+#include
+#include
+#include
+
+#ifdef WOFF_MOZILLA_CLIENT /* define this when building as part of Gecko */
+# include "prmem.h"
+# define malloc PR_Malloc
+# define realloc PR_Realloc
+# define free PR_Free
+#endif
+
+/*
+ * Just simple whole-file encoding and decoding functions; a more extensive
+ * WOFF library could provide support for accessing individual tables from a
+ * compressed font, alternative options for memory allocation/ownership and
+ * error handling, etc.
+ */
+
+/* on errors, each function sets a status variable and jumps to failure: */
+#undef FAIL
+#define FAIL(err) do { status |= err; goto failure; } while (0)
+
+/* adjust an offset for longword alignment */
+#define LONGALIGN(x) (((x) + 3) & ~3)
+
+static int
+compareOffsets(const void * lhs, const void * rhs)
+{
+ const tableOrderRec * a = (const tableOrderRec *) lhs;
+ const tableOrderRec * b = (const tableOrderRec *) rhs;
+ /* don't simply return a->offset - b->offset because these are unsigned
+ offset values; could convert to int, but possible integer overflow */
+ return a->offset > b->offset ? 1 :
+ a->offset < b->offset ? -1 :
+ 0;
+}
+
+#ifndef WOFF_MOZILLA_CLIENT
+
+/******************************************************************/
+/* * * * * * * * * * * * * * ENCODING * * * * * * * * * * * * * * */
+/******************************************************************/
+
+static uint32_t
+calcChecksum(const sfntDirEntry * dirEntry,
+ const uint8_t * sfntData, uint32_t sfntLen)
+{
+ /* just returns zero on errors, they will be detected again elsewhere */
+ const uint32_t * csumPtr;
+ const uint32_t * csumEnd;
+ uint32_t csum = 0;
+ uint32_t length = LONGALIGN(READ32BE(dirEntry->length));
+ uint32_t offset = READ32BE(dirEntry->offset);
+ uint32_t tag;
+ if ((offset & 3) != 0) {
+ return csum;
+ }
+ if (length > sfntLen || offset > sfntLen - length) {
+ return csum;
+ }
+ csumPtr = (const uint32_t *) (sfntData + offset);
+ csumEnd = csumPtr + length / 4;
+ while (csumPtr < csumEnd) {
+ csum += READ32BE(*csumPtr);
+ csumPtr++;
+ }
+ tag = READ32BE(dirEntry->tag);
+ if (tag == TABLE_TAG_head || tag == TABLE_TAG_bhed) {
+ const sfntHeadTable * head;
+ if (length < HEAD_TABLE_SIZE) {
+ return 0;
+ }
+ head = (const sfntHeadTable *)(sfntData + offset);
+ csum -= READ32BE(head->checkSumAdjustment);
+ }
+ return csum;
+}
+
+const uint8_t *
+woffEncode(const uint8_t * sfntData, uint32_t sfntLen,
+ uint16_t majorVersion, uint16_t minorVersion,
+ uint32_t * woffLen, uint32_t * pStatus)
+{
+ uint8_t * woffData = NULL;
+ tableOrderRec * tableOrder = NULL;
+
+ uint32_t tableOffset;
+ uint32_t totalSfntSize;
+
+ uint16_t numOrigTables;
+ uint16_t numTables;
+ uint16_t tableIndex;
+ uint16_t order;
+ const sfntDirEntry * sfntDir;
+ uint32_t tableBase;
+ uint32_t checkSumAdjustment = 0;
+ woffHeader * newHeader;
+ uint32_t tag = 0;
+ uint32_t removedDsigSize = 0;
+ uint32_t status = eWOFF_ok;
+
+ const sfntHeader * header = (const sfntHeader *) (sfntData);
+ const sfntHeadTable * head = NULL;
+
+ if (pStatus && WOFF_FAILURE(*pStatus)) {
+ return NULL;
+ }
+
+ if (READ32BE(header->version) != SFNT_VERSION_TT &&
+ READ32BE(header->version) != SFNT_VERSION_CFF &&
+ READ32BE(header->version) != SFNT_VERSION_true) {
+ status |= eWOFF_warn_unknown_version;
+ }
+
+ numOrigTables = READ16BE(header->numTables);
+ sfntDir = (const sfntDirEntry *) (sfntData + sizeof(sfntHeader));
+
+ for (tableIndex = 0; tableIndex < numOrigTables; ++tableIndex) {
+ /* validate table checksums, to figure out if we need to drop DSIG;
+ also check that table directory is correctly sorted */
+ uint32_t prevTag = tag;
+ uint32_t csum = calcChecksum(&sfntDir[tableIndex], sfntData, sfntLen);
+ if (csum != READ32BE(sfntDir[tableIndex].checksum)) {
+ status |= eWOFF_warn_checksum_mismatch;
+ }
+ checkSumAdjustment += csum;
+ tag = READ32BE(sfntDir[tableIndex].tag);
+ if (tag <= prevTag) {
+ FAIL(eWOFF_invalid);
+ }
+ if (tag == TABLE_TAG_head || tag == TABLE_TAG_bhed) {
+ if (READ32BE(sfntDir[tableIndex].length) < HEAD_TABLE_SIZE) {
+ FAIL(eWOFF_invalid);
+ }
+ head = (const sfntHeadTable *)(sfntData +
+ READ32BE(sfntDir[tableIndex].offset));
+ }
+ }
+ if (!head) {
+ FAIL(eWOFF_invalid);
+ }
+ if ((status & eWOFF_warn_checksum_mismatch) == 0) {
+ /* no point even checking if we already have an error,
+ as fixing that will change the overall checksum too */
+ const uint32_t * csumPtr = (const uint32_t *) sfntData;
+ const uint32_t * csumEnd = csumPtr + 3 + 4 * numOrigTables;
+ while (csumPtr < csumEnd) {
+ checkSumAdjustment += READ32BE(*csumPtr);
+ ++csumPtr;
+ }
+ checkSumAdjustment = 0xB1B0AFBA - checkSumAdjustment;
+ if (checkSumAdjustment != READ32BE(head->checkSumAdjustment)) {
+ status |= eWOFF_warn_checksum_mismatch;
+ }
+ }
+
+ /* Fixing checkSumAdjustment is tricky, because if there's a DSIG table,
+ we're going to have to remove that, which in turn means that table
+ offsets in the directory will all change.
+ And recalculating checkSumAdjustment requires taking account of any
+ individual table checksum corrections, but they have not actually been
+ applied to the sfnt data at this point.
+ And finally, we'd need to get the corrected checkSumAdjustment into the
+ encoded head table (but we can't modify the original sfnt data).
+ An easier way out seems to be to go ahead and encode the font, knowing
+ that checkSumAdjustment will be wrong; then (if the status flag
+ eWOFF_warn_checksum_mismatch is set) we'll decode the font back to
+ sfnt format. This will fix up the checkSumAdjustment (and return a
+ warning status). We'll ignore that warning, and then re-encode the
+ new, cleaned-up sfnt to get the final WOFF data. Perhaps not the most
+ efficient approach, but it seems simpler than trying to predict the
+ correct final checkSumAdjustment and incorporate it into the head
+ table on the fly. */
+
+ tableOrder = (tableOrderRec *) malloc(numOrigTables * sizeof(tableOrderRec));
+ if (!tableOrder) {
+ FAIL(eWOFF_out_of_memory);
+ }
+ for (tableIndex = 0, numTables = 0;
+ tableIndex < numOrigTables; ++tableIndex) {
+ if ((status & eWOFF_warn_checksum_mismatch) != 0) {
+ /* check for DSIG table that we must drop if we're fixing checksums */
+ tag = READ32BE(sfntDir[tableIndex].tag);
+ if (tag == TABLE_TAG_DSIG) {
+ status |= eWOFF_warn_removed_DSIG;
+ removedDsigSize = READ32BE(sfntDir[tableIndex].length);
+ continue;
+ }
+ }
+ tableOrder[numTables].offset = READ32BE(sfntDir[tableIndex].offset);
+ tableOrder[numTables].oldIndex = tableIndex;
+ tableOrder[numTables].newIndex = numTables;
+ ++numTables;
+ }
+ qsort(tableOrder, numTables, sizeof(tableOrderRec), compareOffsets);
+
+ /* initially, allocate space for header and directory */
+ tableOffset = sizeof(woffHeader) + numTables * sizeof(woffDirEntry);
+ woffData = (uint8_t *) malloc(tableOffset);
+ if (!woffData) {
+ FAIL(eWOFF_out_of_memory);
+ }
+
+ /* accumulator for total expected size of decoded font */
+ totalSfntSize = sizeof(sfntHeader) + numTables * sizeof(sfntDirEntry);
+
+/*
+ * We use a macro for this rather than creating a variable because woffData
+ * will get reallocated during encoding. The macro avoids the risk of using a
+ * stale pointer, and the compiler should optimize multiple successive uses.
+ */
+#define WOFFDIR ((woffDirEntry *) (woffData + sizeof(woffHeader)))
+
+ for (order = 0; order < numTables; ++order) {
+ uLong sourceLen, destLen;
+ uint32_t sourceOffset;
+
+ uint16_t oldIndex = tableOrder[order].oldIndex;
+ uint16_t newIndex = tableOrder[order].newIndex;
+
+ WOFFDIR[newIndex].tag = sfntDir[oldIndex].tag;
+ if ((status & eWOFF_warn_checksum_mismatch) != 0) {
+ uint32_t csum = calcChecksum(&sfntDir[oldIndex], sfntData, sfntLen);
+ WOFFDIR[newIndex].checksum = READ32BE(csum);
+ } else {
+ WOFFDIR[newIndex].checksum = sfntDir[oldIndex].checksum;
+ }
+ WOFFDIR[newIndex].origLen = sfntDir[oldIndex].length;
+ WOFFDIR[newIndex].offset = READ32BE(tableOffset);
+
+ /* allocate enough space for upper bound of compressed size */
+ sourceOffset = READ32BE(sfntDir[oldIndex].offset);
+ if ((sourceOffset & 3) != 0) {
+ status |= eWOFF_warn_misaligned_table;
+ }
+ sourceLen = READ32BE(sfntDir[oldIndex].length);
+ if (sourceLen > sfntLen || sourceOffset > sfntLen - sourceLen) {
+ FAIL(eWOFF_invalid);
+ }
+ destLen = LONGALIGN(compressBound(sourceLen));
+ woffData = (uint8_t *) realloc(woffData, tableOffset + destLen);
+ if (!woffData) {
+ FAIL(eWOFF_out_of_memory);
+ }
+
+ /* do the compression directly into the WOFF data block */
+ if (compress2((Bytef *) (woffData + tableOffset), &destLen,
+ (const Bytef *) (sfntData + sourceOffset),
+ sourceLen, 9) != Z_OK) {
+ FAIL(eWOFF_compression_failure);
+ }
+ if (destLen < sourceLen) {
+ /* compressed table was smaller */
+ tableOffset += destLen;
+ WOFFDIR[newIndex].compLen = READ32BE(destLen);
+ } else {
+ /* compression didn't make it smaller, so store original data instead */
+ destLen = sourceLen;
+ /* reallocate to ensure enough space for the table,
+ plus potential padding after it */
+ woffData = (uint8_t *) realloc(woffData,
+ tableOffset + LONGALIGN(sourceLen));
+ if (!woffData) {
+ FAIL(eWOFF_out_of_memory);
+ }
+ /* copy the original data into place */
+ memcpy(woffData + tableOffset,
+ sfntData + READ32BE(sfntDir[oldIndex].offset), sourceLen);
+ tableOffset += sourceLen;
+ WOFFDIR[newIndex].compLen = WOFFDIR[newIndex].origLen;
+ }
+
+ /* we always realloc woffData to a long-aligned size, so this is safe */
+ while ((tableOffset & 3) != 0) {
+ woffData[tableOffset++] = 0;
+ }
+
+ /* update total size of uncompressed OpenType with table size */
+ totalSfntSize += sourceLen;
+ totalSfntSize = LONGALIGN(totalSfntSize);
+ }
+
+ if (totalSfntSize > sfntLen) {
+ if (totalSfntSize > LONGALIGN(sfntLen)) {
+ FAIL(eWOFF_invalid);
+ } else {
+ status |= eWOFF_warn_unpadded_table;
+ }
+ } else if (totalSfntSize < sfntLen) {
+ /* check if the remaining data is a DSIG we're removing;
+ if so, we're already warning about that */
+ if ((status & eWOFF_warn_removed_DSIG) != 0 ||
+ sfntLen - totalSfntSize >
+ LONGALIGN(removedDsigSize) + sizeof(sfntDirEntry)) {
+ status |= eWOFF_warn_trailing_data;
+ }
+ }
+
+ /* write the header */
+ newHeader = (woffHeader *) (woffData);
+ newHeader->signature = WOFF_SIGNATURE;
+ newHeader->signature = READ32BE(newHeader->signature);
+ newHeader->flavor = header->version;
+ newHeader->length = READ32BE(tableOffset);
+ newHeader->numTables = READ16BE(numTables);
+ newHeader->reserved = 0;
+ newHeader->totalSfntSize = READ32BE(totalSfntSize);
+ newHeader->majorVersion = READ16BE(majorVersion);
+ newHeader->minorVersion = READ16BE(minorVersion);
+ newHeader->metaOffset = 0;
+ newHeader->metaCompLen = 0;
+ newHeader->metaOrigLen = 0;
+ newHeader->privOffset = 0;
+ newHeader->privLen = 0;
+
+ free(tableOrder);
+
+ if ((status & eWOFF_warn_checksum_mismatch) != 0) {
+ /* The original font had checksum errors, so we now decode our WOFF data
+ back to sfnt format (which fixes checkSumAdjustment), then re-encode
+ to get a clean copy. */
+ const uint8_t * cleanSfnt = woffDecode(woffData, tableOffset,
+ &sfntLen, &status);
+ if (WOFF_FAILURE(status)) {
+ FAIL(status);
+ }
+ free(woffData);
+ woffData = (uint8_t *) woffEncode(cleanSfnt, sfntLen,
+ majorVersion, minorVersion,
+ &tableOffset, &status);
+ free((void *) cleanSfnt);
+ if (WOFF_FAILURE(status)) {
+ FAIL(status);
+ }
+ }
+
+ if (woffLen) {
+ *woffLen = tableOffset;
+ }
+ if (pStatus) {
+ *pStatus |= status;
+ }
+ return woffData;
+
+failure:
+ if (tableOrder) {
+ free(tableOrder);
+ }
+ if (woffData) {
+ free(woffData);
+ }
+ if (pStatus) {
+ *pStatus = status;
+ }
+ return NULL;
+}
+
+static const uint8_t *
+rebuildWoff(const uint8_t * woffData, uint32_t * woffLen,
+ const uint8_t * metaData, uint32_t metaCompLen, uint32_t metaOrigLen,
+ const uint8_t * privData, uint32_t privLen, uint32_t * pStatus)
+{
+ const woffHeader * origHeader;
+ const woffDirEntry * woffDir;
+ uint8_t * newData = NULL;
+ uint8_t * tableData = NULL;
+ woffHeader * newHeader;
+ uint16_t numTables;
+ uint32_t tableLimit, totalSize, offset;
+ uint16_t i;
+ uint32_t status = eWOFF_ok;
+
+ if (*woffLen < sizeof(woffHeader)) {
+ FAIL(eWOFF_invalid);
+ }
+ origHeader = (const woffHeader *) (woffData);
+
+ if (READ32BE(origHeader->signature) != WOFF_SIGNATURE) {
+ FAIL(eWOFF_bad_signature);
+ }
+
+ numTables = READ16BE(origHeader->numTables);
+ woffDir = (const woffDirEntry *) (woffData + sizeof(woffHeader));
+ tableLimit = 0;
+ for (i = 0; i < numTables; ++i) {
+ uint32_t end = READ32BE(woffDir[i].offset) + READ32BE(woffDir[i].compLen);
+ if (end > tableLimit) {
+ tableLimit = end;
+ }
+ }
+ tableLimit = LONGALIGN(tableLimit);
+
+ /* check for broken input (meta/priv data before sfnt tables) */
+ offset = READ32BE(origHeader->metaOffset);
+ if (offset != 0 && offset < tableLimit) {
+ FAIL(eWOFF_illegal_order);
+ }
+ offset = READ32BE(origHeader->privOffset);
+ if (offset != 0 && offset < tableLimit) {
+ FAIL(eWOFF_illegal_order);
+ }
+
+ totalSize = tableLimit; /* already long-aligned */
+ if (metaCompLen) {
+ totalSize += metaCompLen;
+ }
+ if (privLen) {
+ totalSize = LONGALIGN(totalSize) + privLen;
+ }
+ newData = malloc(totalSize);
+ if (!newData) {
+ FAIL(eWOFF_out_of_memory);
+ }
+
+ /* copy the header, directory, and sfnt tables */
+ memcpy(newData, woffData, tableLimit);
+
+ /* then overwrite the header fields that should be changed */
+ newHeader = (woffHeader *) newData;
+ newHeader->length = READ32BE(totalSize);
+ newHeader->metaOffset = 0;
+ newHeader->metaCompLen = 0;
+ newHeader->metaOrigLen = 0;
+ newHeader->privOffset = 0;
+ newHeader->privLen = 0;
+
+ offset = tableLimit;
+ if (metaData && metaCompLen > 0 && metaOrigLen > 0) {
+ newHeader->metaOffset = READ32BE(offset);
+ newHeader->metaCompLen = READ32BE(metaCompLen);
+ newHeader->metaOrigLen = READ32BE(metaOrigLen);
+ memcpy(newData + offset, metaData, metaCompLen);
+ offset += metaCompLen;
+ }
+
+ if (privData && privLen > 0) {
+ while ((offset & 3) != 0) {
+ newData[offset++] = 0;
+ }
+ newHeader->privOffset = READ32BE(offset);
+ newHeader->privLen = READ32BE(privLen);
+ memcpy(newData + offset, privData, privLen);
+ offset += privLen;
+ }
+
+ *woffLen = offset;
+ free((void *) woffData);
+
+ if (pStatus) {
+ *pStatus |= status;
+ }
+ return newData;
+
+failure:
+ if (newData) {
+ free(newData);
+ }
+ if (pStatus) {
+ *pStatus = status;
+ }
+ return NULL;
+}
+
+const uint8_t *
+woffSetMetadata(const uint8_t * woffData, uint32_t * woffLen,
+ const uint8_t * metaData, uint32_t metaLen,
+ uint32_t * pStatus)
+{
+ const woffHeader * header;
+ uLong compLen = 0;
+ uint8_t * compData = NULL;
+ const uint8_t * privData = NULL;
+ uint32_t privLen = 0;
+ uint32_t status = eWOFF_ok;
+
+ if (pStatus && WOFF_FAILURE(*pStatus)) {
+ return NULL;
+ }
+
+ if (!woffData || !woffLen) {
+ FAIL(eWOFF_bad_parameter);
+ }
+
+ if (*woffLen < sizeof(woffHeader)) {
+ FAIL(eWOFF_invalid);
+ }
+ header = (const woffHeader *) (woffData);
+
+ if (READ32BE(header->signature) != WOFF_SIGNATURE) {
+ FAIL(eWOFF_bad_signature);
+ }
+
+ if (header->privOffset != 0 && header->privLen != 0) {
+ privData = woffData + READ32BE(header->privOffset);
+ privLen = READ32BE(header->privLen);
+ if (privData + privLen > woffData + *woffLen) {
+ FAIL(eWOFF_invalid);
+ }
+ }
+
+ if (metaData && metaLen > 0) {
+ compLen = compressBound(metaLen);
+ compData = malloc(compLen);
+ if (!compData) {
+ FAIL(eWOFF_out_of_memory);
+ }
+
+ if (compress2((Bytef *) compData, &compLen,
+ (const Bytef *) metaData, metaLen, 9) != Z_OK) {
+ FAIL(eWOFF_compression_failure);
+ }
+ }
+
+ woffData = rebuildWoff(woffData, woffLen,
+ compData, compLen, metaLen,
+ privData, privLen, pStatus);
+ free(compData);
+ return woffData;
+
+failure:
+ if (compData) {
+ free(compData);
+ }
+ if (pStatus) {
+ *pStatus = status;
+ }
+ return NULL;
+}
+
+const uint8_t *
+woffSetPrivateData(const uint8_t * woffData, uint32_t * woffLen,
+ const uint8_t * privData, uint32_t privLen,
+ uint32_t * pStatus)
+{
+ const woffHeader * header;
+ const uint8_t * metaData = NULL;
+ uint32_t metaLen = 0;
+ uint32_t status = eWOFF_ok;
+
+ if (pStatus && WOFF_FAILURE(*pStatus)) {
+ return NULL;
+ }
+
+ if (!woffData || !woffLen) {
+ FAIL(eWOFF_bad_parameter);
+ }
+
+ if (*woffLen < sizeof(woffHeader)) {
+ FAIL(eWOFF_invalid);
+ }
+ header = (const woffHeader *) (woffData);
+
+ if (READ32BE(header->signature) != WOFF_SIGNATURE) {
+ FAIL(eWOFF_bad_signature);
+ }
+
+ if (header->metaOffset != 0 && header->metaCompLen != 0) {
+ metaData = woffData + READ32BE(header->metaOffset);
+ metaLen = READ32BE(header->metaCompLen);
+ if (metaData + metaLen > woffData + *woffLen) {
+ FAIL(eWOFF_invalid);
+ }
+ }
+
+ woffData = rebuildWoff(woffData, woffLen,
+ metaData, metaLen, READ32BE(header->metaOrigLen),
+ privData, privLen, pStatus);
+ return woffData;
+
+failure:
+ if (pStatus) {
+ *pStatus = status;
+ }
+ return NULL;
+}
+
+#endif /* WOFF_MOZILLA_CLIENT */
+
+/******************************************************************/
+/* * * * * * * * * * * * * * DECODING * * * * * * * * * * * * * * */
+/******************************************************************/
+
+static uint32_t
+sanityCheck(const uint8_t * woffData, uint32_t woffLen)
+{
+ const woffHeader * header;
+ uint16_t numTables, i;
+ const woffDirEntry * dirEntry;
+ uint32_t tableTotal = 0;
+
+ if (!woffData || !woffLen) {
+ return eWOFF_bad_parameter;
+ }
+
+ if (woffLen < sizeof(woffHeader)) {
+ return eWOFF_invalid;
+ }
+
+ header = (const woffHeader *) (woffData);
+ if (READ32BE(header->signature) != WOFF_SIGNATURE) {
+ return eWOFF_bad_signature;
+ }
+
+ if (READ32BE(header->length) != woffLen || header->reserved != 0) {
+ return eWOFF_invalid;
+ }
+
+ numTables = READ16BE(header->numTables);
+ if (woffLen < sizeof(woffHeader) + numTables * sizeof(woffDirEntry)) {
+ return eWOFF_invalid;
+ }
+
+ dirEntry = (const woffDirEntry *) (woffData + sizeof(woffHeader));
+ for (i = 0; i < numTables; ++i) {
+ uint32_t offs = READ32BE(dirEntry->offset);
+ uint32_t orig = READ32BE(dirEntry->origLen);
+ uint32_t comp = READ32BE(dirEntry->compLen);
+ if (comp > orig || comp > woffLen || offs > woffLen - comp) {
+ return eWOFF_invalid;
+ }
+ orig = (orig + 3) & ~3;
+ if (tableTotal > 0xffffffffU - orig) {
+ return eWOFF_invalid;
+ }
+ tableTotal += orig;
+ ++dirEntry;
+ }
+
+ if (tableTotal > 0xffffffffU - sizeof(sfntHeader) -
+ numTables * sizeof(sfntDirEntry) ||
+ READ32BE(header->totalSfntSize) !=
+ tableTotal + sizeof(sfntHeader) + numTables * sizeof(sfntDirEntry)) {
+ return eWOFF_invalid;
+ }
+
+ return eWOFF_ok;
+}
+
+uint32_t
+woffGetDecodedSize(const uint8_t * woffData, uint32_t woffLen,
+ uint32_t * pStatus)
+{
+ uint32_t status = eWOFF_ok;
+ uint32_t totalLen = 0;
+
+ if (pStatus && WOFF_FAILURE(*pStatus)) {
+ return 0;
+ }
+
+ status = sanityCheck(woffData, woffLen);
+ if (WOFF_FAILURE(status)) {
+ FAIL(status);
+ }
+
+ totalLen = READ32BE(((const woffHeader *) (woffData))->totalSfntSize);
+ /* totalLen must be correctly rounded up to 4-byte alignment, otherwise
+ sanityCheck would have failed */
+
+failure:
+ if (pStatus) {
+ *pStatus = status;
+ }
+ return totalLen;
+}
+
+static void
+woffDecodeToBufferInternal(const uint8_t * woffData, uint32_t woffLen,
+ uint8_t * sfntData, uint32_t bufferLen,
+ uint32_t * pActualSfntLen, uint32_t * pStatus)
+{
+ /* this is only called after sanityCheck has verified that
+ (a) basic header fields are ok
+ (b) all the WOFF table offset/length pairs are valid (within the data)
+ (c) the sum of original sizes + header/directory matches totalSfntSize
+ so we don't have to re-check those overflow conditions here */
+ tableOrderRec * tableOrder = NULL;
+ const woffHeader * header;
+ uint16_t numTables;
+ uint16_t tableIndex;
+ uint16_t order;
+ const woffDirEntry * woffDir;
+ uint32_t totalLen;
+ sfntHeader * newHeader;
+ uint16_t searchRange, rangeShift, entrySelector;
+ uint32_t offset;
+ sfntDirEntry * sfntDir;
+ uint32_t headOffset = 0, headLength = 0;
+ sfntHeadTable * head;
+ uint32_t csum = 0;
+ const uint32_t * csumPtr;
+ uint32_t oldCheckSumAdjustment;
+ uint32_t status = eWOFF_ok;
+
+ if (pStatus && WOFF_FAILURE(*pStatus)) {
+ return;
+ }
+
+ /* check basic header fields */
+ header = (const woffHeader *) (woffData);
+ if (READ32BE(header->flavor) != SFNT_VERSION_TT &&
+ READ32BE(header->flavor) != SFNT_VERSION_CFF &&
+ READ32BE(header->flavor) != SFNT_VERSION_true) {
+ status |= eWOFF_warn_unknown_version;
+ }
+
+ numTables = READ16BE(header->numTables);
+ woffDir = (const woffDirEntry *) (woffData + sizeof(woffHeader));
+
+ totalLen = READ32BE(header->totalSfntSize);
+
+ /* construct the sfnt header */
+ newHeader = (sfntHeader *) (sfntData);
+ newHeader->version = header->flavor;
+ newHeader->numTables = READ16BE(numTables);
+
+ /* calculate header fields for binary search */
+ searchRange = numTables;
+ searchRange |= (searchRange >> 1);
+ searchRange |= (searchRange >> 2);
+ searchRange |= (searchRange >> 4);
+ searchRange |= (searchRange >> 8);
+ searchRange &= ~(searchRange >> 1);
+ searchRange *= 16;
+ newHeader->searchRange = READ16BE(searchRange);
+ rangeShift = numTables * 16 - searchRange;
+ newHeader->rangeShift = READ16BE(rangeShift);
+ entrySelector = 0;
+ while (searchRange > 16) {
+ ++entrySelector;
+ searchRange >>= 1;
+ }
+ newHeader->entrySelector = READ16BE(entrySelector);
+
+ tableOrder = (tableOrderRec *) malloc(numTables * sizeof(tableOrderRec));
+ if (!tableOrder) {
+ FAIL(eWOFF_out_of_memory);
+ }
+ for (tableIndex = 0; tableIndex < numTables; ++tableIndex) {
+ tableOrder[tableIndex].offset = READ32BE(woffDir[tableIndex].offset);
+ tableOrder[tableIndex].oldIndex = tableIndex;
+ }
+ qsort(tableOrder, numTables, sizeof(tableOrderRec), compareOffsets);
+
+ /* process each table, filling in the sfnt directory */
+ offset = sizeof(sfntHeader) + numTables * sizeof(sfntDirEntry);
+ sfntDir = (sfntDirEntry *) (sfntData + sizeof(sfntHeader));
+ for (order = 0; order < numTables; ++order) {
+ uint32_t origLen, compLen, tag, sourceOffset;
+ tableIndex = tableOrder[order].oldIndex;
+
+ /* validity of these was confirmed by sanityCheck */
+ origLen = READ32BE(woffDir[tableIndex].origLen);
+ compLen = READ32BE(woffDir[tableIndex].compLen);
+ sourceOffset = READ32BE(woffDir[tableIndex].offset);
+
+ sfntDir[tableIndex].tag = woffDir[tableIndex].tag;
+ sfntDir[tableIndex].offset = READ32BE(offset);
+ sfntDir[tableIndex].length = woffDir[tableIndex].origLen;
+ sfntDir[tableIndex].checksum = woffDir[tableIndex].checksum;
+ csum += READ32BE(sfntDir[tableIndex].checksum);
+
+ if (compLen < origLen) {
+ uLongf destLen = origLen;
+ if (uncompress((Bytef *)(sfntData + offset), &destLen,
+ (const Bytef *)(woffData + sourceOffset),
+ compLen) != Z_OK || destLen != origLen) {
+ FAIL(eWOFF_compression_failure);
+ }
+ } else {
+ memcpy(sfntData + offset, woffData + sourceOffset, origLen);
+ }
+
+ /* note that old Mac bitmap-only fonts have no 'head' table
+ (eg NISC18030.ttf) but a 'bhed' table instead */
+ tag = READ32BE(sfntDir[tableIndex].tag);
+ if (tag == TABLE_TAG_head || tag == TABLE_TAG_bhed) {
+ headOffset = offset;
+ headLength = origLen;
+ }
+
+ offset += origLen;
+
+ while (offset < totalLen && (offset & 3) != 0) {
+ sfntData[offset++] = 0;
+ }
+ }
+
+ if (headOffset > 0) {
+ /* the font checksum in the 'head' table depends on all the individual
+ table checksums (collected above), plus the header and directory
+ which are added in here */
+ if (headLength < HEAD_TABLE_SIZE) {
+ FAIL(eWOFF_invalid);
+ }
+ head = (sfntHeadTable *)(sfntData + headOffset);
+ oldCheckSumAdjustment = READ32BE(head->checkSumAdjustment);
+ head->checkSumAdjustment = 0;
+ csumPtr = (const uint32_t *)sfntData;
+ while (csumPtr < (const uint32_t *)(sfntData + sizeof(sfntHeader) +
+ numTables * sizeof(sfntDirEntry))) {
+ csum += READ32BE(*csumPtr);
+ csumPtr++;
+ }
+ csum = SFNT_CHECKSUM_CALC_CONST - csum;
+
+ if (oldCheckSumAdjustment != csum) {
+ /* if the checksum doesn't match, we fix it; but this will invalidate
+ any DSIG that may be present */
+ status |= eWOFF_warn_checksum_mismatch;
+ }
+ head->checkSumAdjustment = READ32BE(csum);
+ }
+
+ if (pActualSfntLen) {
+ *pActualSfntLen = totalLen;
+ }
+ if (pStatus) {
+ *pStatus |= status;
+ }
+ free(tableOrder);
+ return;
+
+failure:
+ if (tableOrder) {
+ free(tableOrder);
+ }
+ if (pActualSfntLen) {
+ *pActualSfntLen = 0;
+ }
+ if (pStatus) {
+ *pStatus = status;
+ }
+}
+
+void
+woffDecodeToBuffer(const uint8_t * woffData, uint32_t woffLen,
+ uint8_t * sfntData, uint32_t bufferLen,
+ uint32_t * pActualSfntLen, uint32_t * pStatus)
+{
+ uint32_t status = eWOFF_ok;
+ uint32_t totalLen;
+
+ if (pStatus && WOFF_FAILURE(*pStatus)) {
+ return;
+ }
+
+ status = sanityCheck(woffData, woffLen);
+ if (WOFF_FAILURE(status)) {
+ FAIL(status);
+ }
+
+ if (!sfntData) {
+ FAIL(eWOFF_bad_parameter);
+ }
+
+ totalLen = READ32BE(((const woffHeader *) (woffData))->totalSfntSize);
+ if (bufferLen < totalLen) {
+ FAIL(eWOFF_buffer_too_small);
+ }
+
+ woffDecodeToBufferInternal(woffData, woffLen, sfntData, bufferLen,
+ pActualSfntLen, pStatus);
+ return;
+
+failure:
+ if (pActualSfntLen) {
+ *pActualSfntLen = 0;
+ }
+ if (pStatus) {
+ *pStatus = status;
+ }
+}
+
+const uint8_t *
+woffDecode(const uint8_t * woffData, uint32_t woffLen,
+ uint32_t * sfntLen, uint32_t * pStatus)
+{
+ uint32_t status = eWOFF_ok;
+ uint8_t * sfntData = NULL;
+ uint32_t bufLen;
+
+ if (pStatus && WOFF_FAILURE(*pStatus)) {
+ return NULL;
+ }
+
+ status = sanityCheck(woffData, woffLen);
+ if (WOFF_FAILURE(status)) {
+ FAIL(status);
+ }
+
+ bufLen = READ32BE(((const woffHeader *) (woffData))->totalSfntSize);
+ sfntData = (uint8_t *) malloc(bufLen);
+ if (!sfntData) {
+ FAIL(eWOFF_out_of_memory);
+ }
+
+ woffDecodeToBufferInternal(woffData, woffLen, sfntData, bufLen,
+ sfntLen, &status);
+ if (WOFF_FAILURE(status)) {
+ FAIL(status);
+ }
+
+ if (pStatus) {
+ *pStatus |= status;
+ }
+ return sfntData;
+
+failure:
+ if (sfntData) {
+ free(sfntData);
+ }
+ if (pStatus) {
+ *pStatus = status;
+ }
+ return NULL;
+}
+
+#ifndef WOFF_MOZILLA_CLIENT
+
+const uint8_t *
+woffGetMetadata(const uint8_t * woffData, uint32_t woffLen,
+ uint32_t * metaLen, uint32_t * pStatus)
+{
+ const woffHeader * header;
+ uint32_t offset, compLen;
+ uLong origLen;
+ uint8_t * data = NULL;
+ uint32_t status = eWOFF_ok;
+
+ if (pStatus && WOFF_FAILURE(*pStatus)) {
+ return NULL;
+ }
+
+ status = sanityCheck(woffData, woffLen);
+ if (WOFF_FAILURE(status)) {
+ FAIL(status);
+ }
+
+ header = (const woffHeader *) (woffData);
+
+ offset = READ32BE(header->metaOffset);
+ compLen = READ32BE(header->metaCompLen);
+ origLen = READ32BE(header->metaOrigLen);
+ if (offset == 0 || compLen == 0 || origLen == 0) {
+ return NULL;
+ }
+
+ if (compLen > woffLen || offset > woffLen - compLen) {
+ FAIL(eWOFF_invalid);
+ }
+
+ data = malloc(origLen);
+ if (!data) {
+ FAIL(eWOFF_out_of_memory);
+ }
+
+ if (uncompress((Bytef *)data, &origLen,
+ (const Bytef *)woffData + offset, compLen) != Z_OK ||
+ origLen != READ32BE(header->metaOrigLen)) {
+ FAIL(eWOFF_compression_failure);
+ }
+
+ if (metaLen) {
+ *metaLen = origLen;
+ }
+ if (pStatus) {
+ *pStatus |= status;
+ }
+ return data;
+
+failure:
+ if (data) {
+ free(data);
+ }
+ if (pStatus) {
+ *pStatus = status;
+ }
+ return NULL;
+}
+
+const uint8_t *
+woffGetPrivateData(const uint8_t * woffData, uint32_t woffLen,
+ uint32_t * privLen, uint32_t * pStatus)
+{
+ const woffHeader * header;
+ uint32_t offset, length;
+ uint8_t * data = NULL;
+ uint32_t status = eWOFF_ok;
+
+ if (pStatus && WOFF_FAILURE(*pStatus)) {
+ return NULL;
+ }
+
+ status = sanityCheck(woffData, woffLen);
+ if (WOFF_FAILURE(status)) {
+ FAIL(status);
+ }
+
+ header = (const woffHeader *) (woffData);
+
+ offset = READ32BE(header->privOffset);
+ length = READ32BE(header->privLen);
+ if (offset == 0 || length == 0) {
+ return NULL;
+ }
+
+ if (length > woffLen || offset > woffLen - length) {
+ FAIL(eWOFF_invalid);
+ }
+
+ data = malloc(length);
+ if (!data) {
+ FAIL(eWOFF_out_of_memory);
+ }
+
+ memcpy(data, woffData + offset, length);
+
+ if (privLen) {
+ *privLen = length;
+ }
+ if (pStatus) {
+ *pStatus |= status;
+ }
+ return data;
+
+failure:
+ if (data) {
+ free(data);
+ }
+ if (pStatus) {
+ *pStatus = status;
+ }
+ return NULL;
+}
+
+void
+woffGetFontVersion(const uint8_t * woffData, uint32_t woffLen,
+ uint16_t * major, uint16_t * minor, uint32_t * pStatus)
+{
+ const woffHeader * header;
+ uint32_t status = eWOFF_ok;
+
+ if (pStatus && WOFF_FAILURE(*pStatus)) {
+ return;
+ }
+
+ status = sanityCheck(woffData, woffLen);
+ if (WOFF_FAILURE(status)) {
+ FAIL(status);
+ }
+
+ if (!major || !minor) {
+ FAIL(eWOFF_bad_parameter);
+ }
+
+ *major = *minor = 0;
+
+ header = (const woffHeader *) (woffData);
+
+ *major = READ16BE(header->majorVersion);
+ *minor = READ16BE(header->minorVersion);
+
+failure:
+ if (pStatus) {
+ *pStatus = status;
+ }
+}
+
+/* utility to print messages corresponding to WOFF encoder/decoder errors */
+void
+woffPrintStatus(FILE * f, uint32_t status, const char * prefix)
+{
+ if (!prefix) {
+ prefix = "";
+ }
+ if (WOFF_WARNING(status)) {
+ const char * template = "%sWOFF warning: %s\n";
+ if (status & eWOFF_warn_unknown_version) {
+ fprintf(f, template, prefix, "unrecognized sfnt version");
+ }
+ if (status & eWOFF_warn_checksum_mismatch) {
+ fprintf(f, template, prefix, "checksum mismatch (corrected)");
+ }
+ if (status & eWOFF_warn_misaligned_table) {
+ fprintf(f, template, prefix, "misaligned font table");
+ }
+ if (status & eWOFF_warn_trailing_data) {
+ fprintf(f, template, prefix, "extraneous input data discarded");
+ }
+ if (status & eWOFF_warn_unpadded_table) {
+ fprintf(f, template, prefix, "final table not correctly padded");
+ }
+ if (status & eWOFF_warn_removed_DSIG) {
+ fprintf(f, template, prefix, "digital signature (DSIG) table removed");
+ }
+ }
+ if (WOFF_FAILURE(status)) {
+ const char * template = "%sWOFF error: %s\n";
+ const char * msg;
+ switch (status & 0xff) {
+ case eWOFF_out_of_memory:
+ msg = "memory allocation failure";
+ break;
+ case eWOFF_invalid:
+ msg = "invalid input font";
+ break;
+ case eWOFF_compression_failure:
+ msg = "zlib compression/decompression failure";
+ break;
+ case eWOFF_bad_signature:
+ msg = "incorrect WOFF file signature";
+ break;
+ case eWOFF_buffer_too_small:
+ msg = "buffer too small";
+ break;
+ case eWOFF_bad_parameter:
+ msg = "bad parameter to WOFF function";
+ break;
+ case eWOFF_illegal_order:
+ msg = "incorrect table directory order";
+ break;
+ default:
+ msg = "unknown internal error";
+ break;
+ }
+ fprintf(f, template, prefix, msg);
+ }
+}
+
+#endif /* not WOFF_MOZILLA_CLIENT */
diff --git a/src/calibre/utils/fonts/woff/woff.h b/src/calibre/utils/fonts/woff/woff.h
new file mode 100644
index 0000000000..d8c6f55b08
--- /dev/null
+++ b/src/calibre/utils/fonts/woff/woff.h
@@ -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
+ *
+ * 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
+#endif
+
+#include /* 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
diff --git a/src/calibre/utils/magick/draw.py b/src/calibre/utils/magick/draw.py
index 046d0d5224..9d8cfdfcbf 100644
--- a/src/calibre/utils/magick/draw.py
+++ b/src/calibre/utils/magick/draw.py
@@ -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):
diff --git a/src/calibre/web/fetch/simple.py b/src/calibre/web/fetch/simple.py
index 5480e3f683..bea45f1c8d 100644
--- a/src/calibre/web/fetch/simple.py
+++ b/src/calibre/web/fetch/simple.py
@@ -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):