Code to parse the font shorthand property

This commit is contained in:
Kovid Goyal 2016-06-30 11:07:51 +05:30
parent f1122ac05f
commit b0b7a59afd
2 changed files with 123 additions and 4 deletions

View File

@ -11,7 +11,7 @@ from tinycss.css21 import CSS21Parser, ParseError
from .tokenizer import tokenize_grouped
def parse_font_family(css_string):
def parse_font_family_tokens(tokens):
families = []
current_family = ''
def commit():
@ -19,7 +19,7 @@ def parse_font_family(css_string):
if val:
families.append(val)
for token in tokenize_grouped(css_string.strip()):
for token in tokens:
if token.type == 'STRING':
if current_family:
commit()
@ -35,6 +35,113 @@ def parse_font_family(css_string):
commit()
return families
def parse_font_family(css_string):
return parse_font_family_tokens(tokenize_grouped(type('')(css_string).strip()))
GLOBAL_IDENTS = frozenset('inherit initial unset normal'.split())
STYLE_IDENTS = frozenset('italic oblique'.split())
VARIANT_IDENTS = frozenset(('small-caps',))
WEIGHT_IDENTS = frozenset('bold bolder lighter'.split())
STRETCH_IDENTS = frozenset('ultra-condensed extra-condensed condensed semi-condensed semi-expanded expanded extra-expanded ultra-expanded'.split())
BEFORE_SIZE_IDENTS = STYLE_IDENTS | VARIANT_IDENTS | WEIGHT_IDENTS | STRETCH_IDENTS
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()))
LEGACY_FONT_SPEC = frozenset('caption icon menu message-box small-caption status-bar'.split())
def parse_font(css_string):
# See https://www.w3.org/TR/css-fonts-3/#font-prop
style = variant = weight = stretch = size = height = None
tokens = list(reversed(tuple(tokenize_grouped(type('')(css_string).strip()))))
if tokens and tokens[-1].value in LEGACY_FONT_SPEC:
return {'font-family':['sans-serif']}
while tokens:
tok = tokens.pop()
if tok.type == 'STRING':
tokens.append(tok)
break
if tok.type == 'INTEGER':
if size is None:
if weight is None and tok.value in WEIGHT_SIZES:
weight = tok.as_css()
continue
break
if height is None:
height = tok.as_css()
break
break
if tok.type == 'NUMBER':
if size is not None and height is None:
height = tok.as_css()
break
if tok.type == 'DELIM':
if tok.value == '/' and size is not None and height is None:
continue
break
if tok.type in ('DIMENSION', 'PERCENTAGE'):
if size is None:
size = tok.as_css()
continue
if height is None:
height = tok.as_css()
break
if tok.type == 'IDENT':
if tok.value in GLOBAL_IDENTS:
if size is not None:
if height is None:
height = tok.value
else:
tokens.append(tok)
break
if style is None:
style = tok.value
elif variant is None:
variant = tok.value
elif weight is None:
weight = tok.value
elif stretch is None:
stretch = tok.value
elif size is None:
size = tok.value
elif height is None:
height = tok.value
break
else:
tokens.append(tok)
break
continue
if tok.value in BEFORE_SIZE_IDENTS:
if size is not None:
break
if tok.value in STYLE_IDENTS:
style = tok.value
elif tok.value in VARIANT_IDENTS:
variant = tok.value
elif tok.value in WEIGHT_IDENTS:
weight = tok.value
elif tok.value in STRETCH_IDENTS:
stretch = tok.value
elif tok.value in SIZE_IDENTS:
size = tok.value
else:
tokens.append(tok)
break
families = parse_font_family_tokens(reversed(tokens))
ans = {}
if style is not None:
ans['font-style'] = style
if variant is not None:
ans['font-variant'] = variant
if weight is not None:
ans['font-weight'] = weight
if stretch is not None:
ans['font-stretch'] = stretch
if size is not None:
ans['font-size'] = size
if height is not None:
ans['line-height'] = height
if families:
ans['font-family'] = families
return ans
class FontFaceRule(object):

View File

@ -6,7 +6,7 @@ from __future__ import (unicode_literals, division, absolute_import,
__license__ = 'GPL v3'
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
from tinycss.fonts3 import CSSFonts3Parser, parse_font_family
from tinycss.fonts3 import CSSFonts3Parser, parse_font_family, parse_font
from tinycss.tests import BaseTest
class TestFonts3(BaseTest):
@ -48,4 +48,16 @@ class TestFonts3(BaseTest):
for single in ('serif', 'sans-serif', 'A B C'):
self.ae([single], parse_font_family(single))
def test_parse_font(self):
def t(raw, **kw):
q = {('line' if k == 'height' else 'font') + '-' + k:v for k, v in kw.iteritems()}
self.ae(q, parse_font(raw))
t('caption', family=['sans-serif'])
t('serif', family=['serif'])
t('12pt/14pt sans-serif', size='12pt', height='14pt', family=['sans-serif'])
t('80% sans-serif', size='80%', family=['sans-serif'])
t('x-large/110% "new century schoolbook", serif', size='x-large', height='110%', family=['new century schoolbook', 'serif'])
t('bold italic large Palatino, serif', weight='bold', style='italic', size='large', family=['Palatino', 'serif'])
t('normal small-caps 120%/120% fantasy', style='normal', variant='small-caps', size='120%', height='120%', family=['fantasy'])
t('condensed oblique 12pt Helvetica Neue, serif', stretch='condensed', style='oblique', size='12pt', family=['Helvetica Neue', 'serif'])
t('300 italic 1.3em/1.7em FB Armada, sans-serif', weight='300', style='italic', size='1.3em', height='1.7em', family=['FB Armada', 'sans-serif'])