mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Add a check for aliased fonts
This commit is contained in:
parent
df97799deb
commit
2d414ecba8
127
src/calibre/ebooks/oeb/polish/check/fonts.py
Normal file
127
src/calibre/ebooks/oeb/polish/check/fonts.py
Normal file
@ -0,0 +1,127 @@
|
||||
#!/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>'
|
||||
|
||||
from cssutils.css import CSSRule
|
||||
|
||||
from calibre import force_unicode
|
||||
from calibre.constants import plugins
|
||||
from calibre.ebooks.oeb.base import OEB_DOCS, OEB_STYLES
|
||||
from calibre.ebooks.oeb.polish.check.base import BaseError, WARN
|
||||
from calibre.ebooks.oeb.polish.container import guess_type, OEB_FONTS
|
||||
from calibre.ebooks.oeb.polish.pretty import pretty_script_or_style
|
||||
from calibre.utils.fonts.utils import get_all_font_names
|
||||
|
||||
woff = plugins['woff'][0]
|
||||
|
||||
class InvalidFont(BaseError):
|
||||
|
||||
HELP = _('This font could not be processed. It most likely will'
|
||||
' not work in an ebook reader, either')
|
||||
|
||||
def fix_declaration(style, css_name, font_name):
|
||||
ff = style.getPropertyCSSValue('font-family')
|
||||
changed = False
|
||||
if ff is not None:
|
||||
for i in xrange(ff.length):
|
||||
val = ff.item(i)
|
||||
if val.value and val.value.lower() == css_name.lower():
|
||||
val.value = font_name
|
||||
changed = True
|
||||
return changed
|
||||
|
||||
def fix_sheet(sheet, css_name, font_name):
|
||||
changed = False
|
||||
for rule in sheet.cssRules:
|
||||
if rule.type in (CSSRule.FONT_FACE_RULE, CSSRule.STYLE_RULE):
|
||||
if fix_declaration(rule.style, css_name, font_name):
|
||||
changed = True
|
||||
return changed
|
||||
|
||||
class FontAliasing(BaseError):
|
||||
|
||||
level = WARN
|
||||
|
||||
def __init__(self, font_name, css_name, name, line):
|
||||
BaseError.__init__(self, _('The CSS font-family name %s does not match the actual font name %s') % (css_name, font_name), name, line)
|
||||
self.HELP = _('The font family name specified in the CSS @font-face rule: "{0}" does'
|
||||
' not match the font name inside the actual font file: "{1}". This can'
|
||||
' cause problems in some viewers. You should change the CSS font name'
|
||||
' to match the actual font name.').format(css_name, font_name)
|
||||
self.INDIVIDUAL_FIX = _('Change the font name {0} to {1} everywhere').format(css_name, font_name)
|
||||
self.font_name, self.css_name = font_name, css_name
|
||||
|
||||
def __call__(self, container):
|
||||
changed = False
|
||||
for name, mt in container.mime_map.iteritems():
|
||||
if mt in OEB_STYLES:
|
||||
sheet = container.parsed(name)
|
||||
if fix_sheet(sheet, self.css_name, self.font_name):
|
||||
container.dirty(name)
|
||||
changed = True
|
||||
elif mt in OEB_DOCS:
|
||||
for style in container.parsed(name).xpath('//*[local-name()="style"]'):
|
||||
if style.get('type', 'text/css') == 'text/css':
|
||||
sheet = container.parse_css(style.text)
|
||||
if fix_sheet(sheet, self.css_name, self.font_name):
|
||||
style.text = force_unicode(sheet.cssText, 'utf-8')
|
||||
pretty_script_or_style(container, style)
|
||||
container.dirty(name)
|
||||
changed = True
|
||||
for elem in container.parsed(name).xpath('//*[@style and contains(@style, "font-family")]'):
|
||||
style = container.parse_css(elem.get('style'), is_declaration=True)
|
||||
if fix_declaration(style, self.css_name, self.font_name):
|
||||
elem.set('style', force_unicode(style.cssText, 'utf-8').replace('\n', ' '))
|
||||
container.dirty(name)
|
||||
changed = True
|
||||
return changed
|
||||
|
||||
def check_fonts(container):
|
||||
font_map = {}
|
||||
errors = []
|
||||
for name, mt in container.mime_map.iteritems():
|
||||
if mt in OEB_FONTS:
|
||||
raw = container.raw_data(name)
|
||||
if mt == guess_type('a.woff'):
|
||||
try:
|
||||
raw = woff.from_woff(raw)
|
||||
except Exception as e:
|
||||
errors.append(InvalidFont(_('Not a valid WOFF font: %s') % e, name))
|
||||
continue
|
||||
try:
|
||||
name_map = get_all_font_names(raw)
|
||||
except Exception as e:
|
||||
errors.append(InvalidFont(_('Not a valid font: %s') % e, name))
|
||||
continue
|
||||
font_map[name] = name_map.get('family_name', None) or name_map.get('preferred_family_name', None) or name_map.get('wws_family_name', None)
|
||||
|
||||
sheets = []
|
||||
for name, mt in container.mime_map.iteritems():
|
||||
if mt in OEB_STYLES:
|
||||
sheets.append((name, container.parsed(name), None))
|
||||
elif mt in OEB_DOCS:
|
||||
for style in container.parsed(name).xpath('//*[local-name()="style"]'):
|
||||
if style.get('type', 'text/css') == 'text/css':
|
||||
sheets.append((name, container.parse_css(style.text), style.sourceline))
|
||||
|
||||
for name, sheet, line_offset in sheets:
|
||||
for rule in sheet.cssRules.rulesOfType(CSSRule.FONT_FACE_RULE):
|
||||
src = rule.style.getPropertyCSSValue('src')
|
||||
if src is not None and src.length > 0:
|
||||
href = getattr(src.item(0), 'uri', None)
|
||||
if href is not None:
|
||||
fname = container.href_to_name(href, name)
|
||||
font_name = font_map.get(fname, None)
|
||||
if font_name is None:
|
||||
continue
|
||||
ff = rule.style.getPropertyCSSValue('font-family')
|
||||
if ff is not None and ff.length > 0:
|
||||
ff = getattr(ff.item(0), 'value', None)
|
||||
if ff is not None and ff != font_name:
|
||||
errors.append(FontAliasing(font_name, ff, name, line_offset))
|
||||
|
||||
return errors
|
@ -15,6 +15,7 @@ from calibre.ebooks.oeb.polish.check.base import run_checkers
|
||||
from calibre.ebooks.oeb.polish.check.parsing import check_xml_parsing, check_css_parsing, fix_style_tag
|
||||
from calibre.ebooks.oeb.polish.check.images import check_raster_images
|
||||
from calibre.ebooks.oeb.polish.check.links import check_links
|
||||
from calibre.ebooks.oeb.polish.check.fonts import check_fonts
|
||||
|
||||
XML_TYPES = frozenset(map(guess_type, ('a.xml', 'a.svg', 'a.opf', 'a.ncx')))
|
||||
|
||||
@ -50,6 +51,7 @@ def run_checks(container):
|
||||
errors.extend(check_css_parsing(name, style.text, line_offset=style.sourceline - 1))
|
||||
|
||||
errors += check_links(container)
|
||||
errors += check_fonts(container)
|
||||
|
||||
return errors
|
||||
|
||||
|
@ -430,7 +430,7 @@ class Container(object): # {{{
|
||||
ans = self.decode(ans)
|
||||
return ans
|
||||
|
||||
def parse_css(self, data, fname='<string>'):
|
||||
def parse_css(self, data, fname='<string>', is_declaration=False):
|
||||
from cssutils import CSSParser, log
|
||||
log.setLevel(logging.WARN)
|
||||
log.raiseExceptions = False
|
||||
@ -441,6 +441,9 @@ class Container(object): # {{{
|
||||
parser = CSSParser(loglevel=logging.WARNING,
|
||||
# We dont care about @import rules
|
||||
fetcher=lambda x: (None, None), log=_css_logger)
|
||||
if is_declaration:
|
||||
data = parser.parseStyle(data, validate=False)
|
||||
else:
|
||||
data = parser.parseString(data, href=fname, validate=False)
|
||||
return data
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user