385 lines
12 KiB
Python
385 lines
12 KiB
Python
from math import pi
|
|
import re
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Built-in CSS color names
|
|
# See: http://www.w3.org/TR/css3-color/#svg-color
|
|
|
|
COLOR_NAMES = {
|
|
'aliceblue': (240, 248, 255, 1),
|
|
'antiquewhite': (250, 235, 215, 1),
|
|
'aqua': (0, 255, 255, 1),
|
|
'aquamarine': (127, 255, 212, 1),
|
|
'azure': (240, 255, 255, 1),
|
|
'beige': (245, 245, 220, 1),
|
|
'bisque': (255, 228, 196, 1),
|
|
'black': (0, 0, 0, 1),
|
|
'blanchedalmond': (255, 235, 205, 1),
|
|
'blue': (0, 0, 255, 1),
|
|
'blueviolet': (138, 43, 226, 1),
|
|
'brown': (165, 42, 42, 1),
|
|
'burlywood': (222, 184, 135, 1),
|
|
'cadetblue': (95, 158, 160, 1),
|
|
'chartreuse': (127, 255, 0, 1),
|
|
'chocolate': (210, 105, 30, 1),
|
|
'coral': (255, 127, 80, 1),
|
|
'cornflowerblue': (100, 149, 237, 1),
|
|
'cornsilk': (255, 248, 220, 1),
|
|
'crimson': (220, 20, 60, 1),
|
|
'cyan': (0, 255, 255, 1),
|
|
'darkblue': (0, 0, 139, 1),
|
|
'darkcyan': (0, 139, 139, 1),
|
|
'darkgoldenrod': (184, 134, 11, 1),
|
|
'darkgray': (169, 169, 169, 1),
|
|
'darkgreen': (0, 100, 0, 1),
|
|
'darkkhaki': (189, 183, 107, 1),
|
|
'darkmagenta': (139, 0, 139, 1),
|
|
'darkolivegreen': (85, 107, 47, 1),
|
|
'darkorange': (255, 140, 0, 1),
|
|
'darkorchid': (153, 50, 204, 1),
|
|
'darkred': (139, 0, 0, 1),
|
|
'darksalmon': (233, 150, 122, 1),
|
|
'darkseagreen': (143, 188, 143, 1),
|
|
'darkslateblue': (72, 61, 139, 1),
|
|
'darkslategray': (47, 79, 79, 1),
|
|
'darkturquoise': (0, 206, 209, 1),
|
|
'darkviolet': (148, 0, 211, 1),
|
|
'deeppink': (255, 20, 147, 1),
|
|
'deepskyblue': (0, 191, 255, 1),
|
|
'dimgray': (105, 105, 105, 1),
|
|
'dodgerblue': (30, 144, 255, 1),
|
|
'firebrick': (178, 34, 34, 1),
|
|
'floralwhite': (255, 250, 240, 1),
|
|
'forestgreen': (34, 139, 34, 1),
|
|
'fuchsia': (255, 0, 255, 1),
|
|
'gainsboro': (220, 220, 220, 1),
|
|
'ghostwhite': (248, 248, 255, 1),
|
|
'gold': (255, 215, 0, 1),
|
|
'goldenrod': (218, 165, 32, 1),
|
|
'gray': (128, 128, 128, 1),
|
|
'green': (0, 128, 0, 1),
|
|
'greenyellow': (173, 255, 47, 1),
|
|
'honeydew': (240, 255, 240, 1),
|
|
'hotpink': (255, 105, 180, 1),
|
|
'indianred': (205, 92, 92, 1),
|
|
'indigo': (75, 0, 130, 1),
|
|
'ivory': (255, 255, 240, 1),
|
|
'khaki': (240, 230, 140, 1),
|
|
'lavender': (230, 230, 250, 1),
|
|
'lavenderblush': (255, 240, 245, 1),
|
|
'lawngreen': (124, 252, 0, 1),
|
|
'lemonchiffon': (255, 250, 205, 1),
|
|
'lightblue': (173, 216, 230, 1),
|
|
'lightcoral': (240, 128, 128, 1),
|
|
'lightcyan': (224, 255, 255, 1),
|
|
'lightgoldenrodyellow': (250, 250, 210, 1),
|
|
'lightgreen': (144, 238, 144, 1),
|
|
'lightgrey': (211, 211, 211, 1),
|
|
'lightpink': (255, 182, 193, 1),
|
|
'lightsalmon': (255, 160, 122, 1),
|
|
'lightseagreen': (32, 178, 170, 1),
|
|
'lightskyblue': (135, 206, 250, 1),
|
|
'lightslategray': (119, 136, 153, 1),
|
|
'lightsteelblue': (176, 196, 222, 1),
|
|
'lightyellow': (255, 255, 224, 1),
|
|
'lime': (0, 255, 0, 1),
|
|
'limegreen': (50, 205, 50, 1),
|
|
'linen': (250, 240, 230, 1),
|
|
'magenta': (255, 0, 255, 1),
|
|
'maroon': (128, 0, 0, 1),
|
|
'mediumaquamarine': (102, 205, 170, 1),
|
|
'mediumblue': (0, 0, 205, 1),
|
|
'mediumorchid': (186, 85, 211, 1),
|
|
'mediumpurple': (147, 112, 219, 1),
|
|
'mediumseagreen': (60, 179, 113, 1),
|
|
'mediumslateblue': (123, 104, 238, 1),
|
|
'mediumspringgreen': (0, 250, 154, 1),
|
|
'mediumturquoise': (72, 209, 204, 1),
|
|
'mediumvioletred': (199, 21, 133, 1),
|
|
'midnightblue': (25, 25, 112, 1),
|
|
'mintcream': (245, 255, 250, 1),
|
|
'mistyrose': (255, 228, 225, 1),
|
|
'moccasin': (255, 228, 181, 1),
|
|
'navajowhite': (255, 222, 173, 1),
|
|
'navy': (0, 0, 128, 1),
|
|
'oldlace': (253, 245, 230, 1),
|
|
'olive': (128, 128, 0, 1),
|
|
'olivedrab': (107, 142, 35, 1),
|
|
'orange': (255, 165, 0, 1),
|
|
'orangered': (255, 69, 0, 1),
|
|
'orchid': (218, 112, 214, 1),
|
|
'palegoldenrod': (238, 232, 170, 1),
|
|
'palegreen': (152, 251, 152, 1),
|
|
'paleturquoise': (175, 238, 238, 1),
|
|
'palevioletred': (219, 112, 147, 1),
|
|
'papayawhip': (255, 239, 213, 1),
|
|
'peachpuff': (255, 218, 185, 1),
|
|
'peru': (205, 133, 63, 1),
|
|
'pink': (255, 192, 203, 1),
|
|
'plum': (221, 160, 221, 1),
|
|
'powderblue': (176, 224, 230, 1),
|
|
'purple': (128, 0, 128, 1),
|
|
'red': (255, 0, 0, 1),
|
|
'rosybrown': (188, 143, 143, 1),
|
|
'royalblue': (65, 105, 225, 1),
|
|
'saddlebrown': (139, 69, 19, 1),
|
|
'salmon': (250, 128, 114, 1),
|
|
'sandybrown': (244, 164, 96, 1),
|
|
'seagreen': (46, 139, 87, 1),
|
|
'seashell': (255, 245, 238, 1),
|
|
'sienna': (160, 82, 45, 1),
|
|
'silver': (192, 192, 192, 1),
|
|
'skyblue': (135, 206, 235, 1),
|
|
'slateblue': (106, 90, 205, 1),
|
|
'slategray': (112, 128, 144, 1),
|
|
'snow': (255, 250, 250, 1),
|
|
'springgreen': (0, 255, 127, 1),
|
|
'steelblue': (70, 130, 180, 1),
|
|
'tan': (210, 180, 140, 1),
|
|
'teal': (0, 128, 128, 1),
|
|
'thistle': (216, 191, 216, 1),
|
|
'tomato': (255, 99, 71, 1),
|
|
'transparent': (0, 0, 0, 0),
|
|
'turquoise': (64, 224, 208, 1),
|
|
'violet': (238, 130, 238, 1),
|
|
'wheat': (245, 222, 179, 1),
|
|
'white': (255, 255, 255, 1),
|
|
'whitesmoke': (245, 245, 245, 1),
|
|
'yellow': (255, 255, 0, 1),
|
|
'yellowgreen': (154, 205, 50, 1),
|
|
}
|
|
COLOR_LOOKUP = dict((v, k) for (k, v) in COLOR_NAMES.items())
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Built-in CSS units
|
|
# See: http://www.w3.org/TR/2013/CR-css3-values-20130730/#numeric-types
|
|
|
|
# Maps units to a set of common units per type, with conversion factors
|
|
BASE_UNIT_CONVERSIONS = {
|
|
# Lengths
|
|
'mm': (1, 'mm'),
|
|
'cm': (10, 'mm'),
|
|
'in': (25.4, 'mm'),
|
|
'px': (25.4 / 96, 'mm'),
|
|
'pt': (25.4 / 72, 'mm'),
|
|
'pc': (25.4 / 6, 'mm'),
|
|
|
|
# Angles
|
|
'deg': (1 / 360, 'turn'),
|
|
'grad': (1 / 400, 'turn'),
|
|
'rad': (pi / 2, 'turn'),
|
|
'turn': (1, 'turn'),
|
|
|
|
# Times
|
|
'ms': (1, 'ms'),
|
|
's': (1000, 'ms'),
|
|
|
|
# Frequencies
|
|
'hz': (1, 'hz'),
|
|
'khz': (1000, 'hz'),
|
|
|
|
# Resolutions
|
|
'dpi': (1, 'dpi'),
|
|
'dpcm': (2.54, 'dpi'),
|
|
'dppx': (96, 'dpi'),
|
|
}
|
|
|
|
|
|
def get_conversion_factor(unit):
|
|
"""Look up the "base" unit for this unit and the factor for converting to
|
|
it.
|
|
|
|
Returns a 2-tuple of `factor, base_unit`.
|
|
"""
|
|
if unit in BASE_UNIT_CONVERSIONS:
|
|
return BASE_UNIT_CONVERSIONS[unit]
|
|
else:
|
|
return 1, unit
|
|
|
|
|
|
def convert_units_to_base_units(units):
|
|
"""Convert a set of units into a set of "base" units.
|
|
|
|
Returns a 2-tuple of `factor, new_units`.
|
|
"""
|
|
total_factor = 1
|
|
new_units = []
|
|
for unit in units:
|
|
if unit not in BASE_UNIT_CONVERSIONS:
|
|
continue
|
|
|
|
factor, new_unit = BASE_UNIT_CONVERSIONS[unit]
|
|
total_factor *= factor
|
|
new_units.append(new_unit)
|
|
|
|
new_units.sort()
|
|
return total_factor, tuple(new_units)
|
|
|
|
|
|
def count_base_units(units):
|
|
"""Returns a dict mapping names of base units to how many times they
|
|
appear in the given iterable of units. Effectively this counts how
|
|
many length units you have, how many time units, and so forth.
|
|
"""
|
|
ret = {}
|
|
for unit in units:
|
|
factor, base_unit = get_conversion_factor(unit)
|
|
|
|
ret.setdefault(base_unit, 0)
|
|
ret[base_unit] += 1
|
|
|
|
return ret
|
|
|
|
|
|
def cancel_base_units(units, to_remove):
|
|
"""Given a list of units, remove a specified number of each base unit.
|
|
|
|
Arguments:
|
|
units: an iterable of units
|
|
to_remove: a mapping of base_unit => count, such as that returned from
|
|
count_base_units
|
|
|
|
Returns a 2-tuple of (factor, remaining_units).
|
|
"""
|
|
|
|
# Copy the dict since we're about to mutate it
|
|
to_remove = to_remove.copy()
|
|
remaining_units = []
|
|
total_factor = 1
|
|
|
|
for unit in units:
|
|
factor, base_unit = get_conversion_factor(unit)
|
|
if not to_remove.get(base_unit, 0):
|
|
remaining_units.append(unit)
|
|
continue
|
|
|
|
total_factor *= factor
|
|
to_remove[base_unit] -= 1
|
|
|
|
return total_factor, remaining_units
|
|
|
|
|
|
# A fixed set of units can be omitted when the value is 0
|
|
# See: http://www.w3.org/TR/2013/CR-css3-values-20130730/#lengths
|
|
ZEROABLE_UNITS = frozenset((
|
|
# Relative lengths
|
|
'em', 'ex', 'ch', 'rem',
|
|
# Viewport
|
|
'vw', 'vh', 'vmin', 'vmax',
|
|
# Absolute lengths
|
|
'cm', 'mm', 'in', 'px', 'pt', 'pc',
|
|
))
|
|
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Built-in CSS function reference
|
|
|
|
# Known function names
|
|
BUILTIN_FUNCTIONS = frozenset([
|
|
# CSS2
|
|
'attr', 'counter', 'counters', 'url', 'rgb', 'rect',
|
|
|
|
# CSS3 values: http://www.w3.org/TR/css3-values/
|
|
'calc', 'min', 'max', 'cycle',
|
|
|
|
# CSS3 colors: http://www.w3.org/TR/css3-color/
|
|
'rgba', 'hsl', 'hsla',
|
|
|
|
# CSS3 fonts: http://www.w3.org/TR/css3-fonts/
|
|
'local', 'format',
|
|
|
|
# CSS3 images: http://www.w3.org/TR/css3-images/
|
|
'image', 'element',
|
|
'linear-gradient', 'radial-gradient',
|
|
'repeating-linear-gradient', 'repeating-radial-gradient',
|
|
|
|
# CSS3 transforms: http://www.w3.org/TR/css3-transforms/
|
|
'perspective',
|
|
'matrix', 'matrix3d',
|
|
'rotate', 'rotateX', 'rotateY', 'rotateZ', 'rotate3d',
|
|
'translate', 'translateX', 'translateY', 'translateZ', 'translate3d',
|
|
'scale', 'scaleX', 'scaleY', 'scaleZ', 'scale3d',
|
|
'skew', 'skewX', 'skewY',
|
|
|
|
# CSS3 transitions: http://www.w3.org/TR/css3-transitions/
|
|
'cubic-bezier', 'steps',
|
|
|
|
# CSS filter effects:
|
|
# https://dvcs.w3.org/hg/FXTF/raw-file/tip/filters/index.html
|
|
'grayscale', 'sepia', 'saturate', 'hue-rotate', 'invert', 'opacity',
|
|
'brightness', 'contrast', 'blur', 'drop-shadow', 'custom',
|
|
|
|
# CSS4 image module:
|
|
# http://dev.w3.org/csswg/css-images/
|
|
'image-set', 'cross-fade',
|
|
'conic-gradient', 'repeating-conic-gradient',
|
|
|
|
# Others
|
|
'color-stop', # Older version of CSS3 gradients
|
|
'mask', # ???
|
|
'from', 'to', # Very old WebKit gradient syntax
|
|
])
|
|
|
|
|
|
def is_builtin_css_function(name):
|
|
"""Returns whether the given `name` looks like the name of a builtin CSS
|
|
function.
|
|
|
|
Unrecognized functions not in this list produce warnings.
|
|
"""
|
|
name = name.replace('_', '-')
|
|
|
|
if name in BUILTIN_FUNCTIONS:
|
|
return True
|
|
|
|
# Vendor-specific functions (-foo-bar) are always okay
|
|
if name[0] == '-' and '-' in name[1:]:
|
|
return True
|
|
|
|
return False
|
|
|
|
# ------------------------------------------------------------------------------
|
|
# Bits and pieces of grammar, as regexen
|
|
|
|
SEPARATOR = '\x00'
|
|
|
|
_expr_glob_re = re.compile(r'''
|
|
\#\{(.*?)\} # Global Interpolation only
|
|
''', re.VERBOSE)
|
|
|
|
# XXX these still need to be fixed; the //-in-functions thing is a chumpy hack
|
|
_ml_comment_re = re.compile(r'\/\*(.*?)\*\/', re.DOTALL)
|
|
_sl_comment_re = re.compile(r'(?<!\burl[(])(?<!\w{2}:)\/\/.*')
|
|
|
|
_escape_chars_re = re.compile(r'([^-a-zA-Z0-9_])')
|
|
_interpolate_re = re.compile(r'(#\{\s*)?(\$[-\w]+)(?(1)\s*\})')
|
|
_spaces_re = re.compile(r'\s+')
|
|
_expand_rules_space_re = re.compile(r'\s*{')
|
|
_collapse_properties_space_re = re.compile(r'([:#])\s*{')
|
|
_variable_re = re.compile('^\\$[-a-zA-Z0-9_]+$')
|
|
|
|
_strings_re = re.compile(r'([\'"]).*?\1')
|
|
|
|
_has_placeholder_re = re.compile(r'(?<!\w)([a-z]\w*)?%')
|
|
_prop_split_re = re.compile(r'[:=]')
|
|
_has_code_re = re.compile('''
|
|
(?:^|(?<=[{;}])) # the character just before it should be a '{', a ';' or a '}'
|
|
\s* # ...followed by any number of spaces
|
|
(?:
|
|
(?:
|
|
\+
|
|
| @include
|
|
| @warn
|
|
| @mixin
|
|
| @function
|
|
| @if
|
|
| @else
|
|
| @for
|
|
| @each
|
|
)
|
|
(?![^(:;}]*['"])
|
|
|
|
|
@import
|
|
)
|
|
''', re.VERBOSE)
|