mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Use the new font parsing code for normalize_css
This commit is contained in:
parent
b0b7a59afd
commit
bbaf9875cc
@ -14,6 +14,7 @@ try:
|
|||||||
except ImportError:
|
except ImportError:
|
||||||
raise RuntimeError('You need cssutils >= 0.9.9 for calibre')
|
raise RuntimeError('You need cssutils >= 0.9.9 for calibre')
|
||||||
from cssutils import profile as cssprofiles, CSSParser
|
from cssutils import profile as cssprofiles, CSSParser
|
||||||
|
from tinycss.fonts3 import parse_font, serialize_font_family
|
||||||
|
|
||||||
DEFAULTS = {'azimuth': 'center', 'background-attachment': 'scroll', # {{{
|
DEFAULTS = {'azimuth': 'center', 'background-attachment': 'scroll', # {{{
|
||||||
'background-color': 'transparent', 'background-image': 'none',
|
'background-color': 'transparent', 'background-image': 'none',
|
||||||
@ -118,52 +119,19 @@ def normalize_font(cssvalue, font_family_as_list=False):
|
|||||||
composition = font_composition
|
composition = font_composition
|
||||||
val = cssvalue.cssText
|
val = cssvalue.cssText
|
||||||
if val == 'inherit':
|
if val == 'inherit':
|
||||||
return {k:'inherit' for k in composition}
|
ans = {k:'inherit' for k in composition}
|
||||||
if val in {'caption', 'icon', 'menu', 'message-box', 'small-caption', 'status-bar'}:
|
elif val in {'caption', 'icon', 'menu', 'message-box', 'small-caption', 'status-bar'}:
|
||||||
return {k:DEFAULTS[k] for k in composition}
|
ans = {k:DEFAULTS[k] for k in composition}
|
||||||
if getattr(cssvalue, 'length', 1) < 2:
|
|
||||||
return {} # Mandatory to define both font size and font family
|
|
||||||
style = {k:DEFAULTS[k] for k in composition}
|
|
||||||
families = []
|
|
||||||
vals = [x.cssText for x in cssvalue]
|
|
||||||
found_font_size = False
|
|
||||||
while vals:
|
|
||||||
text = vals.pop()
|
|
||||||
if not families and text == 'inherit':
|
|
||||||
families.append(text)
|
|
||||||
continue
|
|
||||||
if cssprofiles.validate('line-height', text):
|
|
||||||
if not vals or not cssprofiles.validate('font-size', vals[-1]):
|
|
||||||
if cssprofiles.validate('font-size', text):
|
|
||||||
style['font-size'] = text
|
|
||||||
found_font_size = True
|
|
||||||
break
|
|
||||||
return {} # must have font-size here
|
|
||||||
style['line-height'] = text
|
|
||||||
style['font-size'] = vals.pop()
|
|
||||||
found_font_size = True
|
|
||||||
break
|
|
||||||
if cssprofiles.validate('font-size', text):
|
|
||||||
style['font-size'] = text
|
|
||||||
found_font_size = True
|
|
||||||
break
|
|
||||||
if families == ['inherit']:
|
|
||||||
return {} # Cannot have multiple font-families if the last one if inherit
|
|
||||||
families.insert(0, text)
|
|
||||||
if not families or not found_font_size:
|
|
||||||
return {} # font-family required
|
|
||||||
style['font-family'] = families if font_family_as_list else ', '.join(families)
|
|
||||||
props = ['font-style', 'font-variant', 'font-weight']
|
|
||||||
while vals:
|
|
||||||
for i, prop in enumerate(tuple(props)):
|
|
||||||
if cssprofiles.validate(prop, vals[0]):
|
|
||||||
props.pop(i)
|
|
||||||
style[prop] = vals.pop(0)
|
|
||||||
break
|
|
||||||
else:
|
else:
|
||||||
return {} # unrecognized value
|
ans = {k:DEFAULTS[k] for k in composition}
|
||||||
|
ans.update(parse_font(val))
|
||||||
return style
|
if font_family_as_list:
|
||||||
|
if isinstance(ans['font-family'], basestring):
|
||||||
|
ans['font-family'] = [x.strip() for x in ans['font-family'].split(',')]
|
||||||
|
else:
|
||||||
|
if not isinstance(ans['font-family'], basestring):
|
||||||
|
ans['font-family'] = serialize_font_family(ans['font-family'])
|
||||||
|
return ans
|
||||||
|
|
||||||
def normalize_border(name, cssvalue):
|
def normalize_border(name, cssvalue):
|
||||||
style = normalizers['border-' + EDGES[0]]('border-' + EDGES[0], cssvalue)
|
style = normalizers['border-' + EDGES[0]]('border-' + EDGES[0], cssvalue)
|
||||||
@ -300,15 +268,16 @@ def test_normalization(return_tests=False): # {{{
|
|||||||
return ans
|
return ans
|
||||||
|
|
||||||
for raw, expected in {
|
for raw, expected in {
|
||||||
'some_font': {}, 'none': {}, 'inherit':{k:'inherit' for k in font_composition},
|
'some_font': {'font-family':'some_font'}, 'inherit':{k:'inherit' for k in font_composition},
|
||||||
'1.2pt/1.4 A_Font': {'font-family':'A_Font', 'font-size':'1.2pt', 'line-height':'1.4'},
|
'1.2pt/1.4 A_Font': {'font-family':'A_Font', 'font-size':'1.2pt', 'line-height':'1.4'},
|
||||||
'bad font': {}, '10% serif': {'font-family':'serif', 'font-size':'10%'},
|
'bad font': {'font-family':'"bad font"'}, '10% serif': {'font-family':'serif', 'font-size':'10%'},
|
||||||
'12px "My Font", serif': {'font-family':'"My Font", serif', 'font-size': '12px'},
|
'12px "My Font", serif': {'font-family':'"My Font", serif', 'font-size': '12px'},
|
||||||
'normal 0.6em/135% arial,sans-serif': {'font-family': 'arial, sans-serif', 'font-size': '0.6em', 'line-height':'135%', 'font-style':'normal'},
|
'normal 0.6em/135% arial,sans-serif': {'font-family': 'arial, sans-serif', 'font-size': '0.6em', 'line-height':'135%', 'font-style':'normal'},
|
||||||
'bold italic large serif': {'font-family':'serif', 'font-weight':'bold', 'font-style':'italic', 'font-size':'large'},
|
'bold italic large serif': {'font-family':'serif', 'font-weight':'bold', 'font-style':'italic', 'font-size':'large'},
|
||||||
'bold italic small-caps larger/normal serif':
|
'bold italic small-caps larger/normal serif':
|
||||||
{'font-family':'serif', 'font-weight':'bold', 'font-style':'italic', 'font-size':'larger',
|
{'font-family':'serif', 'font-weight':'bold', 'font-style':'italic', 'font-size':'larger',
|
||||||
'line-height':'normal', 'font-variant':'small-caps'},
|
'line-height':'normal', 'font-variant':'small-caps'},
|
||||||
|
'2em A B': {'font-family': '"A B"', 'font-size': '2em'},
|
||||||
}.iteritems():
|
}.iteritems():
|
||||||
val = tuple(parseStyle('font: %s' % raw, validate=False))[0].cssValue
|
val = tuple(parseStyle('font: %s' % raw, validate=False))[0].cssValue
|
||||||
style = normalizers['font']('font', val)
|
style = normalizers['font']('font', val)
|
||||||
|
@ -7,6 +7,8 @@ __license__ = 'GPL v3'
|
|||||||
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
||||||
|
|
||||||
|
|
||||||
|
import re
|
||||||
|
from future_builtins import map
|
||||||
from tinycss.css21 import CSS21Parser, ParseError
|
from tinycss.css21 import CSS21Parser, ParseError
|
||||||
from .tokenizer import tokenize_grouped
|
from .tokenizer import tokenize_grouped
|
||||||
|
|
||||||
@ -38,6 +40,18 @@ def parse_font_family_tokens(tokens):
|
|||||||
def parse_font_family(css_string):
|
def parse_font_family(css_string):
|
||||||
return parse_font_family_tokens(tokenize_grouped(type('')(css_string).strip()))
|
return parse_font_family_tokens(tokenize_grouped(type('')(css_string).strip()))
|
||||||
|
|
||||||
|
def serialize_font_family(families):
|
||||||
|
def one(x):
|
||||||
|
xl = x.lower()
|
||||||
|
if xl in GENERIC_FAMILIES:
|
||||||
|
if xl == 'sansserif':
|
||||||
|
xl = 'sans-serif'
|
||||||
|
return xl
|
||||||
|
if SIMPLE_NAME_PAT.match(x) is not None:
|
||||||
|
return x
|
||||||
|
return '"%s"' % x.replace('"', r'\"')
|
||||||
|
return ', '.join(map(one, families))
|
||||||
|
|
||||||
GLOBAL_IDENTS = frozenset('inherit initial unset normal'.split())
|
GLOBAL_IDENTS = frozenset('inherit initial unset normal'.split())
|
||||||
STYLE_IDENTS = frozenset('italic oblique'.split())
|
STYLE_IDENTS = frozenset('italic oblique'.split())
|
||||||
VARIANT_IDENTS = frozenset(('small-caps',))
|
VARIANT_IDENTS = frozenset(('small-caps',))
|
||||||
@ -47,6 +61,8 @@ BEFORE_SIZE_IDENTS = STYLE_IDENTS | VARIANT_IDENTS | WEIGHT_IDENTS | STRETCH_IDE
|
|||||||
SIZE_IDENTS = frozenset('xx-small x-small small medium large x-large xx-large larger smaller'.split())
|
SIZE_IDENTS = frozenset('xx-small x-small small medium large x-large xx-large larger smaller'.split())
|
||||||
WEIGHT_SIZES = frozenset(map(int, '100 200 300 400 500 600 700 800 900'.split()))
|
WEIGHT_SIZES = frozenset(map(int, '100 200 300 400 500 600 700 800 900'.split()))
|
||||||
LEGACY_FONT_SPEC = frozenset('caption icon menu message-box small-caption status-bar'.split())
|
LEGACY_FONT_SPEC = frozenset('caption icon menu message-box small-caption status-bar'.split())
|
||||||
|
GENERIC_FAMILIES = frozenset('serif sans-serif sansserif cursive fantasy monospace'.split())
|
||||||
|
SIMPLE_NAME_PAT = re.compile(r'[a-zA-Z][a-zA-Z0-9_-]*$')
|
||||||
|
|
||||||
def parse_font(css_string):
|
def parse_font(css_string):
|
||||||
# See https://www.w3.org/TR/css-fonts-3/#font-prop
|
# See https://www.w3.org/TR/css-fonts-3/#font-prop
|
||||||
|
Loading…
x
Reference in New Issue
Block a user