mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-31 14:33:54 -04:00
Code to parse the font shorthand property
This commit is contained in:
parent
f1122ac05f
commit
b0b7a59afd
@ -11,7 +11,7 @@ from tinycss.css21 import CSS21Parser, ParseError
|
|||||||
from .tokenizer import tokenize_grouped
|
from .tokenizer import tokenize_grouped
|
||||||
|
|
||||||
|
|
||||||
def parse_font_family(css_string):
|
def parse_font_family_tokens(tokens):
|
||||||
families = []
|
families = []
|
||||||
current_family = ''
|
current_family = ''
|
||||||
def commit():
|
def commit():
|
||||||
@ -19,7 +19,7 @@ def parse_font_family(css_string):
|
|||||||
if val:
|
if val:
|
||||||
families.append(val)
|
families.append(val)
|
||||||
|
|
||||||
for token in tokenize_grouped(css_string.strip()):
|
for token in tokens:
|
||||||
if token.type == 'STRING':
|
if token.type == 'STRING':
|
||||||
if current_family:
|
if current_family:
|
||||||
commit()
|
commit()
|
||||||
@ -35,6 +35,113 @@ def parse_font_family(css_string):
|
|||||||
commit()
|
commit()
|
||||||
return families
|
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):
|
class FontFaceRule(object):
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@ from __future__ import (unicode_literals, division, absolute_import,
|
|||||||
__license__ = 'GPL v3'
|
__license__ = 'GPL v3'
|
||||||
__copyright__ = '2014, Kovid Goyal <kovid at kovidgoyal.net>'
|
__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
|
from tinycss.tests import BaseTest
|
||||||
|
|
||||||
class TestFonts3(BaseTest):
|
class TestFonts3(BaseTest):
|
||||||
@ -48,4 +48,16 @@ class TestFonts3(BaseTest):
|
|||||||
for single in ('serif', 'sans-serif', 'A B C'):
|
for single in ('serif', 'sans-serif', 'A B C'):
|
||||||
self.ae([single], parse_font_family(single))
|
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'])
|
||||||
|
Loading…
x
Reference in New Issue
Block a user