Source code for gws.tools.style

import re
import base64

import gws
import gws.tools.net

import gws.types as t


#:export
[docs]class StyleStrokeLineCap(t.Enum): butt = 'butt' round = 'round' square = 'square'
#:export
[docs]class StyleStrokeLineJoin(t.Enum): bevel = 'bevel' round = 'round' miter = 'miter'
#:export
[docs]class StyleMarker(t.Enum): circle = 'circle' square = 'square' arrow = 'arrow' cross = 'cross'
#:export
[docs]class StyleLabelOption(t.Enum): all = 'all' none = 'none'
#:export
[docs]class StyleGeometryOption(t.Enum): all = 'all' none = 'none'
#:export
[docs]class StyleLabelAlign(t.Enum): left = 'left' right = 'right' center = 'center'
#:export
[docs]class StyleLabelPlacement(t.Enum): start = 'start' end = 'end' middle = 'middle'
#:export
[docs]class StyleLabelFontStyle(t.Enum): normal = 'normal' italic = 'italic'
#:export
[docs]class StyleLabelFontWeight(t.Enum): normal = 'normal' bold = 'bold'
#:export
[docs]class StyleValues(t.Data): fill: t.Optional[t.Color] stroke: t.Optional[t.Color] stroke_dasharray: t.Optional[t.List[int]] stroke_dashoffset: t.Optional[int] stroke_linecap: t.Optional[StyleStrokeLineCap] stroke_linejoin: t.Optional[StyleStrokeLineJoin] stroke_miterlimit: t.Optional[int] stroke_width: t.Optional[int] marker: t.Optional[StyleMarker] marker_fill: t.Optional[t.Color] marker_size: t.Optional[int] marker_stroke: t.Optional[t.Color] marker_stroke_dasharray: t.Optional[t.List[int]] marker_stroke_dashoffset: t.Optional[int] marker_stroke_linecap: t.Optional[StyleStrokeLineCap] marker_stroke_linejoin: t.Optional[StyleStrokeLineJoin] marker_stroke_miterlimit: t.Optional[int] marker_stroke_width: t.Optional[int] with_geometry: t.Optional[StyleGeometryOption] with_label: t.Optional[StyleLabelOption] label_align: t.Optional[StyleLabelAlign] label_background: t.Optional[t.Color] label_fill: t.Optional[t.Color] label_font_family: t.Optional[str] label_font_size: t.Optional[int] label_font_style: t.Optional[StyleLabelFontStyle] label_font_weight: t.Optional[StyleLabelFontWeight] label_line_height: t.Optional[int] label_max_scale: t.Optional[int] label_min_scale: t.Optional[int] label_offset_x: t.Optional[int] label_offset_y: t.Optional[int] label_padding: t.Optional[t.List[int]] label_placement: t.Optional[StyleLabelPlacement] label_stroke: t.Optional[t.Color] label_stroke_dasharray: t.Optional[t.List[int]] label_stroke_dashoffset: t.Optional[int] label_stroke_linecap: t.Optional[StyleStrokeLineCap] label_stroke_linejoin: t.Optional[StyleStrokeLineJoin] label_stroke_miterlimit: t.Optional[int] label_stroke_width: t.Optional[int] point_size: t.Optional[int] icon: t.Optional[str] offset_x: t.Optional[int] offset_y: t.Optional[int]
## _DEFAULT_VALUES = dict( fill=None, stroke=None, stroke_dasharray=[], stroke_dashoffset=0, stroke_linecap=StyleStrokeLineCap.butt, stroke_linejoin=StyleStrokeLineJoin.miter, stroke_miterlimit=0, stroke_width=0, marker_size=0, marker_stroke_dasharray=[], marker_stroke_dashoffset=0, marker_stroke_linecap=StyleStrokeLineCap.butt, marker_stroke_linejoin=StyleStrokeLineJoin.miter, marker_stroke_miterlimit=0, marker_stroke_width=0, with_geometry=StyleGeometryOption.all, with_label=StyleLabelOption.all, label_align=StyleLabelAlign.center, label_font_family='sans-serif', label_font_size=12, label_font_style=StyleLabelFontStyle.normal, label_font_weight=StyleLabelFontWeight.normal, label_line_height=1, label_max_scale=1000000000, label_min_scale=0, label_offset_x=0, label_offset_y=0, label_placement=StyleLabelPlacement.middle, label_stroke_dasharray=[], label_stroke_dashoffset=0, label_stroke_linecap=StyleStrokeLineCap.butt, label_stroke_linejoin=StyleStrokeLineJoin.miter, label_stroke_miterlimit=0, label_stroke_width=0, point_size=10, icon='', offset_x=0, offset_y=0, ) ## _color_patterns = ( r'^#[0-9a-fA-F]{3}$', r'^#[0-9a-fA-F]{6}$', r'^#[0-9a-fA-F]{8}$', r'^rgb\(\d{1,3},\d{1,3},\d{1,3}\)$', r'^rgba\(\d{1,3},\d{1,3},\d{1,3},\d?(\.\d{1,3})?\)$', r'^[a-z]{3,50}$', ) def _color(val): val = re.sub(r'\s+', '', str(val)) if any(re.match(p, val) for p in _color_patterns): return val def _icon(val): if not val: return s = val m = re.match(r'^url\((.+?)\)$', val) if m: s = m.group(1).strip('\'\"') try: return _to_data_url(s) except Exception as e: raise gws.Error(f'cannot load {val!r}') from e def _to_data_url(val): if val.startswith('data:'): return val # @TODO security, this should be only allowed in a trusted context if re.match(r'^https?:', val): svg = gws.tools.net.http_request(val).content else: svg = gws.read_file_b(val) return 'data:image/svg+xml;base64,' + base64.standard_b64encode(svg).decode('utf8') def _px(val): if isinstance(val, int): return val m = re.match(r'^(-?\d+)px', str(val)) return _int(m.group(1) if m else val) def _int(val): if isinstance(val, int): return val try: return int(val) except: pass def _intlist(val): if not isinstance(val, (list, tuple)): val = re.split(r'[,\s]+', str(val)) val = [_int(x) for x in val] if any(x is None for x in val): return None return val def _padding(val): if not isinstance(val, (list, tuple)): val = re.split(r'[,\s]+', str(val)) val = [_px(x) for x in val] if any(x is None for x in val): return None if len(val) == 4: return val if len(val) == 2: return [val[0], val[1], val[0], val[1]] if len(val) == 1: return [val[0], val[0], val[0], val[0]] return None def _enum(cls): def _check(val): if val in vars(cls): return val return _check def _str(val): val = str(val).strip() return val or None ## class _Parser: fill = _color stroke = _color stroke_dasharray = _intlist stroke_dashoffset = _px stroke_linecap = _enum(StyleStrokeLineCap) stroke_linejoin = _enum(StyleStrokeLineJoin) stroke_miterlimit = _px stroke_width = _px marker = _enum(StyleMarker) marker_fill = _color marker_size = _px marker_stroke = _color marker_stroke_dasharray = _intlist marker_stroke_dashoffset = _px marker_stroke_linecap = _enum(StyleStrokeLineCap) marker_stroke_linejoin = _enum(StyleStrokeLineJoin) marker_stroke_miterlimit = _px marker_stroke_width = _px with_geometry = _enum(StyleGeometryOption) with_label = _enum(StyleLabelOption) label_align = _enum(StyleLabelAlign) label_background = _color label_fill = _color label_font_family = _str label_font_size = _px label_font_style = _enum(StyleLabelFontStyle) label_font_weight = _enum(StyleLabelFontWeight) label_line_height = _int label_max_scale = _int label_min_scale = _int label_offset_x = _px label_offset_y = _px label_padding = _padding label_placement = _enum(StyleLabelPlacement) label_stroke = _color label_stroke_dasharray = _intlist label_stroke_dashoffset = _px label_stroke_linecap = _enum(StyleStrokeLineCap) label_stroke_linejoin = _enum(StyleStrokeLineJoin) label_stroke_miterlimit = _px label_stroke_width = _px point_size = _px icon = _icon offset_x = _px offset_y = _px
[docs]def from_css_dict(d: dict) -> t.StyleValues: values = t.StyleValues(_DEFAULT_VALUES) for k, v in d.items(): if v is None: continue k = k.replace('-', '_') if k.startswith('__'): k = k[2:] fn = gws.get(_Parser, k) if fn: v = fn(v) if v is not None: setattr(values, k, v) return values
# @TODO use a real parser
[docs]def from_css_text(text) -> t.StyleValues: d = {} for r in text.split(';'): r = r.strip() if not r: continue r = r.split(':') d[r[0].strip()] = r[1].strip() return from_css_dict(d)