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'(?