File: color5.py

package info (click to toggle)
python-tinycss2 1.5.1-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 844 kB
  • sloc: python: 2,811; makefile: 21
file content (135 lines) | stat: -rw-r--r-- 4,087 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
from . import color4

#: Supported color spaces.
COLOR_SPACES = color4.COLOR_SPACES | {'device-cmyk'}
#: Supported color schemes.
COLOR_SCHEMES = {'light', 'dark'}
#: XYZ values of the D50 white point, normalized to Y=1.
D50 = color4.D50
#: XYZ values of the D65 white point, normalized to Y=1.
D65 = color4.D65


class Color(color4.Color):
    COLOR_SPACES = None


def parse_color(input, color_schemes=None):
    """Parse a color value as defined in CSS Color Level 5.

    https://www.w3.org/TR/css-color-5/

    :type input: :obj:`str` or :term:`iterable`
    :param input: A string or an iterable of :term:`component values`.
    :type color_schemes: :obj:`str` or :term:`iterable`
    :param color_schemes: the ``'normal'`` string, or an iterable of color
        schemes used to resolve the ``light-dark()`` function.
    :returns:
        * :obj:`None` if the input is not a valid color value.
          (No exception is raised.)
        * The string ``'currentcolor'`` for the ``currentcolor`` keyword
        * A :class:`Color` object for every other values, including keywords.

    """
    color = color4.parse_color(input)

    if color:
        return color

    if color_schemes is None or color_schemes == 'normal':
        color_scheme = 'light'
    else:
        for color_scheme in color_schemes:
            if color_scheme in COLOR_SCHEMES:
                break
        else:
            color_scheme = 'light'

    if isinstance(input, str):
        token = color4.parse_one_component_value(input, skip_comments=True)
    else:
        token = input

    if token.type == 'function':
        tokens = [
            token for token in token.arguments
            if token.type not in ('whitespace', 'comment')]
        name = token.lower_name
        alpha = []

        if name == 'color':
            space, *tokens = tokens

        old_syntax = all(token == ',' for token in tokens[1::2])
        if old_syntax:
            tokens = tokens[::2]
        else:
            for index, token in enumerate(tokens):
                if token == '/':
                    alpha = tokens[index + 1:]
                    tokens = tokens[:index]
                    break

        if name == 'device-cmyk':
            return _parse_device_cmyk(tokens, color4._parse_alpha(alpha), old_syntax)
        elif name == 'color':
            return _parse_color(space, tokens, color4._parse_alpha(alpha))
        elif name == 'light-dark':
            return _parse_light_dark(tokens, color_scheme)
        else:
            return


def _parse_device_cmyk(args, alpha, old_syntax):
    """Parse a list of CMYK channels.

    If args is a list of 4 NUMBER or PERCENTAGE tokens, return
    device-cmyk :class:`Color`. Otherwise, return None.

    Input C, M, Y, K ranges are [0, 1], output are [0, 1].

    """
    if old_syntax:
        if color4._types(args) != {'number'}:
            return
    else:
        if not color4._types(args) <= {'number', 'percentage'}:
            return
    if len(args) != 4:
        return
    cmyk = [
        arg.value if arg.type == 'number' else
        arg.value / 100 if arg.type == 'percentage' else None
        for arg in args]
    cmyk = [max(0., min(1., float(channel))) for channel in cmyk]
    return Color('device-cmyk', cmyk, alpha)


def _parse_light_dark(args, color_scheme):
    colors = []
    for arg in args:
        if color := parse_color(arg, color_scheme):
            colors.append(color)
    if len(colors) == 2:
        if color_scheme == 'light':
            return colors[0]
        else:
            return colors[1]
    return


def _parse_color(space, args, alpha):
    """Parse a color space name list of coordinates.

    Ranges are [0, 1].

    """
    if not color4._types(args) <= {'number', 'percentage'}:
        return
    if space.type != 'ident' or not space.value.startswith('--'):
        return
    coordinates = [
        arg.value if arg.type == 'number' else
        arg.value / 100 if arg.type == 'percentage' else None
        for arg in args]
    return Color(space.value, coordinates, alpha)