# coding: utf-8
"""
tinycss.colors3
---------------
Parser for CSS 3 Fonts syntax:
https://www.w3.org/TR/css-fonts-3/
Adds support for font-face and font-feature-values rules.
:copyright: (c) 2016 by Kozea.
:license: BSD, see LICENSE for more details.
"""
from __future__ import division, unicode_literals
from .css21 import CSS21Parser, ParseError
[docs]
class FontFaceRule(object):
"""A parsed at-rule for font faces.
.. attribute:: at_keyword
Always ``'@font-face'``.
.. attribute:: declarations
A list of :class:`~.css21.Declaration` objects.
.. attribute:: line
Source line where this was read.
.. attribute:: column
Source column where this was read.
"""
def __init__(self, at_keyword, declarations, line, column):
assert at_keyword == '@font-face'
self.at_keyword = at_keyword
self.declarations = declarations
self.line = line
self.column = column
[docs]
class FontFeatureValuesRule(object):
"""A parsed at-rule for font feature values.
.. attribute:: at_keyword
Always ``'@font-feature-values'``.
.. attribute:: line
Source line where this was read.
.. attribute:: column
Source column where this was read.
.. attribute:: at_rules
The list of parsed at-rules inside the @font-feature-values block, in
source order.
.. attribute:: family_names
A list of strings representing font families.
"""
def __init__(self, at_keyword, at_rules, family_names, line, column):
assert at_keyword == '@font-feature-values'
self.at_keyword = at_keyword
self.family_names = family_names
self.at_rules = at_rules
self.line = line
self.column = column
[docs]
class FontFeatureRule(object):
"""A parsed at-rule for font features.
.. attribute:: at_keyword
One of the 16 following strings:
* ``@stylistic``
* ``@styleset``
* ``@character-variant``
* ``@swash``
* ``@ornaments``
* ``@annotation``
.. attribute:: declarations
A list of :class:`~.css21.Declaration` objects.
.. attribute:: line
Source line where this was read.
.. attribute:: column
Source column where this was read.
"""
def __init__(self, at_keyword, declarations, line, column):
self.at_keyword = at_keyword
self.declarations = declarations
self.line = line
self.column = column
[docs]
class CSSFonts3Parser(CSS21Parser):
"""Extend :class:`~.css21.CSS21Parser` for `CSS 3 Fonts`_ syntax.
.. _CSS 3 Fonts: https://www.w3.org/TR/css-fonts-3/
"""
FONT_FEATURE_VALUES_AT_KEYWORDS = [
'@stylistic',
'@styleset',
'@character-variant',
'@swash',
'@ornaments',
'@annotation',
]
def parse_at_rule(self, rule, previous_rules, errors, context):
if rule.at_keyword == '@font-face':
if rule.head:
raise ParseError(
rule.head[0],
'unexpected {0} token in {1} rule header'.format(
rule.head[0].type, rule.at_keyword))
declarations, body_errors = self.parse_declaration_list(rule.body)
errors.extend(body_errors)
return FontFaceRule(
rule.at_keyword, declarations, rule.line, rule.column)
elif rule.at_keyword == '@font-feature-values':
family_names = tuple(
self.parse_font_feature_values_family_names(rule.head))
at_rules, body_errors = (
self.parse_rules(rule.body or [], '@font-feature-values'))
errors.extend(body_errors)
return FontFeatureValuesRule(
rule.at_keyword, at_rules, family_names,
rule.line, rule.column)
elif rule.at_keyword in self.FONT_FEATURE_VALUES_AT_KEYWORDS:
if context != '@font-feature-values':
raise ParseError(
rule, '{0} rule not allowed in {1}'.format(
rule.at_keyword, context))
declarations, body_errors = self.parse_declaration_list(rule.body)
errors.extend(body_errors)
return FontFeatureRule(
rule.at_keyword, declarations, rule.line, rule.column)
return super(CSSFonts3Parser, self).parse_at_rule(
rule, previous_rules, errors, context)
def parse_font_feature_values_family_names(self, tokens):
"""Parse an @font-feature-values selector.
:param tokens:
An iterable of token, typically from the ``head`` attribute of
an unparsed :class:`AtRule`.
:returns:
A generator of strings representing font families.
:raises:
:class:`~.parsing.ParseError` on invalid selectors
"""
family = ''
current_string = False
for token in tokens:
if token.type == 'DELIM' and token.value == ',' and family:
yield family
family = ''
current_string = False
elif token.type == 'STRING' and not family and (
current_string is False):
family = token.value
current_string = True
elif token.type == 'IDENT' and not current_string:
if family:
family += ' '
family += token.value
elif token.type != 'S':
family = ''
break
if family:
yield family
else:
raise ParseError(token, 'invalid @font-feature-values selector')