mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Conversion: Add option to embed all referenced fonts
Conversion: Add an option to embed all fonts that are referenced in the input document but are not already embedded. This will search your system for the referenced font, and if found, the font will be embedded. Only works if the output format supports font embedding (for example: EPUB or AZW3).
This commit is contained in:
parent
30cea5df3a
commit
03452d2a03
@ -136,7 +136,7 @@ def add_pipeline_options(parser, plumber):
|
||||
[
|
||||
'base_font_size', 'disable_font_rescaling',
|
||||
'font_size_mapping', 'embed_font_family',
|
||||
'subset_embedded_fonts',
|
||||
'subset_embedded_fonts', 'embed_all_fonts',
|
||||
'line_height', 'minimum_line_height',
|
||||
'linearize_tables',
|
||||
'extra_css', 'filter_css',
|
||||
@ -320,7 +320,7 @@ def main(args=sys.argv):
|
||||
opts.search_replace = read_sr_patterns(opts.search_replace, log)
|
||||
|
||||
recommendations = [(n.dest, getattr(opts, n.dest),
|
||||
OptionRecommendation.HIGH) \
|
||||
OptionRecommendation.HIGH)
|
||||
for n in parser.options_iter()
|
||||
if n.dest]
|
||||
plumber.merge_ui_recommendations(recommendations)
|
||||
@ -342,3 +342,4 @@ def main(args=sys.argv):
|
||||
|
||||
if __name__ == '__main__':
|
||||
sys.exit(main())
|
||||
|
||||
|
@ -205,6 +205,16 @@ OptionRecommendation(name='embed_font_family',
|
||||
'with some output formats, principally EPUB and AZW3.')
|
||||
),
|
||||
|
||||
OptionRecommendation(name='embed_all_fonts',
|
||||
recommended_value=False, level=OptionRecommendation.LOW,
|
||||
help=_(
|
||||
'Embed every font that is referenced in the input document '
|
||||
'but not already embedded. This will search your system for the '
|
||||
'fonts, and if found, they will be embedded. Embedding will only work '
|
||||
'if the format you are converting to supports embedded fonts, such as '
|
||||
'EPUB, AZW3 or PDF.'
|
||||
)),
|
||||
|
||||
OptionRecommendation(name='subset_embedded_fonts',
|
||||
recommended_value=False, level=OptionRecommendation.LOW,
|
||||
help=_(
|
||||
@ -965,6 +975,9 @@ OptionRecommendation(name='search_replace',
|
||||
if self.for_regex_wizard and hasattr(self.opts, 'no_process'):
|
||||
self.opts.no_process = True
|
||||
self.flush()
|
||||
if self.opts.embed_all_fonts or self.opts.embed_font_family:
|
||||
# Start the threaded font scanner now, for performance
|
||||
from calibre.utils.fonts.scanner import font_scanner # noqa
|
||||
import cssutils, logging
|
||||
cssutils.log.setLevel(logging.WARN)
|
||||
get_types_map() # Ensure the mimetypes module is intialized
|
||||
@ -1129,6 +1142,10 @@ OptionRecommendation(name='search_replace',
|
||||
RemoveFakeMargins()(self.oeb, self.log, self.opts)
|
||||
RemoveAdobeMargins()(self.oeb, self.log, self.opts)
|
||||
|
||||
if self.opts.embed_all_fonts:
|
||||
from calibre.ebooks.oeb.transforms.embed_fonts import EmbedFonts
|
||||
EmbedFonts()(self.oeb, self.log, self.opts)
|
||||
|
||||
if self.opts.subset_embedded_fonts and self.output_plugin.file_type != 'pdf':
|
||||
from calibre.ebooks.oeb.transforms.subset import SubsetFonts
|
||||
SubsetFonts()(self.oeb, self.log, self.opts)
|
||||
|
233
src/calibre/ebooks/oeb/transforms/embed_fonts.py
Normal file
233
src/calibre/ebooks/oeb/transforms/embed_fonts.py
Normal file
@ -0,0 +1,233 @@
|
||||
#!/usr/bin/env python
|
||||
# vim:fileencoding=utf-8
|
||||
from __future__ import (unicode_literals, division, absolute_import,
|
||||
print_function)
|
||||
|
||||
__license__ = 'GPL v3'
|
||||
__copyright__ = '2013, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||
|
||||
import logging
|
||||
from collections import defaultdict
|
||||
|
||||
import cssutils
|
||||
from lxml import etree
|
||||
|
||||
from calibre import guess_type
|
||||
from calibre.ebooks.oeb.base import XPath, CSS_MIME, XHTML
|
||||
from calibre.ebooks.oeb.transforms.subset import get_font_properties, find_font_face_rules, elem_style
|
||||
from calibre.utils.filenames import ascii_filename
|
||||
from calibre.utils.fonts.scanner import font_scanner, NoFonts
|
||||
|
||||
def used_font(style, embedded_fonts):
|
||||
ff = [unicode(f) for f in style.get('font-family', []) if unicode(f).lower() not in {
|
||||
'serif', 'sansserif', 'sans-serif', 'fantasy', 'cursive', 'monospace'}]
|
||||
if not ff:
|
||||
return False, None
|
||||
lnames = {unicode(x).lower() for x in ff}
|
||||
|
||||
matching_set = []
|
||||
|
||||
# Filter on font-family
|
||||
for ef in embedded_fonts:
|
||||
flnames = {x.lower() for x in ef.get('font-family', [])}
|
||||
if not lnames.intersection(flnames):
|
||||
continue
|
||||
matching_set.append(ef)
|
||||
if not matching_set:
|
||||
return True, None
|
||||
|
||||
# Filter on font-stretch
|
||||
widths = {x:i for i, x in enumerate(('ultra-condensed',
|
||||
'extra-condensed', 'condensed', 'semi-condensed', 'normal',
|
||||
'semi-expanded', 'expanded', 'extra-expanded', 'ultra-expanded'
|
||||
))}
|
||||
|
||||
width = widths[style.get('font-stretch', 'normal')]
|
||||
for f in matching_set:
|
||||
f['width'] = widths[style.get('font-stretch', 'normal')]
|
||||
|
||||
min_dist = min(abs(width-f['width']) for f in matching_set)
|
||||
if min_dist > 0:
|
||||
return True, None
|
||||
nearest = [f for f in matching_set if abs(width-f['width']) ==
|
||||
min_dist]
|
||||
if width <= 4:
|
||||
lmatches = [f for f in nearest if f['width'] <= width]
|
||||
else:
|
||||
lmatches = [f for f in nearest if f['width'] >= width]
|
||||
matching_set = (lmatches or nearest)
|
||||
|
||||
# Filter on font-style
|
||||
fs = style.get('font-style', 'normal')
|
||||
matching_set = [f for f in matching_set if f.get('font-style', 'normal') == fs]
|
||||
|
||||
# Filter on font weight
|
||||
fw = int(style.get('font-weight', '400'))
|
||||
matching_set = [f for f in matching_set if f.get('weight', 400) == fw]
|
||||
|
||||
if not matching_set:
|
||||
return True, None
|
||||
return True, matching_set[0]
|
||||
|
||||
|
||||
class EmbedFonts(object):
|
||||
|
||||
'''
|
||||
Embed all referenced fonts, if found on system. Must be called after CSS flattening.
|
||||
'''
|
||||
|
||||
def __call__(self, oeb, log, opts):
|
||||
self.oeb, self.log, self.opts = oeb, log, opts
|
||||
self.sheet_cache = {}
|
||||
self.find_style_rules()
|
||||
self.find_embedded_fonts()
|
||||
self.parser = cssutils.CSSParser(loglevel=logging.CRITICAL, log=logging.getLogger('calibre.css'))
|
||||
self.warned = set()
|
||||
self.warned2 = set()
|
||||
|
||||
for item in oeb.spine:
|
||||
if not hasattr(item.data, 'xpath'):
|
||||
continue
|
||||
sheets = []
|
||||
for href in XPath('//h:link[@href and @type="text/css"]/@href')(item.data):
|
||||
sheet = self.oeb.manifest.hrefs.get(item.abshref(href), None)
|
||||
if sheet is not None:
|
||||
sheets.append(sheet)
|
||||
if sheets:
|
||||
self.process_item(item, sheets)
|
||||
|
||||
def find_embedded_fonts(self):
|
||||
'''
|
||||
Find all @font-face rules and extract the relevant info from them.
|
||||
'''
|
||||
self.embedded_fonts = []
|
||||
for item in self.oeb.manifest:
|
||||
if not hasattr(item.data, 'cssRules'):
|
||||
continue
|
||||
self.embedded_fonts.extend(find_font_face_rules(item, self.oeb))
|
||||
|
||||
def find_style_rules(self):
|
||||
'''
|
||||
Extract all font related style information from all stylesheets into a
|
||||
dict mapping classes to font properties specified by that class. All
|
||||
the heavy lifting has already been done by the CSS flattening code.
|
||||
'''
|
||||
rules = defaultdict(dict)
|
||||
for item in self.oeb.manifest:
|
||||
if not hasattr(item.data, 'cssRules'):
|
||||
continue
|
||||
for i, rule in enumerate(item.data.cssRules):
|
||||
if rule.type != rule.STYLE_RULE:
|
||||
continue
|
||||
props = {k:v for k,v in
|
||||
get_font_properties(rule).iteritems() if v}
|
||||
if not props:
|
||||
continue
|
||||
for sel in rule.selectorList:
|
||||
sel = sel.selectorText
|
||||
if sel and sel.startswith('.'):
|
||||
# We dont care about pseudo-selectors as the worst that
|
||||
# can happen is some extra characters will remain in
|
||||
# the font
|
||||
sel = sel.partition(':')[0]
|
||||
rules[sel[1:]].update(props)
|
||||
|
||||
self.style_rules = dict(rules)
|
||||
|
||||
def get_page_sheet(self):
|
||||
if self.page_sheet is None:
|
||||
manifest = self.oeb.manifest
|
||||
id_, href = manifest.generate('page_css', 'page_styles.css')
|
||||
self.page_sheet = manifest.add(id_, href, CSS_MIME, data=self.parser.parseString('', validate=False))
|
||||
head = self.current_item.xpath('//*[local-name()="head"][1]')
|
||||
if head:
|
||||
href = self.current_item.relhref(href)
|
||||
l = etree.SubElement(head[0], XHTML('link'),
|
||||
rel='stylesheet', type=CSS_MIME, href=href)
|
||||
l.tail = '\n'
|
||||
else:
|
||||
self.log.warn('No <head> cannot embed font rules')
|
||||
return self.page_sheet
|
||||
|
||||
def process_item(self, item, sheets):
|
||||
ff_rules = []
|
||||
self.current_item = item
|
||||
self.page_sheet = None
|
||||
for sheet in sheets:
|
||||
if 'page_css' in sheet.id:
|
||||
ff_rules.extend(find_font_face_rules(sheet, self.oeb))
|
||||
self.page_sheet = sheet
|
||||
|
||||
base = {'font-family':['serif'], 'font-weight': '400',
|
||||
'font-style':'normal', 'font-stretch':'normal'}
|
||||
|
||||
for body in item.data.xpath('//*[local-name()="body"]'):
|
||||
self.find_usage_in(body, base, ff_rules)
|
||||
|
||||
def find_usage_in(self, elem, inherited_style, ff_rules):
|
||||
style = elem_style(self.style_rules, elem.get('class', '') or '', inherited_style)
|
||||
for child in elem:
|
||||
self.find_usage_in(child, style, ff_rules)
|
||||
has_font, existing = used_font(style, ff_rules)
|
||||
if not has_font:
|
||||
return
|
||||
if existing is None:
|
||||
in_book = used_font(style, self.embedded_fonts)[1]
|
||||
if in_book is None:
|
||||
# Try to find the font in the system
|
||||
added = self.embed_font(style)
|
||||
if added is not None:
|
||||
ff_rules.append(added)
|
||||
self.embedded_fonts.append(added)
|
||||
else:
|
||||
# TODO: Create a page rule from the book rule (cannot use it
|
||||
# directly as paths might be different)
|
||||
item = in_book['item']
|
||||
sheet = self.parser.parseString(in_book['rule'].cssText, validate=False)
|
||||
rule = sheet.cssRules[0]
|
||||
page_sheet = self.get_page_sheet()
|
||||
href = page_sheet.abshref(item.href)
|
||||
rule.style.setProperty('src', 'url(%s)' % href)
|
||||
ff_rules.append(find_font_face_rules(sheet, self.oeb)[0])
|
||||
page_sheet.data.insertRule(rule, len(page_sheet.data.cssRules))
|
||||
|
||||
def embed_font(self, style):
|
||||
ff = [unicode(f) for f in style.get('font-family', []) if unicode(f).lower() not in {
|
||||
'serif', 'sansserif', 'sans-serif', 'fantasy', 'cursive', 'monospace'}]
|
||||
if not ff:
|
||||
return
|
||||
ff = ff[0]
|
||||
if ff in self.warned:
|
||||
return
|
||||
try:
|
||||
fonts = font_scanner.fonts_for_family(ff)
|
||||
except NoFonts:
|
||||
self.log.warn('Failed to find fonts for family:', ff, 'not embedding')
|
||||
self.warned.add(ff)
|
||||
return
|
||||
try:
|
||||
weight = int(style.get('font-weight', '400'))
|
||||
except (ValueError, TypeError, AttributeError):
|
||||
w = style['font-weight']
|
||||
if w not in self.warned2:
|
||||
self.log.warn('Invalid weight in font style: %r' % w)
|
||||
self.warned2.add(w)
|
||||
return
|
||||
for f in fonts:
|
||||
if f['weight'] == weight and f['font-style'] == style.get('font-style', 'normal') and f['font-stretch'] == style.get('font-stretch', 'normal'):
|
||||
self.log('Embedding font %s from %s' % (f['full_name'], f['path']))
|
||||
data = font_scanner.get_font_data(f)
|
||||
name = f['full_name']
|
||||
ext = 'otf' if f['is_otf'] else 'ttf'
|
||||
name = ascii_filename(name).replace(' ', '-').replace('(', '').replace(')', '')
|
||||
fid, href = self.oeb.manifest.generate(id=u'font', href=u'fonts/%s.%s'%(name, ext))
|
||||
item = self.oeb.manifest.add(fid, href, guess_type('dummy.'+ext)[0], data=data)
|
||||
item.unload_data_from_memory()
|
||||
page_sheet = self.get_page_sheet()
|
||||
href = page_sheet.relhref(item.href)
|
||||
css = '''@font-face { font-family: "%s"; font-weight: %s; font-style: %s; font-stretch: %s; src: url(%s) }''' % (
|
||||
f['font-family'], f['font-weight'], f['font-style'], f['font-stretch'], href)
|
||||
sheet = self.parser.parseString(css, validate=False)
|
||||
page_sheet.data.insertRule(sheet.cssRules[0], len(page_sheet.data.cssRules))
|
||||
return find_font_face_rules(sheet, self.oeb)[0]
|
||||
|
@ -194,7 +194,7 @@ class CSSFlattener(object):
|
||||
for i, font in enumerate(faces):
|
||||
ext = 'otf' if font['is_otf'] else 'ttf'
|
||||
fid, href = self.oeb.manifest.generate(id=u'font',
|
||||
href=u'%s.%s'%(ascii_filename(font['full_name']).replace(u' ', u'-'), ext))
|
||||
href=u'fonts/%s.%s'%(ascii_filename(font['full_name']).replace(u' ', u'-'), ext))
|
||||
item = self.oeb.manifest.add(fid, href,
|
||||
guess_type('dummy.'+ext)[0],
|
||||
data=font_scanner.get_font_data(font))
|
||||
|
@ -12,6 +12,111 @@ from collections import defaultdict
|
||||
from calibre.ebooks.oeb.base import urlnormalize
|
||||
from calibre.utils.fonts.sfnt.subset import subset, NoGlyphs, UnsupportedFont
|
||||
|
||||
def get_font_properties(rule, default=None):
|
||||
'''
|
||||
Given a CSS rule, extract normalized font properties from
|
||||
it. Note that shorthand font property should already have been expanded
|
||||
by the CSS flattening code.
|
||||
'''
|
||||
props = {}
|
||||
s = rule.style
|
||||
for q in ('font-family', 'src', 'font-weight', 'font-stretch',
|
||||
'font-style'):
|
||||
g = 'uri' if q == 'src' else 'value'
|
||||
try:
|
||||
val = s.getProperty(q).propertyValue[0]
|
||||
val = getattr(val, g)
|
||||
if q == 'font-family':
|
||||
val = [x.value for x in s.getProperty(q).propertyValue]
|
||||
if val and val[0] == 'inherit':
|
||||
val = None
|
||||
except (IndexError, KeyError, AttributeError, TypeError, ValueError):
|
||||
val = None if q in {'src', 'font-family'} else default
|
||||
if q in {'font-weight', 'font-stretch', 'font-style'}:
|
||||
val = unicode(val).lower() if (val or val == 0) else val
|
||||
if val == 'inherit':
|
||||
val = default
|
||||
if q == 'font-weight':
|
||||
val = {'normal':'400', 'bold':'700'}.get(val, val)
|
||||
if val not in {'100', '200', '300', '400', '500', '600', '700',
|
||||
'800', '900', 'bolder', 'lighter'}:
|
||||
val = default
|
||||
if val == 'normal':
|
||||
val = '400'
|
||||
elif q == 'font-style':
|
||||
if val not in {'normal', 'italic', 'oblique'}:
|
||||
val = default
|
||||
elif q == 'font-stretch':
|
||||
if val not in {'normal', 'ultra-condensed', 'extra-condensed',
|
||||
'condensed', 'semi-condensed', 'semi-expanded',
|
||||
'expanded', 'extra-expanded', 'ultra-expanded'}:
|
||||
val = default
|
||||
props[q] = val
|
||||
return props
|
||||
|
||||
|
||||
def find_font_face_rules(sheet, oeb):
|
||||
'''
|
||||
Find all @font-face rules in the given sheet and extract the relevant info from them.
|
||||
sheet can be either a ManifestItem or a CSSStyleSheet.
|
||||
'''
|
||||
ans = []
|
||||
try:
|
||||
rules = sheet.data.cssRules
|
||||
except AttributeError:
|
||||
rules = sheet.cssRules
|
||||
|
||||
for i, rule in enumerate(rules):
|
||||
if rule.type != rule.FONT_FACE_RULE:
|
||||
continue
|
||||
props = get_font_properties(rule, default='normal')
|
||||
if not props['font-family'] or not props['src']:
|
||||
continue
|
||||
|
||||
try:
|
||||
path = sheet.abshref(props['src'])
|
||||
except AttributeError:
|
||||
path = props['src']
|
||||
ff = oeb.manifest.hrefs.get(urlnormalize(path), None)
|
||||
if not ff:
|
||||
continue
|
||||
props['item'] = ff
|
||||
if props['font-weight'] in {'bolder', 'lighter'}:
|
||||
props['font-weight'] = '400'
|
||||
props['weight'] = int(props['font-weight'])
|
||||
props['rule'] = rule
|
||||
props['chars'] = set()
|
||||
ans.append(props)
|
||||
|
||||
return ans
|
||||
|
||||
|
||||
def elem_style(style_rules, cls, inherited_style):
|
||||
'''
|
||||
Find the effective style for the given element.
|
||||
'''
|
||||
classes = cls.split()
|
||||
style = inherited_style.copy()
|
||||
for cls in classes:
|
||||
style.update(style_rules.get(cls, {}))
|
||||
wt = style.get('font-weight', None)
|
||||
pwt = inherited_style.get('font-weight', '400')
|
||||
if wt == 'bolder':
|
||||
style['font-weight'] = {
|
||||
'100':'400',
|
||||
'200':'400',
|
||||
'300':'400',
|
||||
'400':'700',
|
||||
'500':'700',
|
||||
}.get(pwt, '900')
|
||||
elif wt == 'lighter':
|
||||
style['font-weight'] = {
|
||||
'600':'400', '700':'400',
|
||||
'800':'700', '900':'700'}.get(pwt, '100')
|
||||
|
||||
return style
|
||||
|
||||
|
||||
class SubsetFonts(object):
|
||||
|
||||
'''
|
||||
@ -76,72 +181,15 @@ class SubsetFonts(object):
|
||||
self.log('Reduced total font size to %.1f%% of original'%
|
||||
(totals[0]/totals[1] * 100))
|
||||
|
||||
def get_font_properties(self, rule, default=None):
|
||||
'''
|
||||
Given a CSS rule, extract normalized font properties from
|
||||
it. Note that shorthand font property should already have been expanded
|
||||
by the CSS flattening code.
|
||||
'''
|
||||
props = {}
|
||||
s = rule.style
|
||||
for q in ('font-family', 'src', 'font-weight', 'font-stretch',
|
||||
'font-style'):
|
||||
g = 'uri' if q == 'src' else 'value'
|
||||
try:
|
||||
val = s.getProperty(q).propertyValue[0]
|
||||
val = getattr(val, g)
|
||||
if q == 'font-family':
|
||||
val = [x.value for x in s.getProperty(q).propertyValue]
|
||||
if val and val[0] == 'inherit':
|
||||
val = None
|
||||
except (IndexError, KeyError, AttributeError, TypeError, ValueError):
|
||||
val = None if q in {'src', 'font-family'} else default
|
||||
if q in {'font-weight', 'font-stretch', 'font-style'}:
|
||||
val = unicode(val).lower() if (val or val == 0) else val
|
||||
if val == 'inherit':
|
||||
val = default
|
||||
if q == 'font-weight':
|
||||
val = {'normal':'400', 'bold':'700'}.get(val, val)
|
||||
if val not in {'100', '200', '300', '400', '500', '600', '700',
|
||||
'800', '900', 'bolder', 'lighter'}:
|
||||
val = default
|
||||
if val == 'normal': val = '400'
|
||||
elif q == 'font-style':
|
||||
if val not in {'normal', 'italic', 'oblique'}:
|
||||
val = default
|
||||
elif q == 'font-stretch':
|
||||
if val not in { 'normal', 'ultra-condensed', 'extra-condensed',
|
||||
'condensed', 'semi-condensed', 'semi-expanded',
|
||||
'expanded', 'extra-expanded', 'ultra-expanded'}:
|
||||
val = default
|
||||
props[q] = val
|
||||
return props
|
||||
|
||||
def find_embedded_fonts(self):
|
||||
'''
|
||||
Find all @font-face rules and extract the relevant info from them.
|
||||
'''
|
||||
self.embedded_fonts = []
|
||||
for item in self.oeb.manifest:
|
||||
if not hasattr(item.data, 'cssRules'): continue
|
||||
for i, rule in enumerate(item.data.cssRules):
|
||||
if rule.type != rule.FONT_FACE_RULE:
|
||||
continue
|
||||
props = self.get_font_properties(rule, default='normal')
|
||||
if not props['font-family'] or not props['src']:
|
||||
continue
|
||||
|
||||
path = item.abshref(props['src'])
|
||||
ff = self.oeb.manifest.hrefs.get(urlnormalize(path), None)
|
||||
if not ff:
|
||||
continue
|
||||
props['item'] = ff
|
||||
if props['font-weight'] in {'bolder', 'lighter'}:
|
||||
props['font-weight'] = '400'
|
||||
props['weight'] = int(props['font-weight'])
|
||||
props['chars'] = set()
|
||||
props['rule'] = rule
|
||||
self.embedded_fonts.append(props)
|
||||
if not hasattr(item.data, 'cssRules'):
|
||||
continue
|
||||
self.embedded_fonts.extend(find_font_face_rules(item, self.oeb))
|
||||
|
||||
def find_style_rules(self):
|
||||
'''
|
||||
@ -151,12 +199,13 @@ class SubsetFonts(object):
|
||||
'''
|
||||
rules = defaultdict(dict)
|
||||
for item in self.oeb.manifest:
|
||||
if not hasattr(item.data, 'cssRules'): continue
|
||||
if not hasattr(item.data, 'cssRules'):
|
||||
continue
|
||||
for i, rule in enumerate(item.data.cssRules):
|
||||
if rule.type != rule.STYLE_RULE:
|
||||
continue
|
||||
props = {k:v for k,v in
|
||||
self.get_font_properties(rule).iteritems() if v}
|
||||
get_font_properties(rule).iteritems() if v}
|
||||
if not props:
|
||||
continue
|
||||
for sel in rule.selectorList:
|
||||
@ -172,41 +221,17 @@ class SubsetFonts(object):
|
||||
|
||||
def find_font_usage(self):
|
||||
for item in self.oeb.manifest:
|
||||
if not hasattr(item.data, 'xpath'): continue
|
||||
if not hasattr(item.data, 'xpath'):
|
||||
continue
|
||||
for body in item.data.xpath('//*[local-name()="body"]'):
|
||||
base = {'font-family':['serif'], 'font-weight': '400',
|
||||
'font-style':'normal', 'font-stretch':'normal'}
|
||||
self.find_usage_in(body, base)
|
||||
|
||||
def elem_style(self, cls, inherited_style):
|
||||
'''
|
||||
Find the effective style for the given element.
|
||||
'''
|
||||
classes = cls.split()
|
||||
style = inherited_style.copy()
|
||||
for cls in classes:
|
||||
style.update(self.style_rules.get(cls, {}))
|
||||
wt = style.get('font-weight', None)
|
||||
pwt = inherited_style.get('font-weight', '400')
|
||||
if wt == 'bolder':
|
||||
style['font-weight'] = {
|
||||
'100':'400',
|
||||
'200':'400',
|
||||
'300':'400',
|
||||
'400':'700',
|
||||
'500':'700',
|
||||
}.get(pwt, '900')
|
||||
elif wt == 'lighter':
|
||||
style['font-weight'] = {
|
||||
'600':'400', '700':'400',
|
||||
'800':'700', '900':'700'}.get(pwt, '100')
|
||||
|
||||
return style
|
||||
|
||||
def used_font(self, style):
|
||||
'''
|
||||
Given a style find the embedded font that matches it. Returns None if
|
||||
no match is found ( can happen if not family matches).
|
||||
no match is found (can happen if no family matches).
|
||||
'''
|
||||
ff = style.get('font-family', [])
|
||||
lnames = {unicode(x).lower() for x in ff}
|
||||
@ -222,7 +247,7 @@ class SubsetFonts(object):
|
||||
return None
|
||||
|
||||
# Filter on font-stretch
|
||||
widths = {x:i for i, x in enumerate(( 'ultra-condensed',
|
||||
widths = {x:i for i, x in enumerate(('ultra-condensed',
|
||||
'extra-condensed', 'condensed', 'semi-condensed', 'normal',
|
||||
'semi-expanded', 'expanded', 'extra-expanded', 'ultra-expanded'
|
||||
))}
|
||||
@ -280,7 +305,7 @@ class SubsetFonts(object):
|
||||
return ans
|
||||
|
||||
def find_usage_in(self, elem, inherited_style):
|
||||
style = self.elem_style(elem.get('class', '') or '', inherited_style)
|
||||
style = elem_style(self.style_rules, elem.get('class', '') or '', inherited_style)
|
||||
for child in elem:
|
||||
self.find_usage_in(child, style)
|
||||
font = self.used_font(style)
|
||||
@ -290,3 +315,4 @@ class SubsetFonts(object):
|
||||
font['chars'] |= chars
|
||||
|
||||
|
||||
|
||||
|
@ -32,7 +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', 'subset_embedded_fonts',
|
||||
'embed_font_family', 'embed_all_fonts', 'subset_embedded_fonts',
|
||||
'smarten_punctuation', 'unsmarten_punctuation',
|
||||
'disable_font_rescaling', 'insert_blank_line',
|
||||
'remove_paragraph_spacing',
|
||||
|
@ -14,6 +14,70 @@
|
||||
<string>Form</string>
|
||||
</property>
|
||||
<layout class="QGridLayout" name="gridLayout">
|
||||
<item row="12" column="1" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_keep_ligatures">
|
||||
<property name="text">
|
||||
<string>Keep &ligatures</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="3">
|
||||
<widget class="QCheckBox" name="opt_linearize_tables">
|
||||
<property name="text">
|
||||
<string>&Linearize tables</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_18">
|
||||
<property name="text">
|
||||
<string>Base &font size:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_base_font_size</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="3">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>&Line size:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_insert_blank_line_size</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1" colspan="2">
|
||||
<widget class="EncodingComboBox" name="opt_input_encoding">
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_remove_paragraph_spacing">
|
||||
<property name="text">
|
||||
<string>Remove &spacing between paragraphs</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="3">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>&Indent size:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_remove_paragraph_spacing_indent_size</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="4">
|
||||
<widget class="QDoubleSpinBox" name="opt_line_height">
|
||||
<property name="suffix">
|
||||
@ -24,6 +88,57 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_insert_blank_line">
|
||||
<property name="text">
|
||||
<string>Insert &blank line between paragraphs</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="4">
|
||||
<widget class="QDoubleSpinBox" name="opt_insert_blank_line_size">
|
||||
<property name="suffix">
|
||||
<string> em</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>1</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Text &justification:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_change_justification</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="2" colspan="3">
|
||||
<widget class="QComboBox" name="opt_change_justification"/>
|
||||
</item>
|
||||
<item row="11" column="0">
|
||||
<widget class="QCheckBox" name="opt_smarten_punctuation">
|
||||
<property name="text">
|
||||
<string>Smarten &punctuation</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="1" colspan="4">
|
||||
<widget class="QCheckBox" name="opt_asciiize">
|
||||
<property name="text">
|
||||
<string>&Transliterate unicode characters to ASCII</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="0">
|
||||
<widget class="QCheckBox" name="opt_unsmarten_punctuation">
|
||||
<property name="text">
|
||||
<string>&UnSmarten punctuation</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="3">
|
||||
<widget class="QLabel" name="label">
|
||||
<property name="text">
|
||||
@ -44,51 +159,6 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="1">
|
||||
<widget class="QDoubleSpinBox" name="opt_minimum_line_height">
|
||||
<property name="suffix">
|
||||
<string> %</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>900.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QDoubleSpinBox" name="opt_base_font_size">
|
||||
<property name="suffix">
|
||||
<string> pt</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>0.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>50.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>15.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Font size &key:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_font_size_mapping</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="2" column="1" colspan="3">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
@ -133,56 +203,72 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="5" column="1" colspan="2">
|
||||
<widget class="EncodingComboBox" name="opt_input_encoding">
|
||||
<property name="editable">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_remove_paragraph_spacing">
|
||||
<property name="text">
|
||||
<string>Remove &spacing between paragraphs</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="3">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>&Indent size:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_remove_paragraph_spacing_indent_size</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="4">
|
||||
<widget class="QDoubleSpinBox" name="opt_remove_paragraph_spacing_indent_size">
|
||||
<property name="toolTip">
|
||||
<string><p>When calibre removes inter paragraph spacing, it automatically sets a paragraph indent, to ensure that paragraphs can be easily distinguished. This option controls the width of that indent.</string>
|
||||
</property>
|
||||
<property name="specialValueText">
|
||||
<string>No change</string>
|
||||
</property>
|
||||
<item row="3" column="1">
|
||||
<widget class="QDoubleSpinBox" name="opt_minimum_line_height">
|
||||
<property name="suffix">
|
||||
<string> em</string>
|
||||
<string> %</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>900.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="1">
|
||||
<widget class="QDoubleSpinBox" name="opt_base_font_size">
|
||||
<property name="suffix">
|
||||
<string> pt</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>1</number>
|
||||
</property>
|
||||
<property name="minimum">
|
||||
<double>-0.100000000000000</double>
|
||||
<double>0.000000000000000</double>
|
||||
</property>
|
||||
<property name="maximum">
|
||||
<double>50.000000000000000</double>
|
||||
</property>
|
||||
<property name="singleStep">
|
||||
<double>0.100000000000000</double>
|
||||
<double>1.000000000000000</double>
|
||||
</property>
|
||||
<property name="value">
|
||||
<double>15.000000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="12" column="0" colspan="5">
|
||||
<item row="0" column="0" colspan="5">
|
||||
<widget class="QCheckBox" name="opt_disable_font_rescaling">
|
||||
<property name="text">
|
||||
<string>&Disable font size rescaling</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1" colspan="2">
|
||||
<widget class="FontFamilyChooser" name="opt_embed_font_family" native="true"/>
|
||||
</item>
|
||||
<item row="2" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Font size &key:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_font_size_mapping</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>&Embed font family:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_embed_font_family</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="13" column="0" colspan="5">
|
||||
<widget class="QTabWidget" name="tabWidget">
|
||||
<property name="currentIndex">
|
||||
<number>0</number>
|
||||
@ -300,121 +386,42 @@
|
||||
</widget>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="0" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_insert_blank_line">
|
||||
<property name="text">
|
||||
<string>Insert &blank line between paragraphs</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="4">
|
||||
<widget class="QDoubleSpinBox" name="opt_insert_blank_line_size">
|
||||
<widget class="QDoubleSpinBox" name="opt_remove_paragraph_spacing_indent_size">
|
||||
<property name="toolTip">
|
||||
<string><p>When calibre removes inter paragraph spacing, it automatically sets a paragraph indent, to ensure that paragraphs can be easily distinguished. This option controls the width of that indent.</string>
|
||||
</property>
|
||||
<property name="specialValueText">
|
||||
<string>No change</string>
|
||||
</property>
|
||||
<property name="suffix">
|
||||
<string> em</string>
|
||||
</property>
|
||||
<property name="decimals">
|
||||
<number>1</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="0">
|
||||
<widget class="QLabel" name="label_5">
|
||||
<property name="text">
|
||||
<string>Text &justification:</string>
|
||||
<property name="minimum">
|
||||
<double>-0.100000000000000</double>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_change_justification</cstring>
|
||||
<property name="singleStep">
|
||||
<double>0.100000000000000</double>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="9" column="2" colspan="3">
|
||||
<widget class="QComboBox" name="opt_change_justification"/>
|
||||
</item>
|
||||
<item row="10" column="0">
|
||||
<widget class="QCheckBox" name="opt_smarten_punctuation">
|
||||
<property name="text">
|
||||
<string>Smarten &punctuation</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="10" column="1" colspan="4">
|
||||
<widget class="QCheckBox" name="opt_asciiize">
|
||||
<property name="text">
|
||||
<string>&Transliterate unicode characters to ASCII</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="0">
|
||||
<widget class="QCheckBox" name="opt_unsmarten_punctuation">
|
||||
<property name="text">
|
||||
<string>&UnSmarten punctuation</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="1" colspan="2">
|
||||
<widget class="QCheckBox" name="opt_keep_ligatures">
|
||||
<property name="text">
|
||||
<string>Keep &ligatures</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="3">
|
||||
<widget class="QCheckBox" name="opt_linearize_tables">
|
||||
<property name="text">
|
||||
<string>&Linearize tables</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="1" column="0">
|
||||
<widget class="QLabel" name="label_18">
|
||||
<property name="text">
|
||||
<string>Base &font size:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_base_font_size</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="8" column="3">
|
||||
<widget class="QLabel" name="label_7">
|
||||
<property name="text">
|
||||
<string>&Line size:</string>
|
||||
</property>
|
||||
<property name="alignment">
|
||||
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_insert_blank_line_size</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="0">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<property name="text">
|
||||
<string>&Embed font family:</string>
|
||||
</property>
|
||||
<property name="buddy">
|
||||
<cstring>opt_embed_font_family</cstring>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="0" colspan="5">
|
||||
<widget class="QCheckBox" name="opt_disable_font_rescaling">
|
||||
<property name="text">
|
||||
<string>&Disable font size rescaling</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="6" column="1" colspan="2">
|
||||
<widget class="FontFamilyChooser" name="opt_embed_font_family" native="true"/>
|
||||
</item>
|
||||
<item row="6" column="3" colspan="2">
|
||||
<item row="7" column="3">
|
||||
<widget class="QCheckBox" name="opt_subset_embedded_fonts">
|
||||
<property name="text">
|
||||
<string>&Subset all embedded fonts</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="7" column="0" colspan="3">
|
||||
<widget class="QCheckBox" name="opt_embed_all_fonts">
|
||||
<property name="text">
|
||||
<string>&Embed referenced fonts</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
<customwidgets>
|
||||
|
Loading…
x
Reference in New Issue
Block a user